"use client";
import { useCallback, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useTrips } from "@/src/hooks/useTrip";
import { TripFormModal } from "@/src/Components/Trip/Tripformmodal";
import { ArchivedTripList } from "@/src/Components/Trip/archive/ArchivedTripList";
import { Alert, Spinner, ArchiveButton, ConfirmDialog , Toast } from "@/src/Components/UI";
import type {
Trip,
TripStatus,
CreateTripPayload,
UpdateTripPayload,
} from "@/src/types/trip";
import { TRIP_STATUS_MAP } from "@/src/types/trip";
import Header from "@/src/Components/UI/Header";
// ── Status badge — same visual pattern as Driver's status badge ─────────────
function StatusBadge({ status }: { status: TripStatus }) {
const s = TRIP_STATUS_MAP[status];
return (
{s.label}
);
}
// ── Archived trips modal — unchanged wrapper, kept as-is ────────────────────
function ArchivedTripsModal({ onClose }: { onClose: () => void }) {
const router = useRouter();
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",
}}
>
أرشيف الرحلات
{/* Was pointing at the normal trip page — send it to the archived route instead */}
router.push(`/dashboard/trips/archived/${trip.id}`)
}
/>
);
}
// ── Page ──────────────────────────────────────────────────────────────────────
export default function TripsPage() {
const router = useRouter();
// All list state + mutations now come from the hook, same as useDrivers
// is used on the Driver page. No direct tripService calls in this file.
const {
trips,
total,
pages,
loading,
error,
page,
setPage,
search,
handleSearch,
status,
handleStatusFilter,
createTrip,
updateTrip,
deleteTrip,
notification,
dismissNotification,
clearError,
} = useTrips();
const [showForm, setShowForm] = useState(false);
const [editTrip, setEditTrip] = useState(null);
const [deleteTarget, setDeleteTarget] = useState(null);
const [deleting, setDeleting] = useState(false);
const [archiveOpen, setArchiveOpen] = useState(false);
// Debounced search input, same pattern as the Driver page's search box
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 handleEdit = useCallback((trip: Trip) => {
setEditTrip(trip);
setShowForm(true);
}, []);
const handleDelete = useCallback((trip: Trip) => {
setDeleteTarget(trip);
}, []);
// ── Create / update — delegates to the hook (hook fires the toast) ───────
const handleSubmit = async (
payload: CreateTripPayload | UpdateTripPayload,
isNew: boolean,
): Promise => {
if (isNew) {
return createTrip(payload as CreateTripPayload);
}
return updateTrip(editTrip!.id, payload as UpdateTripPayload);
};
// ── Delete confirm — delegates to the hook ────────────────────────────────
const handleConfirmDelete = async () => {
if (!deleteTarget) return;
setDeleting(true);
const ok = await deleteTrip(deleteTarget.id);
setDeleting(false);
if (ok) setDeleteTarget(null);
};
return (
{/* ── Header ── */}
{}}
setPage={setPage}
isAudit={false}
onAdd={() => { setEditTrip(null); setShowForm(true); }}/>
{/* ── Error handling: Alert banner for list/API errors ──
Same pattern as the Driver page: a persistent banner tied to the
hook's `error` state, dismissible via `clearError`. */}
{error && }
{/* ── Table ── */}
رقم الرحلة
العنوان
السائق
السيارة
الحالة
البدء
الانتهاء
إجراءات
{loading ? (
جارٍ التحميل…
) : trips.length === 0 ? (
لا توجد رحلات مطابقة {search && `لـ "${search}"`}
) : (
{trips.map((trip, i) => (
- router.push(`/dashboard/trips/${trip.id}`)}
style={{
display: "grid",
gridTemplateColumns:
"1.5fr 2fr 1.3fr 1.3fr 1fr 1.1fr 1.1fr 0.8fr",
alignItems: "center",
gap: "0.5rem",
padding: "0.875rem 1.5rem",
borderBottom: "1px solid var(--color-border)",
background:
i % 2 !== 0 ? "var(--color-surface-muted)" : "transparent",
fontSize: 13,
cursor: "pointer",
transition: "background 0.15s",
}}
onMouseEnter={(e) =>
(e.currentTarget.style.background =
"var(--color-surface-hover, #F8FAFC)")
}
onMouseLeave={(e) =>
(e.currentTarget.style.background =
i % 2 !== 0 ? "var(--color-surface-muted)" : "transparent")
}
>
{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",
})
: "—"}
{/* Inline row actions — stopPropagation so the click does not
also trigger navigation to the detail page. */}
e.stopPropagation()}
>
))}
)}
{/* ── Pagination — same pattern as Driver page ── */}
{pages > 1 && (
صفحة{" "}
{page}
{" "}
من{" "}
{pages}
{" — "}
{total} رحلة
{[
{
label: "السابق",
action: () => setPage((p) => Math.max(1, p - 1)),
disabled: page === 1,
},
{
label: "التالي",
action: () => setPage((p) => Math.min(pages, p + 1)),
disabled: page === pages,
},
].map((btn) => (
))}
)}
{/* ── Form modal ── */}
{showForm && (
setShowForm(false)}
onSubmit={async (payload, isNew) => {
const ok = await handleSubmit(payload, isNew);
if (ok) setShowForm(false);
return ok;
}}
/>
)}
{/* ── Delete modal ── */}
setDeleteTarget(null)}
onConfirm={handleConfirmDelete}
title="حذف الرحلة"
description={`هل أنت متأكد من حذف رحلة ${deleteTarget?.title ?? ""} (${deleteTarget?.tripNumber ?? ""})؟ لا يمكن التراجع عن هذا الإجراء.`}
/>
{/* ── Archive browser modal ── */}
{archiveOpen && (
setArchiveOpen(false)} />
)}
{/* ── Floating archive button — same component Driver page uses ── */}
setArchiveOpen(true)} label="الأرشيف" />
);
}