"use client"; import { useEffect, useRef, useState } from "react"; import { Alert, Spinner } from "../UI"; import { get } from "../../services/api"; import { getStoredToken } from "../../lib/auth"; import type { Driver, DriverFormErrors, CreateDriverPayload, UpdateDriverPayload, NationalIdType, DriverCardType, DriverStatus, } from "../../types/driver"; import type { Branch } from "../../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", }; // ── Date helper ────────────────────────────────────────────────────────────── function toIsoDateTime(val: string): string { if (!val) return val; if (val.includes("T")) return val; return `${val}T00:00:00.000Z`; } // ── Validation ─────────────────────────────────────────────────────────────── function validate(form: Partial): DriverFormErrors { const e: DriverFormErrors = {}; if (!form.name?.trim()) e.name = "الاسم مطلوب"; if (!form.phone?.trim()) e.phone = "رقم الجوال مطلوب"; if (form.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) e.email = "البريد الإلكتروني غير صالح"; return e; } // ── 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( // nationalId is not in CreateDriverPayload — read-only from Driver (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 DriverFormErrors): React.CSSProperties => ({ ...inputBase, ...(errors[field] ? { borderColor: "var(--color-danger)", background: "#FEF2F2" } : {}), }); const clearFieldError = (field: keyof DriverFormErrors) => setErrors((p) => ({ ...p, [field]: undefined })); // ── Submit ───────────────────────────────────────────────────────────────── const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const errs = validate({ name, phone, email: email || undefined }); if (Object.keys(errs).length) { setErrors(errs); return; } const raw: Record = { name, phone }; if (email) raw.email = email; if (address) raw.address = address; if (nationality) raw.nationality = nationality; if (nationalIdType) raw.nationalIdType = nationalIdType; if (gosiNumber) raw.gosiNumber = gosiNumber; if (licenseNumber) raw.licenseNumber = licenseNumber; if (licenseType) raw.licenseType = licenseType; if (licenseExpiry) raw.licenseExpiry = toIsoDateTime(licenseExpiry); if (driverCardNumber) raw.driverCardNumber = driverCardNumber; if (driverCardType) raw.driverCardType = driverCardType; if (driverCardExpiry) raw.driverCardExpiry = toIsoDateTime(driverCardExpiry); if (driverType) raw.driverType = driverType; if (branchId) raw.branchId = branchId; if (!isNew) raw.status = status; if (photo) raw.photo = photo; if (nationalPhoto) raw.nationalPhoto = nationalPhoto; if (driverCardPhoto) raw.driverCardPhoto = driverCardPhoto; const payload = raw as unknown as CreateDriverPayload; setSaving(true); setApiError(""); const ok = await onSubmit(payload, isNew); setSaving(false); if (ok) onClose(); else setApiError("حدث خطأ غير متوقع. يرجى المحاولة لاحقاً."); }; // ── 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 ── */}

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

{/* ── Section: ID & GOSI ── */}

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

{/* ── Section: License ── */}

رخصة القيادة

{/* ── Section: Driver Card ── */}

بطاقة السائق

{/* ── Section: Operational ── */}

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

{!isNew && ( )}
{/* ── Section: Photos ── */}

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

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