"use client";
// app/dashboard/trips/page.tsx
// CHANGE: replaced the old hand-rolled inline table with .
// CHANGE: TripFormModal now imported from the barrel (@/src/Components/Trip)
// instead of a direct path — avoids the Tripformmodal.tsx case-sensitivity
// mismatch that breaks on case-sensitive filesystems (Linux/Docker builds).
// CHANGE: removed the local ArchivedTripsModal wrapper (it duplicated what
// the new ArchivedTripModal already does — search bar + table + detail) and
// removed the ArchivedTripList import; now renders
// directly.
import { useCallback, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useTrips } from "@/src/hooks/useTrip";
import { TripFormModal, TripTable } from "@/src/Components/Trip";
import { ArchivedTripModal } from "@/src/Components/Trip/archive/ArchivedTripModal";
import { Alert, ArchiveButton, ConfirmDialog, Toast } from "@/src/Components/UI";
import type { Trip, CreateTripPayload, UpdateTripPayload } from "@/src/types/trip";
import Header from "@/src/Components/UI/Header";
// ── Page ──────────────────────────────────────────────────────────────────────
export default function TripsPage() {
const router = useRouter();
// All list state + mutations 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,
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);
}, []);
// TripTable exposes a dedicated "view" action (icon button) instead of a
// row click — wired to the trip detail page, same destination the old
// row-click handler used to navigate to.
const handleView = useCallback(
(trip: Trip) => router.push(`/dashboard/trips/${trip.id}`),
[router],
);
const handleAdd = useCallback(() => {
setEditTrip(null);
setShowForm(true);
}, []);
// ── 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={handleAdd}
/>
{/* ── 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 ── */}
{/* ── 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 — now the split ArchivedTripModal directly,
no local wrapper needed since it already ships its own search bar,
table, and detail-view wiring. ── */}
{archiveOpen && setArchiveOpen(false)} />}
{/* ── Floating archive button — same component Driver page uses ── */}
setArchiveOpen(true)} label="الأرشيف" />
);
}