"use client"; // src/Components/Trip/TripFormModal.tsx // CHANGE: renamed from Tripformmodal.tsx (proper PascalCase, matches // UserFormModal.tsx / DriverFormModal.tsx). Split out of the old single-file // Trip module into TripFormModal.tsx / TripTable.tsx / TripDetailModal.tsx. // FIX 1: notes/endReason schema — empty string now transforms to undefined // before .min() runs, so leaving them blank on create no longer fails // validation despite .optional() (see trip.validator.ts). // FIX 2: edit-mode defaultValues now fall back to the nested driver/car/branch // ids, and setValue() re-applies the saved id once each dropdown's options // actually load — previously the DOM element itself keeps showing // the raw "YYYY-MM-DDTHH:mm" value the user typed — the transform only // affects what's handed to the yup resolver and to onSubmit. interface TripFormValues { title: string; driverId: string; carId: string; branchId: string; status: TripStatus; startTime: string; endTime: string; collectedCount: number | undefined; deliveredCount: number | undefined; returnedCount: number | undefined; totalCashCollected: number | undefined; notes: string; endReason: string; reason: string; // update-only } // ── Props ──────────────────────────────────────────────────────────────────── interface TripFormModalProps { editTrip: Trip | null; onClose: () => void; onSubmit: ( payload: CreateTripPayload | UpdateTripPayload, isNew: boolean, ) => Promise; } // ── Component ──────────────────────────────────────────────────────────────── export function TripFormModal({ editTrip, onClose, onSubmit, }: TripFormModalProps) { const isNew = editTrip === null; // ── Dropdown options ────────────────────────────────────────────────────── const [drivers, setDrivers] = useState([]); const [cars, setCars] = useState([]); const [branches, setBranches] = useState([]); // ── react-hook-form ──────────────────────────────────────────────────────── // Moved above the dropdown-loading effect below: that effect calls // setValue(), so useForm() (which defines it) must run first — otherwise // setValue is referenced before its own initialization (TDZ ReferenceError). const { register, handleSubmit, setError, setFocus, setValue, formState: { errors, isSubmitting }, } = useForm({ // Cast both the schema argument and the resolver's result: // - argument: createTripSchema/updateTripSchema have structurally // different shapes (create requires title/driverId/etc., update makes // everything optional), so the ternary's type is a union yupResolver's // single-schema signature won't accept. // - result: yup's inferred type for .optional() fields // (`collectedCount?: number`) is structurally incompatible with // TripFormValues's `collectedCount: number | undefined` — letting // useForm alone establish the field-values type avoids // that mismatch. resolver: yupResolver((isNew ? createTripSchema : updateTripSchema) as any) as any, defaultValues: { title: editTrip?.title ?? "", driverId: editTrip?.driverId ?? editTrip?.driver?.id ?? "", carId: editTrip?.carId ?? editTrip?.car?.id ?? "", branchId: editTrip?.branchId ?? editTrip?.branch?.id ?? "", status: editTrip?.status ?? "Scheduled", startTime: editTrip?.startTime?.slice(0, 16) ?? "", endTime: editTrip?.endTime?.slice(0, 16) ?? "", collectedCount: editTrip?.collectedCount ?? undefined, deliveredCount: editTrip?.deliveredCount ?? undefined, returnedCount: editTrip?.returnedCount ?? undefined, totalCashCollected: editTrip?.totalCashCollected != null ? Number(editTrip.totalCashCollected) : undefined, notes: editTrip?.notes ?? "", endReason: editTrip?.endReason ?? "", reason: "", }, }); useEffect(() => { const token = getStoredToken(); driverService.getActiveOptions(token).then((list) => { setDrivers(list); // defaultValues were applied before this list existed, so the , so setFocus works with no separate // ref needed here. useEffect(() => { setFocus("title"); }, [setFocus]); const numberField = ( field: "collectedCount" | "deliveredCount" | "returnedCount" | "totalCashCollected", ) => register(field, { setValueAs: (v) => (v === "" || v === null ? undefined : Number(v)), }); // datetime-local -> ISO-8601, applied at the RHF-value level only (DOM input // keeps showing the raw datetime-local string the user picked). const dateTimeField = (field: "startTime" | "endTime") => register(field, { setValueAs: (v) => (v ? `${v}:00.000Z` : ""), }); const titleError = errors.title && errors.title.type !== "manual" ? errors.title.message : undefined; // ── Submit ──────────────────────────────────────────────────────────────── const submitHandler = async (data: TripFormValues) => { const raw: Record = { title: data.title || undefined, driverId: data.driverId || undefined, carId: data.carId || undefined, branchId: data.branchId || undefined, status: data.status || undefined, startTime: data.startTime || undefined, endTime: data.endTime || undefined, collectedCount: data.collectedCount, deliveredCount: data.deliveredCount, returnedCount: data.returnedCount, totalCashCollected: data.totalCashCollected, notes: data.notes || undefined, endReason: data.endReason || undefined, ...(!isNew && data.reason ? { reason: data.reason } : {}), }; setApiError(""); try { const ok = await onSubmit(raw as CreateTripPayload | UpdateTripPayload, isNew); if (ok) { onClose(); } else { setError("title", { message: "حدث خطأ غير متوقع. يرجى المحاولة لاحقاً." }); setApiError("حدث خطأ غير متوقع. يرجى المحاولة لاحقاً."); } } catch (err) { const message = err instanceof Error ? err.message : "حدث خطأ غير متوقع. يرجى المحاولة لاحقاً."; setError("title", { message }); setApiError(message); } }; return ( } >
{errors.title?.type === "manual" && ( setApiError("")} /> )} {/* ── Section: trip management ── */}

أساسيات الرحلة

{/* Title */}
{/* Driver */} {/* Car */}
{/* Branch */} {/* Status */}
{/* ── Section: التوقيت ── */}

التوقيت

{/* ── Section: الأعداد والمبالغ ── */}

الأعداد والمبالغ

{/* ── Section: ملاحظات ── */}

ملاحظات