"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { useTrips } from "@/src/hooks/useTrip"; import { tripService } from "@/src/services/trip.service"; import { getStoredToken } from "@/src/lib/auth"; import { TripFormModal } from "@/src/Components/Trip/Tripformmodal"; import { TripDeleteModal } from "@/src/Components/Trip/Tripdeletemodal"; import { ArchivedTripList } from "@/src/Components/Trip/archive/ArchivedTripList"; import { Spinner, ArchiveButton } from "@/src/Components/UI"; import { Toast, type ToastNotification } from "@/src/Components/UI/Toast"; import type { Trip, TripStatus, CreateTripPayload, UpdateTripPayload } from "@/src/types/trip"; import { TRIP_STATUS_MAP } from "@/src/types/trip"; // ── API error extractor ──────────────────────────────────────────────────── // Same logic as the detail page / useTrip.ts — kept local so create/update // here can notify independently of the list hook's own notification state. function extractApiMessage(err: unknown, fallback: string): string { if (typeof err === "string" && err.trim()) return err.trim(); if (err && typeof err === "object") { const e = err as Record; const responseData = (e["response"] as Record | undefined)?.["data"]; if (responseData && typeof responseData === "object") { const rd = responseData as Record; if (typeof rd["message"] === "string" && rd["message"].trim()) return rd["message"]; if (Array.isArray(rd["message"])) return (rd["message"] as string[]).join(" — "); if (typeof rd["error"] === "string" && rd["error"].trim()) return rd["error"]; } if (typeof e["message"] === "string" && e["message"].trim()) return e["message"]; } return fallback; } // ── Status badge ───────────────────────────────────────────────────────────── function StatusBadge({ status }: { status: TripStatus }) { const s = TRIP_STATUS_MAP[status]; return ( {s.label} ); } // ── Archived trips modal ───────────────────────────────────────────────────── // Lightweight wrapper modal around — mirrors the // ArchivedUsersModal pattern used on the users page. function ArchivedTripsModal({ onClose }: { onClose: () => void }) { const router = useRouter(); // close on Escape useEffect(() => { const h = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); }, [onClose]); return (
{ if (e.target === e.currentTarget) onClose(); }} style={{ position: "fixed", inset: 0, zIndex: 70, background: "rgba(15,23,42,0.6)", backdropFilter: "blur(4px)", display: "flex", alignItems: "flex-start", justifyContent: "center", padding: "2rem 1rem", overflowY: "auto", }} >
e.stopPropagation()} style={{ width: "100%", maxWidth: 920, background: "var(--color-surface-sunken)", borderRadius: "var(--radius-xl)", border: "1px solid var(--color-border)", boxShadow: "0 24px 64px rgba(0,0,0,.2)", overflow: "hidden", }} > {/* header */}

أرشيف الرحلات

{/* body */}
router.push(`/dashboard/trips/${trip.id}`)} />
); } // ── Page ───────────────────────────────────────────────────────────────────── export default function TripsPage() { const router = useRouter(); const { trips, total, pages, loading, error, page, setPage, search, handleSearch, status, handleStatusFilter, reload, } = useTrips(); // ── Notifications (standalone — fired explicitly by every action below) ── const [notification, setNotification] = useState(null); const timerRef = useRef | null>(null); const notify = useCallback((n: ToastNotification) => { if (timerRef.current) clearTimeout(timerRef.current); setNotification(n); timerRef.current = setTimeout(() => setNotification(null), 4000); }, []); const dismissNotification = useCallback(() => { if (timerRef.current) clearTimeout(timerRef.current); setNotification(null); }, []); useEffect(() => () => { if (timerRef.current) clearTimeout(timerRef.current); }, []); // Local input value is debounced before it reaches the hook's `search`, // exactly like the table reloads only after the user stops typing. const [searchInput, setSearchInput] = useState(search); useEffect(() => { const t = setTimeout(() => handleSearch(searchInput), 400); return () => clearTimeout(t); // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchInput]); const [showForm, setShowForm] = useState(false); const [editTrip, setEditTrip] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const [deleting, setDeleting] = useState(false); // Archive browser modal open/closed const [archiveOpen, setArchiveOpen] = useState(false); // ── Create / update — calls tripService directly and notifies explicitly ── const handleSubmit = async ( payload: CreateTripPayload | UpdateTripPayload, isNew: boolean, ): Promise => { try { const token = getStoredToken(); if (isNew) { await tripService.create(payload as CreateTripPayload, token); notify({ type: "success", message: "تم إضافة الرحلة بنجاح." }); } else { await tripService.update(editTrip!.id, payload as UpdateTripPayload, token); notify({ type: "success", message: "تم تحديث بيانات الرحلة بنجاح." }); } reload(); return true; } catch (err) { notify({ type: "error", message: extractApiMessage(err, isNew ? "تعذّر إضافة الرحلة." : "تعذّر تحديث الرحلة."), }); return false; } }; const handleDelete = async () => { if (!deleteTarget) return; setDeleting(true); try { const token = getStoredToken(); await tripService.delete(deleteTarget.id, token); notify({ type: "success", message: "تم حذف الرحلة بنجاح." }); setDeleteTarget(null); reload(); } catch (err) { notify({ type: "error", message: extractApiMessage(err, "تعذّر حذف الرحلة.") }); } finally { setDeleting(false); } }; return (
{/* ── Header ── */}

الرحلات

إدارة جدولة الرحلات وتتبع حالتها

{/* ── Filters ── */}
setSearchInput(e.target.value)} placeholder="بحث برقم الرحلة أو العنوان…" dir="rtl" style={{ flex: 1, minWidth: 200, height: 38, padding: "0 0.75rem", borderRadius: "var(--radius-md)", border: "1px solid var(--color-border)", background: "var(--color-surface-muted)", fontSize: 13, color: "var(--color-text-primary)", fontFamily: "var(--font-sans)", outline: "none", }} />
{/* ── Table card ── */}
{loading ? (
) : error ? (
⚠ {error}
) : trips.length === 0 ? (
لا توجد رحلات مطابقة
) : (
{["رقم الرحلة", "العنوان", "السائق", "السيارة", "الحالة", "البدء", "الانتهاء", "إجراءات"].map(h => ( ))} {trips.map((trip, i) => ( router.push(`/dashboard/trips/${trip.id}`)} style={{ borderBottom: i < trips.length - 1 ? "1px solid var(--color-border)" : "none", cursor: "pointer", transition: "background 0.15s", }} onMouseEnter={e => (e.currentTarget.style.background = "var(--color-surface-muted)")} onMouseLeave={e => (e.currentTarget.style.background = "")} > ))}
{h}
{trip.tripNumber} {trip.title} {trip.driver?.name ?? "—"} {trip.car ? `${trip.car.manufacturer} ${trip.car.model}` : "—"} {trip.startTime ? new Date(trip.startTime).toLocaleString("ar-SA", { dateStyle: "short", timeStyle: "short" }) : "—"} {trip.endTime ? new Date(trip.endTime).toLocaleString("ar-SA", { dateStyle: "short", timeStyle: "short" }) : "—"} e.stopPropagation()}>
)} {/* ── Pagination ── */} {pages > 1 && (
{total} رحلة — صفحة {page} من {pages}
{[...Array(pages)].map((_, idx) => ( ))}
)}
{/* ── Modals ── */} {showForm && ( setShowForm(false)} onSubmit={handleSubmit} /> )} {deleteTarget && ( setDeleteTarget(null)} onConfirm={handleDelete} /> )} {archiveOpen && ( setArchiveOpen(false)} /> )} {/* ── Toast ── */}
); }