@@ -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 ──
|
||||
|
||||
Reference in New Issue
Block a user