"use client"; import { useEffect, useRef, useState } from "react"; import * as yup from "yup"; import { Alert, Spinner } from "../UI"; import { get } from "@/services/api"; import { getStoredToken } from "@/lib/auth"; import { createTripSchema, updateTripSchema } from "@/validations/trip.validator"; import type { TripSchemaErrors } from "@/validations/trip.validator"; import type { Trip, TripStatus, CreateTripPayload, UpdateTripPayload, } from "@/types/trip"; // ── Shared styles ──────────────────────────────────────────────────────────── 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, }; // ── Driver / Car / Branch minimal types ───────────────────────────────────── interface DriverOption { id: string; name: string; phone: string; status: string; } interface CarOption { id: string; manufacturer: string; model: string; plateNumber: string; status?: string; } interface BranchOption { id: string; name: string; } // ── 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([]); useEffect(() => { const token = getStoredToken(); // Fetch active drivers get<{ data: { data: DriverOption[] } }>("driver?limit=100&status=Active", token) .then(res => setDrivers((res as unknown as { data: { data: DriverOption[] } }).data?.data ?? [])) .catch(() => {}); // Fetch active cars get<{ data: { data: CarOption[] } }>("cars?limit=100&status=Active", token) .then(res => setCars((res as unknown as { data: { data: CarOption[] } }).data?.data ?? [])) .catch(() => {}); // Fetch branches get<{ data: { data: BranchOption[] } }>("branches?limit=100", token) .then(res => setBranches((res as unknown as { data: { data: BranchOption[] } }).data?.data ?? [])) .catch(() => {}); }, []); // ── Form state ──────────────────────────────────────────────────────────── const [title, setTitle] = useState(editTrip?.title ?? ""); const [driverId, setDriverId] = useState(editTrip?.driverId ?? ""); const [carId, setCarId] = useState(editTrip?.carId ?? ""); const [branchId, setBranchId] = useState(editTrip?.branchId ?? ""); const [status, setStatus] = useState(editTrip?.status ?? "Scheduled"); const [startTime, setStartTime] = useState(editTrip?.startTime?.slice(0, 16) ?? ""); const [endTime, setEndTime] = useState(editTrip?.endTime?.slice(0, 16) ?? ""); const [collectedCount, setCollectedCount] = useState( editTrip?.collectedCount != null ? String(editTrip.collectedCount) : "", ); const [deliveredCount, setDeliveredCount] = useState( editTrip?.deliveredCount != null ? String(editTrip.deliveredCount) : "", ); const [returnedCount, setReturnedCount] = useState( editTrip?.returnedCount != null ? String(editTrip.returnedCount) : "", ); const [totalCashCollected, setTotalCashCollected] = useState( editTrip?.totalCashCollected != null ? String(editTrip.totalCashCollected) : "", ); const [notes, setNotes] = useState(editTrip?.notes ?? ""); const [endReason, setEndReason] = useState(editTrip?.endReason ?? ""); const [reason, setReason] = useState(""); // update-only 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]); // ── Error style helper ──────────────────────────────────────────────────── const inputStyle = (field: keyof TripSchemaErrors): React.CSSProperties => ({ ...inputBase, ...(errors[field] ? { borderColor: "var(--color-danger)", background: "#FEF2F2" } : {}), }); const clearErr = (field: keyof TripSchemaErrors) => setErrors(p => ({ ...p, [field]: undefined })); // ── Submit ──────────────────────────────────────────────────────────────── const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const raw: Record = { title: title || undefined, driverId: driverId || undefined, carId: carId || undefined, branchId: branchId || undefined, status: status || undefined, startTime: startTime ? `${startTime}:00.000Z` : undefined, endTime: endTime ? `${endTime}:00.000Z` : undefined, collectedCount: collectedCount !== "" ? Number(collectedCount) : undefined, deliveredCount: deliveredCount !== "" ? Number(deliveredCount) : undefined, returnedCount: returnedCount !== "" ? Number(returnedCount) : undefined, totalCashCollected: totalCashCollected !== "" ? Number(totalCashCollected) : undefined, notes: notes || undefined, endReason: endReason || undefined, ...(!isNew && reason ? { reason } : {}), }; try { const schema = isNew ? createTripSchema : updateTripSchema; await schema.validate(raw, { abortEarly: false }); } catch (err) { if (err instanceof yup.ValidationError) { const map: TripSchemaErrors = {}; err.inner.forEach(e => { if (e.path) (map as Record)[e.path] = e.message; }); setErrors(map); } return; } setSaving(true); setApiError(""); try { const ok = await onSubmit(raw as CreateTripPayload | UpdateTripPayload, isNew); if (ok) onClose(); else setApiError("حدث خطأ أثناء الحفظ. يرجى المحاولة مرة أخرى."); } catch (err) { setApiError(err instanceof Error ? err.message : "حدث خطأ غير متوقع."); } finally { setSaving(false); } }; return (
{ if (e.target === e.currentTarget) onClose(); }} style={{ position: "fixed", inset: 0, zIndex: 60, background: "rgba(15,23,42,0.55)", backdropFilter: "blur(4px)", display: "flex", alignItems: "flex-start", justifyContent: "center", padding: "2rem 1rem", overflowY: "auto", }} >
e.stopPropagation()} style={{ width: "100%", maxWidth: 700, background: "var(--color-surface)", borderRadius: "var(--radius-xl)", border: "1px solid var(--color-border)", boxShadow: "0 24px 56px rgba(0,0,0,.2)", overflow: "hidden", }} > {/* Header */}

{isNew ? "إضافة رحلة جديدة" : "تعديل بيانات الرحلة"}

{/* Form */}
{apiError && ( {apiError} )} {/* ── Section: أساسيات الرحلة ── */}

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

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

التوقيت

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

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

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

ملاحظات