generate contract by the system, add waggon type,fix ui

This commit is contained in:
marshal
2026-06-07 01:20:52 +03:00
88 changed files with 2845 additions and 1237 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { Loader2 } from "lucide-react";
import {
Modal,
@@ -9,9 +9,10 @@ import {
Switch,
Stack,
Group,
Divider,
Text,
Box,
SimpleGrid,
Divider,
} from "@mantine/core";
import {
@@ -32,6 +33,43 @@ export interface RuleEngineFormDialogProps {
onSubmit: (values: Record<string, unknown>) => 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,
@@ -42,6 +80,8 @@ const buildInitialValues = (
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;
}
@@ -74,11 +114,34 @@ const resolveSelectValue = (
return String(raw);
};
const inputStyles = {
label: { fontWeight: 600, marginBottom: 6, color: "var(--mantine-color-gray-8)" },
input: {
borderColor: "#e2e8f0",
background: "white",
transition: "border-color 0.15s ease, box-shadow 0.15s ease",
"&:focus": {
borderColor: "var(--freight-brand)",
boxShadow: "0 0 0 3px var(--freight-brand-ring)",
},
},
} as const;
const FieldLabel = ({ label, required }: { label: string; required?: boolean }) => (
<Group gap={4} wrap="nowrap">
<span>{label}</span>
{required ? (
<Text component="span" c="red" size="sm">
*
</Text>
) : null}
</Group>
);
const RuleEngineFormDialog = ({
open,
onOpenChange,
title,
description,
fields,
initialRecord,
isSubmitting,
@@ -95,6 +158,8 @@ const RuleEngineFormDialog = ({
}
}, [open, fields, initialRecord]);
const formRows = useMemo(() => buildFormRows(fields), [fields]);
const setField = (name: string, value: unknown) => {
setValues((current) => ({ ...current, [name]: value }));
};
@@ -130,157 +195,147 @@ const RuleEngineFormDialog = ({
onSubmit(payload);
};
const renderField = (field: FormFieldDef) => {
if (field.type === "boolean") {
return (
<Group
key={field.name}
justify="space-between"
align="center"
wrap="nowrap"
gap="md"
px="md"
style={{
minHeight: 42,
background: "#f8fafc",
border: "1px solid #e2e8f0",
borderRadius: "var(--mantine-radius-md)",
}}
>
<Text size="sm" fw={600}>
{field.label}
</Text>
<Switch
checked={Boolean(values[field.name])}
onChange={(e) => setField(field.name, e.currentTarget.checked)}
size="md"
color="green"
/>
</Group>
);
}
const label = <FieldLabel label={field.label} required={field.required} />;
if (field.type === "select") {
return (
<Select
key={field.name}
label={label}
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="md"
styles={inputStyles}
/>
);
}
if (field.type === "textarea") {
return (
<Textarea
key={field.name}
label={label}
value={String(values[field.name] ?? "")}
onChange={(e) => setField(field.name, e.currentTarget.value)}
placeholder={field.placeholder}
required={field.required}
minRows={4}
autosize
maxRows={8}
size="md"
radius="md"
styles={inputStyles}
/>
);
}
return (
<TextInput
key={field.name}
label={label}
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="md"
styles={inputStyles}
/>
);
};
return (
<Modal
opened={open}
onClose={() => onOpenChange(false)}
title={title}
title={
<Text size="lg" fw={700} lh={1.2}>
{title}
</Text>
}
centered
size="md"
size={720}
radius="lg"
padding="xl"
overlayProps={{ backgroundOpacity: 0.45, blur: 3 }}
styles={{
header: {
paddingBottom: "20px",
borderBottom: "1px solid var(--mantine-color-gray-2)",
content: {
maxWidth: "min(720px, 95vw)",
},
body: {
paddingTop: "24px",
paddingBottom: "24px",
},
title: {
fontSize: "1.25rem",
fontWeight: 700,
color: "var(--mantine-color-gray-9)",
paddingTop: 20,
},
}}
>
<form onSubmit={handleSubmit}>
<Stack gap="lg">
<Text size="sm" c="dimmed">
{description}
</Text>
<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)",
},
}}
/>
) : 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>
))}
<Box style={{ maxHeight: "calc(65vh - 120px)", overflowY: "auto", paddingRight: 4 }}>
<Stack gap="md">
{formRows.map((row) =>
row.kind === "pair" ? (
<SimpleGrid key={`${row.fields[0].name}-${row.fields[1].name}`} cols={2} spacing="md">
<Box style={{ minWidth: 0 }}>{renderField(row.fields[0])}</Box>
<Box style={{ minWidth: 0 }}>{renderField(row.fields[1])}</Box>
</SimpleGrid>
) : (
<Box key={row.field.name}>{renderField(row.field)}</Box>
),
)}
</Stack>
</div>
</Box>
<Divider />
<Group justify="flex-end" gap="sm">
<Button
variant="light"
variant="default"
onClick={() => onOpenChange(false)}
disabled={isSubmitting}
radius="lg"
radius="md"
size="md"
>
Cancel
@@ -288,9 +343,15 @@ const RuleEngineFormDialog = ({
<Button
type="submit"
disabled={isSubmitting}
leftSection={isSubmitting && <Loader2 size={18} style={{ animation: "spin 1s linear infinite" }} />}
radius="lg"
color="blue"
leftSection={
isSubmitting ? (
<Loader2 size={18} style={{ animation: "spin 1s linear infinite" }} />
) : undefined
}
radius="md"
color="green"
variant="filled"
fw={600}
size="md"
>
{isSubmitting ? "Saving..." : "Save"}