This commit is contained in:
Marshal
2026-06-06 06:32:04 +00:00
parent f8270175a2
commit 053b4f35c5
15 changed files with 894 additions and 661 deletions

View File

@@ -1,27 +1,18 @@
import { useEffect, useState } from "react";
import { Loader2 } from "lucide-react";
import {
Modal,
Button,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
Field,
FieldContent,
FieldLabel,
Input,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Separator,
Switch,
TextInput,
Textarea,
} from "@edr/ui-common";
Select,
Switch,
Stack,
Group,
Divider,
Text,
Box,
} from "@mantine/core";
import {
RULE_ENGINE_SELECT_NONE,
@@ -29,8 +20,6 @@ import {
} from "@/pages/ruleEngine/config/resources";
import type { RuleEngineRecord } from "@/types/rule-engine";
import { ruleEngineField, ruleEngineSurface } from "./ruleEngineStyles";
export interface RuleEngineFormDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
@@ -142,133 +131,174 @@ const RuleEngineFormDialog = ({
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className={ruleEngineSurface.dialog}>
<DialogHeader className="space-y-1">
<DialogTitle className="text-lg font-semibold">{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<Modal
opened={open}
onClose={() => onOpenChange(false)}
title={title}
centered
size="md"
radius="lg"
styles={{
header: {
paddingBottom: "20px",
borderBottom: "1px solid var(--mantine-color-gray-2)",
},
body: {
paddingTop: "24px",
paddingBottom: "24px",
},
title: {
fontSize: "1.25rem",
fontWeight: 700,
color: "var(--mantine-color-gray-9)",
},
}}
>
<form onSubmit={handleSubmit}>
<Stack gap="lg">
<Text size="sm" c="dimmed">
{description}
</Text>
<form onSubmit={handleSubmit} className="space-y-1">
<div className="max-h-[min(60vh,28rem)] space-y-4 overflow-y-auto pr-1">
{fields.map((field) => (
<Field key={field.name} orientation="vertical" className="gap-1.5">
{field.type === "boolean" ? (
<div className={ruleEngineField.switchRow}>
<div className="min-w-0">
<FieldLabel htmlFor={field.name} className={ruleEngineField.label}>
{field.label}
</FieldLabel>
<p className={ruleEngineField.switchHint}>
{Boolean(values[field.name]) ? "Enabled" : "Disabled"}
</p>
</div>
<Switch
id={field.name}
checked={Boolean(values[field.name])}
onCheckedChange={(checked) => setField(field.name, checked)}
<div style={{ maxHeight: "calc(60vh - 150px)", overflowY: "auto", paddingRight: "12px" }}>
<Stack gap="lg">
{fields.map((field) => (
<Box key={field.name}>
{field.type === "boolean" ? (
<Group
justify="space-between"
align="center"
p="lg"
style={{
background: "linear-gradient(135deg, var(--mantine-color-blue-0) 0%, var(--mantine-color-cyan-0) 100%)",
borderRadius: "12px",
border: "1px solid var(--mantine-color-blue-2)",
}}
>
<div>
<Text size="sm" fw={600} mb="4px">
{field.label}
</Text>
<Text size="xs" c="dimmed">
{Boolean(values[field.name]) ? "✓ Enabled" : "○ Disabled"}
</Text>
</div>
<Switch
checked={Boolean(values[field.name])}
onChange={(e) => setField(field.name, e.currentTarget.checked)}
size="lg"
/>
</Group>
) : field.type === "select" ? (
<Select
label={
<Group gap="4px">
<span>{field.label}</span>
{field.required && <span style={{ color: "var(--mantine-color-red-6)" }}>*</span>}
</Group>
}
placeholder={
selectOptionsLoading ? "Loading options..." : (field.placeholder ?? "Select an option")
}
value={resolveSelectValue(field, values)}
onChange={(v) =>
setField(field.name, v === RULE_ENGINE_SELECT_NONE ? "" : v)
}
disabled={selectOptionsLoading}
data={(field.options ?? []).filter((opt) => opt.value !== "").map((opt) => ({
label: opt.label,
value: opt.value,
}))}
searchable
clearable
size="md"
radius="lg"
styles={{
input: {
borderColor: "var(--mantine-color-gray-3)",
},
}}
/>
</div>
) : (
<>
<FieldLabel htmlFor={field.name} className={ruleEngineField.label}>
{field.label}
{field.required ? (
<span className={ruleEngineField.requiredMark}> *</span>
) : null}
</FieldLabel>
<FieldContent>
{field.type === "select" ? (
<Select
value={resolveSelectValue(field, values)}
onValueChange={(v) =>
setField(
field.name,
v === RULE_ENGINE_SELECT_NONE ? "" : v,
)
}
disabled={selectOptionsLoading}
>
<SelectTrigger
id={field.name}
className={ruleEngineField.selectTrigger}
>
<SelectValue
placeholder={
selectOptionsLoading
? "Loading options..."
: (field.placeholder ?? "Select an option")
}
/>
</SelectTrigger>
<SelectContent className={ruleEngineField.selectContent}>
{(field.options ?? [])
.filter((opt) => opt.value !== "")
.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
) : field.type === "textarea" ? (
<Textarea
id={field.name}
value={String(values[field.name] ?? "")}
onChange={(e) => setField(field.name, e.target.value)}
placeholder={field.placeholder}
className={ruleEngineField.textarea}
/>
) : (
<Input
id={field.name}
type={
field.type === "number"
? "number"
: field.type === "date"
? "date"
: "text"
}
value={String(values[field.name] ?? "")}
onChange={(e) => setField(field.name, e.target.value)}
placeholder={field.placeholder}
className={ruleEngineField.input}
required={field.required}
/>
)}
</FieldContent>
</>
)}
</Field>
))}
) : field.type === "textarea" ? (
<Textarea
label={
<Group gap="4px">
<span>{field.label}</span>
{field.required && <span style={{ color: "var(--mantine-color-red-6)" }}>*</span>}
</Group>
}
value={String(values[field.name] ?? "")}
onChange={(e) => setField(field.name, e.currentTarget.value)}
placeholder={field.placeholder}
required={field.required}
minRows={5}
size="md"
radius="lg"
styles={{
input: {
borderColor: "var(--mantine-color-gray-3)",
},
}}
/>
) : (
<TextInput
label={
<Group gap="4px">
<span>{field.label}</span>
{field.required && <span style={{ color: "var(--mantine-color-red-6)" }}>*</span>}
</Group>
}
type={
field.type === "number"
? "number"
: field.type === "date"
? "date"
: "text"
}
value={String(values[field.name] ?? "")}
onChange={(e) => setField(field.name, e.currentTarget.value)}
placeholder={field.placeholder}
required={field.required}
size="md"
radius="lg"
styles={{
input: {
borderColor: "var(--mantine-color-gray-3)",
},
}}
/>
)}
</Box>
))}
</Stack>
</div>
<Separator className="my-4" />
<Divider />
<DialogFooter className="gap-2 sm:gap-2">
<Group justify="flex-end" gap="sm">
<Button
type="button"
variant="outline"
className="rounded-md"
variant="light"
onClick={() => onOpenChange(false)}
disabled={isSubmitting}
radius="lg"
size="md"
>
Cancel
</Button>
<Button type="submit" className="rounded-md" disabled={isSubmitting}>
{isSubmitting ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
Saving...
</>
) : (
"Save"
)}
<Button
type="submit"
disabled={isSubmitting}
leftSection={isSubmitting && <Loader2 size={18} style={{ animation: "spin 1s linear infinite" }} />}
radius="lg"
color="blue"
size="md"
>
{isSubmitting ? "Saving..." : "Save"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</Group>
</Stack>
</form>
</Modal>
);
};