fix(rule-engine): use a real date picker, and stop the label wrapping

Date fields rendered as a native <input type="date">, which ignores the
form's styling and varies by browser. They now use Mantine's DateInput,
kept on the `YYYY-MM-DD` string the API's date columns take — no Date
round-trip, so none of the UTC-parsing shift that new Date("2026-01-01")
introduces east of Greenwich.

Labels were passed as an element, which put a flex box inside the
<label>: a line of dead space above every input, and Mantine's own
required asterisk pushed onto its own line. Passing the plain string and
withAsterisk lets Mantine render both.
This commit is contained in:
Nathnael
2026-08-20 11:37:02 +00:00
parent f0d66ce8f9
commit d6f1c5fb43

View File

@@ -1,4 +1,5 @@
import { useEffect, useMemo, useState } from "react";
import { DateInput } from "@mantine/dates";
import { Loader2, Plus, Trash2 } from "lucide-react";
import {
ActionIcon,
@@ -162,16 +163,6 @@ const inputStyles = {
label: { fontWeight: 600, marginBottom: 6, color: "var(--mantine-color-gray-8)" },
} 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,
@@ -405,7 +396,11 @@ const RuleEngineFormDialog = ({
);
}
const label = <FieldLabel label={field.label} required={field.required} />;
// A plain string, so Mantine renders the label and its required asterisk
// itself. Passing an element here put a flex box inside the <label>,
// which added a line of dead space above every input and bumped
// Mantine's own asterisk onto a line of its own.
const label = field.label;
if (field.type === "tierList") {
const rows = Array.isArray(values[field.name])
@@ -513,6 +508,7 @@ const RuleEngineFormDialog = ({
<MultiSelect
key={field.name}
label={label}
withAsterisk={field.required}
description={field.description}
placeholder={
selectOptionsLoading
@@ -602,6 +598,31 @@ const RuleEngineFormDialog = ({
);
}
if (field.type === "date") {
const raw = String(values[field.name] ?? "");
return (
<DateInput
key={field.name}
label={label}
description={field.description}
placeholder="Select date"
// Mantine's DateValue accepts a `YYYY-MM-DD` string, which is exactly
// what the API's date columns take — so the value passes straight
// through with no Date round-trip, and none of the UTC-parsing shift
// that `new Date("2026-01-01")` would introduce east of Greenwich.
value={raw || null}
onChange={(v) => setField(field.name, v ?? "")}
disabled={field.disabled || (field.disabledOnEdit && !!initialRecord)}
required={field.required}
error={fieldErrors[field.name] || undefined}
clearable
size="md"
radius="md"
styles={inputStyles}
/>
);
}
const isNumber = field.type === "number";
const computed = field.computeValue ? field.computeValue(values) : undefined;
@@ -610,7 +631,7 @@ const RuleEngineFormDialog = ({
key={field.name}
label={label}
description={field.description}
type={isNumber ? "number" : field.type === "date" ? "date" : "text"}
type={isNumber ? "number" : "text"}
// Every rule-engine number (sizes, capacities, counts, points, rates,
// display order) is a non-negative magnitude — reject negatives outright
// rather than letting a typed "-" reach the API.