update forms

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-05-07 21:02:15 +03:00
co-authored by Copilot
parent dd32658500
commit 38565298fc
32 changed files with 797 additions and 236 deletions
@@ -43,6 +43,12 @@ type VehicleComboboxProps = {
disabled?: boolean
}
function normalizeTextValue(value: unknown): string {
if (typeof value === "string") return value
if (typeof value === "number") return String(value)
return ""
}
function VehicleCombobox({
value,
onChange,
@@ -59,14 +65,14 @@ function VehicleCombobox({
// Local state keeps the input text in sync even when the form value
// is changed externally (e.g., cascade reset or edit-mode population).
const [inputText, setInputText] = useState(value ?? "")
const [inputText, setInputText] = useState(() => normalizeTextValue(value))
useEffect(() => {
setInputText(value ?? "")
setInputText(normalizeTextValue(value))
}, [value])
// Client-side filtering of suggestions based on what's been typed
const normalizedInput = inputText.toLowerCase()
const normalizedInput = normalizeTextValue(inputText).toLowerCase()
const filtered = options.filter((opt) =>
!normalizedInput || opt.toLowerCase().includes(normalizedInput),
)
@@ -80,7 +86,7 @@ function VehicleCombobox({
<div ref={anchorRef}>
<Combobox
value={inputText || null}
value={normalizeTextValue(inputText) || null}
onValueChange={(val) => {
const str = val !== null ? String(val) : ""
setInputText(str)
@@ -89,14 +95,15 @@ function VehicleCombobox({
disabled={disabled}
onInputValueChange={(text, { reason }) => {
if (reason === "input-change") {
setInputText(text)
onChange(text)
const next = normalizeTextValue(text)
setInputText(next)
onChange(next)
}
}}
>
<ComboboxInput
placeholder={placeholder}
showClear={!!inputText}
showClear={normalizeTextValue(inputText).length > 0}
onBlur={onBlur}
aria-invalid={!!error || undefined}
@@ -307,7 +314,6 @@ export function RhfVehicleIdentityField() {
<VehicleCombobox
label="Year"
placeholder="e.g. 2024"
required
value={yearValue}
onChange={handleYearChange}
onBlur={yearCtrl.field.onBlur}
@@ -169,6 +169,7 @@ export function VehicleForm({ resourceId, initialData, onSuccess }: VehicleFormP
{/* Associations */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfAsyncSelectField
required
name="shop_type_id"
label="Shop Type"
placeholder="Select shop type"
@@ -180,6 +181,7 @@ export function VehicleForm({ resourceId, initialData, onSuccess }: VehicleFormP
{...STORE_OBJECT}
/>
<RhfAsyncSelectField
required
name="vehicle_body_type_id"
label="Body Type"
placeholder="Select body type"
@@ -249,6 +251,7 @@ export function VehicleForm({ resourceId, initialData, onSuccess }: VehicleFormP
{/* License & identifiers */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfTextField
required
name="license_plate"
label="License Plate"
placeholder="e.g. ABC-123"
@@ -6,20 +6,20 @@ export const relationFieldSchema = z
export const vehicleFormSchema = z.object({
// ── Relations ──
shop_type_id: relationFieldSchema,
vehicle_body_type_id: relationFieldSchema,
shop_type_id: relationFieldSchema.refine((val) => !!val?.value, "Shop type is required"),
vehicle_body_type_id: relationFieldSchema.refine((val) => !!val?.value, "Vehicle body type is required"),
vehicle_fuel_type_id: relationFieldSchema,
vehicle_transmission_id: relationFieldSchema,
vehicle_color_id: relationFieldSchema,
customer_id: relationFieldSchema,
// ── Vehicle identity ──
make: z.string().optional(),
model: z.string().optional(),
make: z.string().min(1, "Make is required").max(50, "Make must be at most 50 characters"),
model: z.string().min(1, "Model is required").max(50, "Model must be at most 50 characters"),
year: z.string().optional(),
sub_model: z.string().optional(),
// ── License & identifiers ──
license_plate: z.string().optional(),
license_plate: z.string().min(1, "License plate is required").max(50, "License plate must be at most 50 characters"),
vin_number: z.string().optional(),
// ── Technical specs ──