import { useEffect, useMemo, useState } from "react"; import { Loader2 } from "lucide-react"; import { Modal, Button, TextInput, Textarea, Select, Switch, Stack, Group, Text, Box, SimpleGrid, Divider, } from "@mantine/core"; import { RULE_ENGINE_SELECT_NONE, type FormFieldDef, } from "@/pages/ruleEngine/config/resources"; import type { RuleEngineRecord } from "@/types/rule-engine"; import { RULE_ENGINE_POSITION_END } from "./ruleEngineOrder.utils"; export interface RuleEngineFormDialogProps { open: boolean; onOpenChange: (open: boolean) => void; title: string; description: string; fields: FormFieldDef[]; initialRecord?: RuleEngineRecord | null; isSubmitting: boolean; selectOptionsLoading?: boolean; positionOptions?: { label: string; value: string }[]; positionLoading?: boolean; onSubmit: (values: Record) => void; } type FormRow = | { kind: "pair"; fields: [FormFieldDef, FormFieldDef] } | { kind: "single"; field: FormFieldDef }; const isShortField = (field: FormFieldDef) => field.type === "text" || field.type === "number" || field.type === "select" || field.type === "date"; const buildFormRows = (fields: FormFieldDef[]): FormRow[] => { const rows: FormRow[] = []; let index = 0; while (index < fields.length) { const field = fields[index]; if (field.type === "textarea" || field.type === "boolean") { rows.push({ kind: "single", field }); index += 1; continue; } const next = fields[index + 1]; if (next && isShortField(next)) { rows.push({ kind: "pair", fields: [field, next] }); index += 2; continue; } rows.push({ kind: "single", field }); index += 1; } return rows; }; const buildInitialValues = ( fields: FormFieldDef[], record?: RuleEngineRecord | null, ): Record => { const values: Record = {}; for (const field of fields) { const raw = record?.[field.name]; if (raw !== undefined && raw !== null) { if (field.type === "date" && typeof raw === "string") { values[field.name] = raw.slice(0, 10); } else if (Array.isArray(raw)) { values[field.name] = raw.join(", "); } else { values[field.name] = raw; } } else if (field.type === "boolean") { values[field.name] = false; } else if (field.type === "number") { values[field.name] = ""; } else { values[field.name] = ""; } } return values; }; const resolveSelectValue = ( field: FormFieldDef, values: Record, ): string | undefined => { const raw = values[field.name]; const isEmpty = raw === "" || raw === null || raw === undefined; if (field.optional && isEmpty) { return RULE_ENGINE_SELECT_NONE; } if (isEmpty) { return undefined; } return String(raw); }; const inputStyles = { label: { fontWeight: 600, marginBottom: 6, color: "var(--mantine-color-gray-8)" }, } as const; const FieldLabel = ({ label, required }: { label: string; required?: boolean }) => ( {label} {required ? ( * ) : null} ); const RuleEngineFormDialog = ({ open, onOpenChange, title, fields, initialRecord, isSubmitting, selectOptionsLoading = false, positionOptions, positionLoading = false, onSubmit, }: RuleEngineFormDialogProps) => { const [values, setValues] = useState>(() => buildInitialValues(fields, initialRecord), ); const [position, setPosition] = useState(RULE_ENGINE_POSITION_END); useEffect(() => { if (open) { setValues(buildInitialValues(fields, initialRecord)); setPosition(RULE_ENGINE_POSITION_END); } }, [open, fields, initialRecord]); const visibleFields = useMemo( () => fields.filter((field) => { if ( field.hideWhen && field.hideWhen.equals.includes(String(values[field.hideWhen.field] ?? "")) ) { return false; } if ( field.showWhen && !field.showWhen.equals.includes(String(values[field.showWhen.field] ?? "")) ) { return false; } return true; }), [fields, values], ); const formRows = useMemo(() => buildFormRows(visibleFields), [visibleFields]); const setField = (name: string, value: unknown) => { setValues((current) => ({ ...current, [name]: value })); }; const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); const payload: Record = {}; for (const field of visibleFields) { const raw = values[field.name]; if (field.type === "number") { if (raw === "" || raw === undefined) continue; payload[field.name] = Number(raw); } else if (field.type === "boolean") { payload[field.name] = Boolean(raw); } else if ( field.type === "select" && (raw === "" || raw === RULE_ENGINE_SELECT_NONE) ) { if (!field.required) continue; } else if (raw === "" || raw === undefined) { if (!field.required) continue; payload[field.name] = raw; } else { payload[field.name] = raw; } } if (fields.some((f) => f.name === "code" && typeof payload.code === "string")) { payload.code = String(payload.code).toUpperCase(); } if (!initialRecord && positionOptions && position !== RULE_ENGINE_POSITION_END) { payload.insertAfterId = position; } onSubmit(payload); }; const renderField = (field: FormFieldDef) => { if (field.type === "boolean") { return ( {field.label} setField(field.name, e.currentTarget.checked)} size="md" color="edr-green" /> ); } const label = ; if (field.type === "select") { return (