add archive layout pages to user car driver trip order

This commit is contained in:
m7amedez5511
2026-06-30 17:35:18 +03:00
parent f5882f3791
commit c33778012a
33 changed files with 2669 additions and 30 deletions

View File

@@ -1,11 +1,12 @@
"use client";
// app/(dashboard)/cars/page.tsx
import { useCallback, useState } from "react";
import { Spinner, Alert } from "@/src/Components/UI";
import { CarFormModal } from "@/src/Components/car/CarFormModal";
import { CarDetailPanel } from "@/src/Components/car/CarDetailPanel";
import { CarDeleteModal } from "@/src/Components/car/CarDeleteModal";
import { Spinner, Alert, ArchiveButton } from "@/src/Components/UI";
import { CarFormModal } from "@/src/Components/car/CarFormModal";
import { CarDetailPanel } from "@/src/Components/car/CarDetailPanel";
import { CarDeleteModal } from "@/src/Components/car/CarDeleteModal";
import { ArchivedCarsModal } from "@/src/Components/car/archive/Archivedcarsmodal";
import { useCars, useCarMutations, useToast } from "@/src/hooks/useCars";
import { fmtDateShort, isExpiringSoon, STATUS_MAP, INS_MAP } from "@/src/types/car";
import type { Car, CreateCarPayload, ToastMsg, UpdateCarPayload } from "@/src/types/car";
@@ -169,6 +170,7 @@ export default function CarsPage() {
const [detailId, setDetailId] = useState<string | null>(null);
const [formTarget, setFormTarget] = useState<Car | null | false>(false); // false = closed
const [deleteTarget, setDeleteTarget] = useState<Car | null>(null);
const [archiveOpen, setArchiveOpen] = useState(false);
const { toast, notify } = useToast();
@@ -226,6 +228,10 @@ export default function CarsPage() {
/>
)}
{archiveOpen && (
<ArchivedCarsModal onClose={() => setArchiveOpen(false)} />
)}
<section style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }} dir="rtl">
{/* ── Header ── */}
@@ -400,6 +406,9 @@ export default function CarsPage() {
</div>
)}
</section>
{/* Floating button to open the archive browser */}
<ArchiveButton onClick={() => setArchiveOpen(true)} />
</>
);
}

View File

@@ -1,9 +1,10 @@
"use client";
import { useState, useCallback } from "react";
import { Alert, Spinner } from "@/src/Components/UI";
import { Alert, Spinner, ArchiveButton } from "@/src/Components/UI";
import { DriverFormModal } from "@/src/Components/Driver/DriverFormModal";
import { DriverDeleteModal } from "@/src/Components/Driver/DriverDeleteModal";
import { ArchivedDrivers } from "@/src/Components/Driver/archive/ArchivedDrivers";
import { useDrivers } from "@/src/hooks/useDriver";
import { CreateDriverPayload, Driver, DRIVER_STATUS_MAP, UpdateDriverPayload } from "@/src/types/driver";
import { DriverDetailPanel } from "@/src/Components/Driver/DriverDetailPanel";
@@ -74,6 +75,8 @@ export default function DriversPage() {
const [deleting, setDeleting] = useState(false);
// Bumped after a successful edit to force the detail panel to re-fetch
const [panelRefreshKey, setPanelRefreshKey] = useState(0);
// Archive browser modal open/closed
const [archiveOpen, setArchiveOpen] = useState(false);
// ── Handlers ──────────────────────────────────────────────────────────────
const handleEdit = useCallback((driver: Driver) => {
@@ -418,6 +421,14 @@ export default function DriversPage() {
onConfirm={handleConfirmDelete}
/>
)}
{/* ── Archive browser modal ── */}
{archiveOpen && (
<ArchivedDrivers onClose={() => setArchiveOpen(false)} />
)}
{/* ── Floating button to open the archive browser ── */}
<ArchiveButton onClick={() => setArchiveOpen(true)} />
</>
);
}

View File

