"use client"; import { useEffect, useRef, useState } from "react"; import * as yup from "yup"; import { Alert, Spinner } from "../UI"; import { get } from "@/src/services/api"; import { getStoredToken } from "@/src/lib/auth"; import { createDriverSchema, updateDriverSchema } from "@/src/validations/driver.validator"; import type { DriverSchemaErrors } from "@/src/validations/driver.validator"; import type { Driver, CreateDriverPayload, UpdateDriverPayload, NationalIdType, DriverCardType, DriverStatus, } from "@/src/types/driver"; import type { Branch } from "@/src/types/branch"; // ── Shared input style ─────────────────────────────────────────────────────── const inputBase: React.CSSProperties = { width: "100%", height: 40, padding: "0 0.75rem", borderRadius: "var(--radius-md)", border: "1px solid var(--color-border)", background: "var(--color-surface)", fontSize: 13, color: "var(--color-text-primary)", outline: "none", fontFamily: "var(--font-sans)", }; const labelStyle: React.CSSProperties = { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, fontWeight: 600, color: "var(--color-text-secondary)", }; const errorTextStyle: React.CSSProperties = { fontSize: 11, color: "var(--color-danger)", fontWeight: 500, }; const sectionHeadingStyle: React.CSSProperties = { fontSize: 11, letterSpacing: "0.25em", textTransform: "uppercase", color: "var(--color-text-hint)", fontWeight: 700, margin: "0.5rem 0 0", }; const optionalLabelStyle: React.CSSProperties = { fontSize: 10, fontWeight: 500, color: "var(--color-text-hint)", marginRight: 4, }; // ── Date helper ────────────────────────────────────────────────────────────── function toIsoDateTime(val: string): string { if (!val) return val; if (val.includes("T")) return val; return `${val}T00:00:00.000Z`; } // ── Props ──────────────────────────────────────────────────────────────────── interface DriverFormModalProps { editDriver: Driver | null; branches: Branch[]; onClose: () => void; onSubmit: ( payload: CreateDriverPayload | UpdateDriverPayload, isNew: boolean, ) => Promise; } // ── Component ──────────────────────────────────────────────────────────────── export function DriverFormModal({ editDriver, branches: branchesProp, onClose, onSubmit, }: DriverFormModalProps) { const isNew = editDriver === null; // ── Local branches (auto-fetched if prop is empty) ───────────────────────── const [branches, setBranches] = useState(branchesProp); useEffect(() => { if (branchesProp.length > 0) { setBranches(branchesProp); return; } const token = getStoredToken(); get<{ data: { data: Branch[] } }>("branches?limit=100", token) .then((res) => { const list = (res as unknown as { data: { data: Branch[] } }).data?.data ?? []; setBranches(list); }) .catch(() => { /* silently ignore */ }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // ── Form state ──────────────────────────────────────────────────────────── const [name, setName] = useState(editDriver?.name ?? ""); const [phone, setPhone] = useState(editDriver?.phone ?? ""); const [email, setEmail] = useState(editDriver?.email ?? ""); const [address, setAddress] = useState(editDriver?.address ?? ""); const [nationality, setNationality] = useState(editDriver?.nationality ?? ""); const [nationalIdType, setNationalIdType] = useState( editDriver?.nationalIdType ?? "", ); const [nationalId, setNationalId] = useState( (editDriver as Driver & { nationalId?: string })?.nationalId ?? "", ); const [nationalIdExpiry, setNationalIdExpiry] = useState( (editDriver as Driver & { nationalIdExpiry?: string })?.nationalIdExpiry?.slice(0, 10) ?? "", ); const [gosiNumber, setGosiNumber] = useState(editDriver?.gosiNumber ?? ""); const [licenseNumber, setLicenseNumber] = useState(editDriver?.licenseNumber ?? ""); const [licenseType, setLicenseType] = useState(editDriver?.licenseType ?? ""); const [licenseExpiry, setLicenseExpiry] = useState( editDriver?.licenseExpiry?.slice(0, 10) ?? "", ); const [driverCardNumber, setDriverCardNumber] = useState(editDriver?.driverCardNumber ?? ""); const [driverCardType, setDriverCardType] = useState( editDriver?.driverCardType ?? "", ); const [driverCardExpiry, setDriverCardExpiry] = useState( editDriver?.driverCardExpiry?.slice(0, 10) ?? "", ); const [driverType, setDriverType] = useState(editDriver?.driverType ?? ""); const [branchId, setBranchId] = useState( (editDriver as Driver & { branchId?: string })?.branchId ?? "", ); const [status, setStatus] = useState(editDriver?.status ?? "Active"); // Photo state (new uploads only) const [photo, setPhoto] = useState(null); const [nationalPhoto, setNationalPhoto] = useState(null); const [driverCardPhoto, setDriverCardPhoto] = useState(null); const [errors, setErrors] = useState({}); const [saving, setSaving] = useState(false); const [apiError, setApiError] = useState(""); const firstRef = useRef(null); useEffect(() => { firstRef.current?.focus(); }, []); useEffect(() => { const h = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); }, [onClose]); // ── Input error style ────────────────────────────────────────────────────── const inputStyle = (field: keyof DriverSchemaErrors): React.CSSProperties => ({ ...inputBase, ...(errors[field] ? { borderColor: "var(--color-danger)", background: "#FEF2F2" } : {}), }); const clearFieldError = (field: keyof DriverSchemaErrors) => setErrors((p) => ({ ...p, [field]: undefined })); // ── Submit with yup validation ──────────────────────────────────────────── const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // Build the raw object to validate const raw: Record = { name: name || undefined, phone: phone || undefined, email: email || undefined, address: address || undefined, nationality: nationality || undefined, nationalIdType: nationalIdType || undefined, gosiNumber: gosiNumber || undefined, licenseNumber: licenseNumber || undefined, licenseType: licenseType || undefined, licenseExpiry: licenseExpiry || undefined, driverCardNumber: driverCardNumber || undefined, driverCardType: driverCardType || undefined, driverCardExpiry: driverCardExpiry || undefined, driverType: driverType || undefined, branchId: branchId || undefined, ...(!isNew ? { status } : {}), }; try { const schema = isNew ? createDriverSchema : updateDriverSchema; await schema.validate(raw, { abortEarly: false }); } catch (err) { if (err instanceof yup.ValidationError) { const fieldErrors: DriverSchemaErrors = {}; err.inner.forEach((e) => { if (e.path) { fieldErrors[e.path as keyof DriverSchemaErrors] = e.message; } }); setErrors(fieldErrors); return; } } // ── Build clean payload to send to backend ──────────────────────────── const payload: Record = { name, phone }; if (email) payload.email = email; if (address) payload.address = address; if (nationality) payload.nationality = nationality; if (nationalIdType) payload.nationalIdType = nationalIdType; if (nationalId) payload.nationalId = nationalId; if (nationalIdExpiry) payload.nationalIdExpiry = toIsoDateTime(nationalIdExpiry); if (gosiNumber) payload.gosiNumber = gosiNumber; if (licenseNumber) payload.licenseNumber = licenseNumber; if (licenseType) payload.licenseType = licenseType; if (licenseExpiry) payload.licenseExpiry = toIsoDateTime(licenseExpiry); if (driverCardNumber) payload.driverCardNumber = driverCardNumber; if (driverCardType) payload.driverCardType = driverCardType; if (driverCardExpiry) payload.driverCardExpiry = toIsoDateTime(driverCardExpiry); if (driverType) payload.driverType = driverType; if (branchId) payload.branchId = branchId; if (!isNew) payload.status = status; if (photo) payload.photo = photo; if (nationalPhoto) payload.nationalPhoto = nationalPhoto; if (driverCardPhoto) payload.driverCardPhoto = driverCardPhoto; setSaving(true); setApiError(""); try { const ok = await onSubmit(payload as unknown as CreateDriverPayload, isNew); if (ok) { onClose(); } else { // onSubmit returned false without throwing — show generic fallback setApiError("حدث خطأ غير متوقع. يرجى المحاولة لاحقاً."); } } catch (err) { // onSubmit threw — surface the real API error message const message = err instanceof Error ? err.message : "حدث خطأ غير متوقع. يرجى المحاولة لاحقاً."; setApiError(message); } finally { setSaving(false); } }; // ── File input helper ────────────────────────────────────────────────────── function FileInput({ label: lbl, current, onChange, }: { label: string; current: File | null; onChange: (f: File | null) => void; }) { return ( ); } // ── Render ───────────────────────────────────────────────────────────────── return (
{ if (e.target === e.currentTarget) onClose(); }} style={{ position: "fixed", inset: 0, zIndex: 50, background: "rgba(15,23,42,0.55)", backdropFilter: "blur(4px)", display: "flex", alignItems: "center", justifyContent: "center", padding: "1rem", overflowY: "auto", }} >
e.stopPropagation()} style={{ width: "100%", maxWidth: 640, background: "var(--color-surface)", borderRadius: "var(--radius-2xl)", border: "1px solid var(--color-border)", boxShadow: "0 24px 64px rgba(0,0,0,.18)", overflow: "hidden", margin: "auto", }} > {/* Header */}

{isNew ? "إضافة سائق" : "تعديل سائق"}

{isNew ? "سائق جديد" : editDriver?.name}

{/* Form */}
{apiError && ( setApiError("")} /> )} {/* ── Section: Personal Info ── */}

البيانات الشخصية

{/* Name — required */} {/* Phone — required */} {/* Email — optional */} {/* Nationality — optional */} {/* Address — optional, full width */}
{/* ── Section: ID & GOSI ── */}

الهوية والتأمينات

{/* National ID Type — optional */} {/* National ID number */} {/* National ID expiry */} {/* GOSI — optional */}
{/* ── Section: License ── */}

رخصة القيادة

{/* License number — optional */} {/* License type — optional */} {/* License expiry — optional, validated if provided */}
{/* ── Section: Driver Card ── */}

بطاقة السائق

{/* Card number — optional */} {/* Card type — optional */} {/* Card expiry — optional, validated if provided */}
{/* ── Section: Operational ── */}

بيانات تشغيلية

{/* Branch — optional */} {/* Driver type — optional */} {/* Status — edit only */} {!isNew && ( )}
{/* ── Section: Photos ── */}

الصور والمستندات

جميع حقول الصور اختيارية

{/* ── Actions ── */}
); }