@@ -3,10 +3,11 @@
import { useState, useCallback } from "react";
import { Alert, Spinner, Toast } from "@/src/Components/UI";
import { OrderFormModal } from "@/src/Components/Order/Orderformmodal";
import { OrderDeleteModal } from "@/src/Components/Order/Orderdeletemodal";
import { Alert, Spinner, Toast, ArchiveButton } from "@/src/Components/UI";
import { OrderFormModal } from "@/src/Components/Order/OrderFormModal";
import { OrderDeleteModal } from "@/src/Components/Order/OrderDeleteModal";
import { OrderDetailPanel, ORDER_STATUS_MAP } from "@/src/Components/Order/OrderDetailBanel";
import { ArchivedOrdersModal } from "@/src/Components/Order/archive/ArchivedOrdersModal";
import { useOrders } from "@/src/hooks/useOrder";
import type { CreateOrderPayload, Order, UpdateOrderPayload } from "@/src/types/order";
@@ -81,6 +82,8 @@ export default function OrderComponent() {
// Bumped after a successful edit/status-change to force the detail panel
// to re-fetch — same trick as DriversPage's panelRefreshKey.
const [panelRefreshKey, setPanelRefreshKey] = useState(0);
// Archive browser modal open/closed
const [archiveOpen, setArchiveOpen] = useState(false);
// ── Handlers ─────────────────────────────────────────────────────────
const handleEdit = useCallback((order: Order) => {
@@ -447,12 +450,20 @@ export default function OrderComponent() {
/>
)}
{/* ── Archive browser modal ── */}
{archiveOpen && (
<ArchivedOrdersModal onClose={() => setArchiveOpen(false)} />
)}
{/* ── Toast: transient success/error feedback for create, update,
delete, and status-change actions — sourced from useOrders()'
`notification` state (see useOrders.ts notify()). This is the
same toast used by the Driver pages via DriverDeleteModal's
parent and useDriver.ts, kept consistent here. ── */}
<Toast notification={notification} />
{/* Floating button to open the archive browser */}
<ArchiveButton onClick={() => setArchiveOpen(true)} />
</>
);
}

View File

@@ -7,7 +7,8 @@ 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 { Spinner } from "@/src/Components/UI";
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";
@@ -51,6 +52,67 @@ function StatusBadge({ status }: { status: TripStatus }) {
);
}
// ── Archived trips modal ─────────────────────────────────────────────────────
// Lightweight wrapper modal around <ArchivedTripList /> — 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 (
<div
role="dialog" aria-modal="true" aria-labelledby="archived-trips-title"
onClick={e => { 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",
}}
>
<div
onClick={e => 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 */}
<div dir="rtl" style={{
display: "flex", alignItems: "center", justifyContent: "space-between",
padding: "1.25rem 1.5rem",
borderBottom: "1px solid var(--color-border)",
background: "var(--color-surface)",
}}>
<h2 id="archived-trips-title" style={{ fontSize: 17, fontWeight: 700, color: "var(--color-text-primary)", margin: 0 }}>
أرشيف الرحلات
</h2>
<button type="button" onClick={onClose} aria-label="إغلاق"
style={{ width: 34, height: 34, borderRadius: "var(--radius-md)", border: "1px solid var(--color-border)", background: "var(--color-surface)", cursor: "pointer", fontSize: 18, color: "var(--color-text-muted)", display: "flex", alignItems: "center", justifyContent: "center" }}>
×
</button>
</div>
{/* body */}
<div style={{ padding: "1.5rem" }}>
<ArchivedTripList onView={trip => router.push(`/dashboard/trips/${trip.id}`)} />
</div>
</div>
</div>
);
}
// ── Page ─────────────────────────────────────────────────────────────────────
export default function TripsPage() {
@@ -93,6 +155,8 @@ export default function TripsPage() {
const [editTrip, setEditTrip] = useState<Trip | null>(null);
const [deleteTarget, setDeleteTarget] = useState<Trip | null>(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 (
@@ -146,22 +210,42 @@ export default function TripsPage() {
إدارة جدولة الرحلات وتتبع حالتها
</p>
</div>
<button
onClick={() => { setEditTrip(null); setShowForm(true); }}
style={{
height: 40, padding: "0 1.25rem",
borderRadius: "var(--radius-md)", border: "none",
background: "var(--color-brand-600)", color: "#FFF",
fontSize: 13, fontWeight: 700, cursor: "pointer",
display: "flex", alignItems: "center", gap: 8,
fontFamily: "var(--font-sans)",
}}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" />
</svg>
إضافة رحلة
</button>
<div style={{ display: "flex", gap: "0.75rem" }}>
<button
onClick={() => setArchiveOpen(true)}
style={{
height: 40, padding: "0 1.25rem",
borderRadius: "var(--radius-md)", border: "1px solid var(--color-border)",
background: "var(--color-surface)", color: "var(--color-text-secondary)",
fontSize: 13, fontWeight: 700, cursor: "pointer",
display: "flex", alignItems: "center", gap: 8,
fontFamily: "var(--font-sans)",
}}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="2" y="4" width="20" height="5" rx="1" />
<path d="M4 9v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9" />
<path d="M10 13h4" />
</svg>
أرشيف الرحلات
</button>
<button
onClick={() => { setEditTrip(null); setShowForm(true); }}
style={{
height: 40, padding: "0 1.25rem",
borderRadius: "var(--radius-md)", border: "none",
background: "var(--color-brand-600)", color: "#FFF",
fontSize: 13, fontWeight: 700, cursor: "pointer",
display: "flex", alignItems: "center", gap: 8,
fontFamily: "var(--font-sans)",
}}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" />
</svg>
إضافة رحلة
</button>
</div>
</div>
{/* ── Filters ── */}
@@ -367,6 +451,9 @@ export default function TripsPage() {
onConfirm={handleDelete}
/>
)}
{archiveOpen && (
<ArchivedTripsModal onClose={() => setArchiveOpen(false)} />
)}
{/* ── Toast ── */}
<Toast notification={notification} onDismiss={dismissNotification} />

View File

@@ -1,13 +1,14 @@
"use client";
import { useState } from "react";
import { Alert, Toast } from "@/src/Components/UI";
import { Alert, Toast, ArchiveButton } from "@/src/Components/UI";
import { UserTable } from "@/src/Components/User/UserTable";
import { UserFormModal } from "@/src/Components/User/UserFormModal";
import { UserDetailModal } from "@/src/Components/User/UserDetailModal";
import { DeleteConfirmModal } from "@/src/Components/User/DeleteConfirmModal";
import { useUsers } from "@/src/hooks/useUser";
import type { User, UserFormData } from "@/src/types/user";
import { ArchivedUsersModal } from "@/src/Components/User/archive/Archivedusersmodal";
export default function UsersPage() {
// ── Modal state ─────────────────────────────────────────────────────────────
@@ -16,6 +17,8 @@ export default function UsersPage() {
const [deleteTarget, setDeleteTarget] = useState<User | null>(null);
// ID of user whose detail modal is open; null = closed
const [viewUserId, setViewUserId] = useState<string | null>(null);
// Archive browser modal open/closed
const [archiveOpen, setArchiveOpen] = useState(false);
// Local submitting flag shown in DeleteConfirmModal spinner
const [deleting, setDeleting] = useState(false);
@@ -40,9 +43,9 @@ export default function UsersPage() {
const handleDeleteConfirm = async () => {
if (!deleteTarget || deleting) return;
setDeleting(true);
const ok = await deleteUser(deleteTarget.id);
setDeleting(false);
if (ok) setDeleteTarget(null);
};
@@ -84,6 +87,11 @@ export default function UsersPage() {
/>
)}
{/* Archive browser modal */}
{archiveOpen && (
<ArchivedUsersModal onClose={() => setArchiveOpen(false)} />
)}
<section style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
{/* ── Page header ── */}
@@ -178,6 +186,9 @@ export default function UsersPage() {
onPageChange={setPage}
/>
</section>
{/* Floating button to open the archive browser */}
<ArchiveButton onClick={() => setArchiveOpen(true)} />
</>
);
}