From c33778012adda2a74be47a46238ad499dd3cb617 Mon Sep 17 00:00:00 2001
From: m7amedez5511
Date: Tue, 30 Jun 2026 17:35:18 +0300
Subject: [PATCH] add archive layout pages to user car driver trip order
---
app/dashboard/cars/page.tsx | 19 +-
app/dashboard/drivers/page.tsx | 13 +-
app/dashboard/orders/page.tsx | 17 +-
app/dashboard/trips/page.tsx | 121 ++++++-
app/dashboard/users/page.tsx | 17 +-
.../archive/ArchivedDriverDetailModal.tsx | 301 ++++++++++++++++++
.../Driver/archive/ArchivedDriverTable.tsx | 141 ++++++++
.../Driver/archive/ArchivedDrivers.tsx | 119 +++++++
.../archive/ArchivedOrderDetailModal.tsx | 85 +++++
.../Order/archive/ArchivedOrderTable.tsx | 123 +++++++
.../Order/archive/ArchivedOrdersModal.tsx | 115 +++++++
.../Trip/archive/ArchivedTripList.tsx | 167 ++++++++++
src/Components/UI/ArchiveButton.tsx | 61 ++++
src/Components/UI/index.ts | 4 +-
.../User/archive/Archiveduserdetailmodal.tsx | 235 ++++++++++++++
.../User/archive/Archivedusersmodal.tsx | 114 +++++++
.../User/archive/Archivedusertable.tsx | 134 ++++++++
.../car/archive/Archivedcardetailpanel.tsx | 149 +++++++++
.../car/archive/Archivedcarsmodal.tsx | 120 +++++++
.../car/archive/Archivedcartable.tsx | 133 ++++++++
src/hooks/archive/Usearchivedcars.ts | 65 ++++
src/hooks/archive/useArchivedDrivers.ts | 49 +++
src/hooks/archive/useArchivedOrders.ts | 58 ++++
src/hooks/archive/useArchivedTrips.ts | 45 +++
src/hooks/archive/useArchivedUsers.ts | 43 +++
.../archive/archivedDriver.service.ts | 46 +++
src/services/archive/archivedOrder.service.ts | 8 +
src/services/archive/archivedTrip.service.ts | 20 ++
src/services/archive/archivedUser.service.ts | 25 ++
src/types/driver.ts | 58 ++++
src/types/order.ts | 30 ++
src/types/trip.ts | 18 ++
src/types/user.ts | 46 +++
33 files changed, 2669 insertions(+), 30 deletions(-)
create mode 100644 src/Components/Driver/archive/ArchivedDriverDetailModal.tsx
create mode 100644 src/Components/Driver/archive/ArchivedDriverTable.tsx
create mode 100644 src/Components/Driver/archive/ArchivedDrivers.tsx
create mode 100644 src/Components/Order/archive/ArchivedOrderDetailModal.tsx
create mode 100644 src/Components/Order/archive/ArchivedOrderTable.tsx
create mode 100644 src/Components/Order/archive/ArchivedOrdersModal.tsx
create mode 100644 src/Components/Trip/archive/ArchivedTripList.tsx
create mode 100644 src/Components/UI/ArchiveButton.tsx
create mode 100644 src/Components/User/archive/Archiveduserdetailmodal.tsx
create mode 100644 src/Components/User/archive/Archivedusersmodal.tsx
create mode 100644 src/Components/User/archive/Archivedusertable.tsx
create mode 100644 src/Components/car/archive/Archivedcardetailpanel.tsx
create mode 100644 src/Components/car/archive/Archivedcarsmodal.tsx
create mode 100644 src/Components/car/archive/Archivedcartable.tsx
create mode 100644 src/hooks/archive/Usearchivedcars.ts
create mode 100644 src/hooks/archive/useArchivedDrivers.ts
create mode 100644 src/hooks/archive/useArchivedOrders.ts
create mode 100644 src/hooks/archive/useArchivedTrips.ts
create mode 100644 src/hooks/archive/useArchivedUsers.ts
create mode 100644 src/services/archive/archivedDriver.service.ts
create mode 100644 src/services/archive/archivedOrder.service.ts
create mode 100644 src/services/archive/archivedTrip.service.ts
create mode 100644 src/services/archive/archivedUser.service.ts
diff --git a/app/dashboard/cars/page.tsx b/app/dashboard/cars/page.tsx
index 1b7eaf9..9c9acbf 100644
--- a/app/dashboard/cars/page.tsx
+++ b/app/dashboard/cars/page.tsx
@@ -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(null);
const [formTarget, setFormTarget] = useState(false); // false = closed
const [deleteTarget, setDeleteTarget] = useState(null);
+ const [archiveOpen, setArchiveOpen] = useState(false);
const { toast, notify } = useToast();
@@ -226,6 +228,10 @@ export default function CarsPage() {
/>
)}
+ {archiveOpen && (
+ setArchiveOpen(false)} />
+ )}
+
{/* ── Header ── */}
@@ -400,6 +406,9 @@ export default function CarsPage() {
)}
+
+ {/* Floating button to open the archive browser */}
+ setArchiveOpen(true)} />
>
);
}
\ No newline at end of file
diff --git a/app/dashboard/drivers/page.tsx b/app/dashboard/drivers/page.tsx
index 74e08bd..a70e960 100644
--- a/app/dashboard/drivers/page.tsx
+++ b/app/dashboard/drivers/page.tsx
@@ -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 && (
+ setArchiveOpen(false)} />
+ )}
+
+ {/* ── Floating button to open the archive browser ── */}
+ setArchiveOpen(true)} />
>
);
}
\ No newline at end of file
diff --git a/app/dashboard/orders/page.tsx b/app/dashboard/orders/page.tsx
index d6a2266..5c1b043 100644
--- a/app/dashboard/orders/page.tsx
+++ b/app/dashboard/orders/page.tsx
@@ -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 && (
+ 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. ── */}
+
+ {/* Floating button to open the archive browser */}
+ setArchiveOpen(true)} />
>
);
}
diff --git a/app/dashboard/trips/page.tsx b/app/dashboard/trips/page.tsx
index 792ede4..85047b1 100644
--- a/app/dashboard/trips/page.tsx
+++ b/app/dashboard/trips/page.tsx
@@ -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 — 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() {
@@ -93,6 +155,8 @@ export default function TripsPage() {
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 (
@@ -146,22 +210,42 @@ export default function TripsPage() {
إدارة جدولة الرحلات وتتبع حالتها
-
+
+
+
+
{/* ── Filters ── */}
@@ -367,6 +451,9 @@ export default function TripsPage() {
onConfirm={handleDelete}
/>
)}
+ {archiveOpen && (
+ setArchiveOpen(false)} />
+ )}
{/* ── Toast ── */}
diff --git a/app/dashboard/users/page.tsx b/app/dashboard/users/page.tsx
index ccfbb02..1482c4e 100644
--- a/app/dashboard/users/page.tsx
+++ b/app/dashboard/users/page.tsx
@@ -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(null);
// ID of user whose detail modal is open; null = closed
const [viewUserId, setViewUserId] = useState(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 && (
+ setArchiveOpen(false)} />
+ )}
+
{/* ── Page header ── */}
@@ -178,6 +186,9 @@ export default function UsersPage() {
onPageChange={setPage}
/>
+
+ {/* Floating button to open the archive browser */}
+ setArchiveOpen(true)} />
>
);
}
\ No newline at end of file
diff --git a/src/Components/Driver/archive/ArchivedDriverDetailModal.tsx b/src/Components/Driver/archive/ArchivedDriverDetailModal.tsx
new file mode 100644
index 0000000..22a7e72
--- /dev/null
+++ b/src/Components/Driver/archive/ArchivedDriverDetailModal.tsx
@@ -0,0 +1,301 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { Spinner } from "../../UI";
+import { PhotoCard } from "../DriverPhotos";
+import { getStoredToken } from "@/src/lib/auth";
+import { archivedDriverService } from "@/src/services/archive/archivedDriver.service";
+import {
+ DRIVER_STATUS_MAP,
+ DRIVER_CARD_TYPE_MAP,
+ NATIONAL_ID_TYPE_MAP,
+} from "@/src/types/driver";
+import type { ArchivedDriver, DriverStatusHistoryEntry } from "@/src/types/driver";
+
+interface ArchivedDriverDetailModalProps {
+ driverId: string;
+ onClose: () => void;
+}
+
+// ── small helper components ───────────────────────────────────────────────────
+
+/** Renders a single label/value row inside the detail body. */
+function DetailRow({ label, value }: { label: string; value?: string | null }) {
+ return (
+
+
+ {label}
+
+
+ {value || "—"}
+
+
+ );
+}
+
+function Avatar({ name }: { name: string }) {
+ const initials = name.trim().split(" ").slice(0, 2).map(w => w[0]).join("").toUpperCase();
+ return (
+
+ {initials}
+
+ );
+}
+
+/**
+ * Modal showing the full profile of a single archived driver, including
+ * documents/photos and status history. Fetches `getById` and
+ * `getStatusHistory` in parallel on mount.
+ */
+export function ArchivedDriverDetailModal({ driverId, onClose }: ArchivedDriverDetailModalProps) {
+ const [driver, setDriver] = useState(null);
+ const [history, setHistory] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ // close on Escape
+ useEffect(() => {
+ const handler = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); };
+ window.addEventListener("keydown", handler);
+ return () => window.removeEventListener("keydown", handler);
+ }, [onClose]);
+
+ // fetch archived driver details + status history on mount
+ useEffect(() => {
+ let cancelled = false;
+ (async () => {
+ try {
+ const token = getStoredToken();
+ const [driverRes, historyRes] = await Promise.all([
+ archivedDriverService.getById(driverId, token),
+ archivedDriverService.getStatusHistory(driverId, token),
+ ]);
+ if (!cancelled) {
+ setDriver(driverRes.data);
+ setHistory(historyRes.data ?? []);
+ }
+ } catch {
+ if (!cancelled) setError("تعذّر تحميل بيانات السائق المؤرشف. يرجى المحاولة لاحقاً.");
+ } finally {
+ if (!cancelled) setLoading(false);
+ }
+ })();
+ return () => { cancelled = true; };
+ }, [driverId]);
+
+ // ── helpers ───────────────────────────────────────────────────────────────
+ const fmt = (iso?: string | null) =>
+ iso ? new Date(iso).toLocaleDateString("ar-SA", { year: "numeric", month: "long", day: "numeric" }) : null;
+
+ const statusConfig = driver ? DRIVER_STATUS_MAP[driver.status] : null;
+
+ // ── render ────────────────────────────────────────────────────────────────
+ return (
+ { if (e.target === e.currentTarget) onClose(); }}
+ style={{
+ position: "fixed", inset: 0, zIndex: 55,
+ background: "rgba(15,23,42,0.55)", backdropFilter: "blur(4px)",
+ display: "flex", alignItems: "center", justifyContent: "center", padding: "1rem",
+ }}
+ >
+
e.stopPropagation()}
+ style={{
+ width: "100%", maxWidth: 560,
+ background: "var(--color-surface)",
+ borderRadius: "var(--radius-2xl)",
+ border: "1px solid var(--color-border)",
+ boxShadow: "0 24px 64px rgba(0,0,0,.18)",
+ overflow: "hidden",
+ display: "flex", flexDirection: "column",
+ }}
+ >
+ {/* ── header ── */}
+
+
+
+ سائق مؤرشف
+
+
+ {driver?.name ?? "عرض السائق"}
+
+
+
+
+
+ {/* ── body ── */}
+
+
+ {/* loading */}
+ {loading && (
+
+
+ جارٍ التحميل…
+
+ )}
+
+ {/* error */}
+ {!loading && error && (
+
+ {error}
+
+ )}
+
+ {/* content */}
+ {!loading && driver && (
+
+
+ {/* avatar + name + status row */}
+
+
+
+
{driver.name}
+ {driver.userName && (
+
+ @{driver.userName}
+
+ )}
+ {statusConfig && (
+
+
+ {statusConfig.label}
+
+ )}
+
+
+
+ {/* core info */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* photos */}
+
+ الصور والمستندات
+
+
+
+ {/* status history */}
+ {history.length > 0 && (
+ <>
+
+ سجل الحالات
+
+
+ {history.map((h) => {
+ const s = DRIVER_STATUS_MAP[h.status] ?? DRIVER_STATUS_MAP.Active;
+ return (
+
+
+ {s.label}
+ {h.reason && (
+
+ — {h.reason}
+
+ )}
+
+
{fmt(h.createdAt)}
+
+ );
+ })}
+
+ >
+ )}
+
+ )}
+
+
+ {/* ── footer ── */}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/Driver/archive/ArchivedDriverTable.tsx b/src/Components/Driver/archive/ArchivedDriverTable.tsx
new file mode 100644
index 0000000..9108bea
--- /dev/null
+++ b/src/Components/Driver/archive/ArchivedDriverTable.tsx
@@ -0,0 +1,141 @@
+"use client";
+
+import { Spinner } from "../../UI";
+import { DRIVER_STATUS_MAP } from "@/src/types/driver";
+import type { ArchivedDriver } from "@/src/types/driver";
+
+// ── status badge ─────────────────────────────────────────────────────────────
+function StatusBadge({ status }: { status: ArchivedDriver["status"] }) {
+ const cfg = DRIVER_STATUS_MAP[status] ?? DRIVER_STATUS_MAP.Inactive;
+ return (
+
+
+ {cfg.label}
+
+ );
+}
+
+// ── icon button ──────────────────────────────────────────────────────────────
+function IconBtn({ onClick, title, color, bg, borderColor, children }: { onClick: () => void; title: string; color: string; bg: string; borderColor: string; children: React.ReactNode }) {
+ return (
+
+ );
+}
+
+// ── card / header styles ─────────────────────────────────────────────────────
+const cardStyle: React.CSSProperties = {
+ borderRadius: "var(--radius-xl)", border: "1px solid var(--color-border)",
+ background: "var(--color-surface)", overflow: "hidden", boxShadow: "var(--shadow-card)",
+};
+const thStyle: React.CSSProperties = {
+ padding: "0.75rem 1.5rem", fontSize: 11, fontWeight: 700,
+ textTransform: "uppercase", letterSpacing: "0.2em",
+ color: "var(--color-text-muted)", background: "var(--color-surface-muted)",
+ borderBottom: "1px solid var(--color-border)",
+};
+
+const ROW_GRID_COLUMNS = "2fr 1.5fr 1fr 1fr 1fr 100px";
+
+// ── Props ────────────────────────────────────────────────────────────────────
+interface ArchivedDriverTableProps {
+ drivers: ArchivedDriver[];
+ loading: boolean;
+ search: string;
+ page: number;
+ pages: number;
+ onView: (driver: ArchivedDriver) => void;
+ onPageChange: (p: number) => void;
+}
+
+/**
+ * Renders the paginated table of archived drivers, including loading,
+ * empty, and error-free states. Mirrors ArchivedUserTable for consistency.
+ */
+export function ArchivedDriverTable({ drivers, loading, search, page, pages, onView, onPageChange }: ArchivedDriverTableProps) {
+ return (
+
+ {/* column headers */}
+
+ الاسم
+ اسم المستخدم
+ الحالة
+ تاريخ الإنشاء
+ آخر تحديث
+ إجراءات
+
+
+ {/* loading state */}
+ {loading ? (
+
+
+ جارٍ التحميل…
+
+ ) : drivers.length === 0 ? (
+ /* empty state */
+
+
+ {search ? `لا توجد نتائج لـ "${search}"` : "لا يوجد سائقون في الأرشيف."}
+
+
+ ) : (
+ /* data rows */
+
+ {drivers.map((d, i) => (
+ - onView(d)} style={{
+ display: "grid", gridTemplateColumns: ROW_GRID_COLUMNS,
+ 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",
+ }}>
+
+ {d.userName ?? "—"}
+
+
+ {new Date(d.createdAt).toLocaleDateString("ar-SA", { year: "numeric", month: "short", day: "numeric" })}
+
+
+ {new Date(d.updatedAt).toLocaleDateString("ar-SA", { year: "numeric", month: "short", day: "numeric" })}
+
+
+ {/* view only — delete/restore not implemented yet, same as users */}
+
onView(d)}>
+
+
+
+
+ ))}
+
+ )}
+
+ {/* pagination */}
+ {pages > 1 && (
+
+
+ صفحة {page} من {pages}
+
+
+ {[
+ { label: "السابق", action: () => onPageChange(Math.max(1, page - 1)), disabled: page === 1 },
+ { label: "التالي", action: () => onPageChange(Math.min(pages, page + 1)), disabled: page === pages },
+ ].map(btn => (
+
+ ))}
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/Driver/archive/ArchivedDrivers.tsx b/src/Components/Driver/archive/ArchivedDrivers.tsx
new file mode 100644
index 0000000..e893a7a
--- /dev/null
+++ b/src/Components/Driver/archive/ArchivedDrivers.tsx
@@ -0,0 +1,119 @@
+"use client";
+
+import { useState } from "react";
+import { Alert } from "../../UI";
+import { ArchivedDriverTable } from "./ArchivedDriverTable";
+import { ArchivedDriverDetailModal } from "./ArchivedDriverDetailModal";
+import { useArchivedDrivers } from "@/src/hooks/archive/useArchivedDrivers";
+
+interface ArchivedDriversProps {
+ onClose: () => void;
+}
+
+/**
+ * Top-level modal for browsing archived drivers. Wires the
+ * useArchivedDrivers hook to the table and detail modal, mirroring
+ * ArchivedUsersModal's structure and behavior for drivers.
+ */
+export function ArchivedDrivers({ onClose }: ArchivedDriversProps) {
+ const [viewDriverId, setViewDriverId] = useState(null);
+
+ const {
+ drivers, loading, total, pages, error,
+ page, search,
+ setPage, handleSearch, clearError,
+ } = useArchivedDrivers();
+
+ 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",
+ }}
+ >
+ {viewDriverId && (
+
setViewDriverId(null)} />
+ )}
+
+ 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 */}
+
+
+
+ الأرشيف
+
+
+ السائقون المؤرشفون
+
+ ({total})
+
+
+
+
+
+
+ {/* body */}
+
+ {/* search */}
+
+
+
handleSearch(e.target.value)}
+ dir="rtl"
+ style={{
+ width: "100%", height: 40,
+ paddingRight: 36, paddingLeft: 12,
+ borderRadius: "var(--radius-lg)",
+ border: "1px solid var(--color-border)",
+ background: "var(--color-surface)",
+ fontSize: 13, outline: "none",
+ fontFamily: "var(--font-sans)",
+ color: "var(--color-text-primary)",
+ }}
+ />
+
+
+ {error &&
}
+
+
setViewDriverId(driver.id)}
+ onPageChange={setPage}
+ />
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/Order/archive/ArchivedOrderDetailModal.tsx b/src/Components/Order/archive/ArchivedOrderDetailModal.tsx
new file mode 100644
index 0000000..c57a98d
--- /dev/null
+++ b/src/Components/Order/archive/ArchivedOrderDetailModal.tsx
@@ -0,0 +1,85 @@
+"use client";
+
+import { ORDER_STATUS_MAP } from "../OrderDetailBanel";
+import type { ArchivedOrder } from "@/src/types/order";
+
+interface ArchivedOrderDetailModalProps {
+ order: ArchivedOrder;
+ onClose: () => void;
+}
+
+function DetailRow({ label, value, mono = false }: { label: string; value: string; mono?: boolean }) {
+ return (
+
+ {label}
+ {value}
+
+ );
+}
+
+function fmtDate(iso?: string | null): string {
+ if (!iso) return "—";
+ return new Date(iso).toLocaleDateString("ar-SA", { year: "numeric", month: "long", day: "numeric" });
+}
+
+export function ArchivedOrderDetailModal({ order, onClose }: ArchivedOrderDetailModalProps) {
+ const s = ORDER_STATUS_MAP[order.currentStatus] ?? ORDER_STATUS_MAP.Created;
+
+ return (
+ { if (e.target === e.currentTarget) onClose(); }}
+ style={{ position: "fixed", inset: 0, zIndex: 80, background: "rgba(15,23,42,0.55)", backdropFilter: "blur(4px)", display: "flex", alignItems: "center", justifyContent: "center", padding: "1rem" }}>
+
e.stopPropagation()}
+ style={{ width: "100%", maxWidth: 480, background: "var(--color-surface)", borderRadius: "var(--radius-2xl)", border: "1px solid var(--color-border)", boxShadow: "0 24px 64px rgba(0,0,0,.18)", overflow: "hidden" }}>
+
+
+
طلب مؤرشف
+
+ {order.shipmentNumber}
+
+
+
+
+
+
+
+
+ {s.label}
+
+
+
+
+
+
+
+
+
+ {order.statusHistory && order.statusHistory.length > 0 && (
+
+
سجل الحالات
+ {order.statusHistory.map(h => {
+ const hs = ORDER_STATUS_MAP[h.status] ?? ORDER_STATUS_MAP.Created;
+ return (
+
+ {hs.label}{h.reason ? ` — ${h.reason}` : ""}
+ {fmtDate(h.createdAt)}
+
+ );
+ })}
+
+ )}
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/Order/archive/ArchivedOrderTable.tsx b/src/Components/Order/archive/ArchivedOrderTable.tsx
new file mode 100644
index 0000000..33cd259
--- /dev/null
+++ b/src/Components/Order/archive/ArchivedOrderTable.tsx
@@ -0,0 +1,123 @@
+"use client";
+
+import { Spinner } from "../../UI";
+import { ORDER_STATUS_MAP } from "../OrderDetailBanel";
+import type { ArchivedOrder } from "@/src/types/order";
+
+function fmtAmount(n?: number | null): string {
+ if (n == null) return "—";
+ return `${n.toFixed(2)} ر.س`;
+}
+
+const cardStyle: React.CSSProperties = {
+ borderRadius: "var(--radius-xl)", border: "1px solid var(--color-border)",
+ background: "var(--color-surface)", overflow: "hidden", boxShadow: "var(--shadow-card)",
+};
+const thStyle: React.CSSProperties = {
+ padding: "0.75rem 1.5rem", fontSize: 11, fontWeight: 700,
+ textTransform: "uppercase", letterSpacing: "0.2em",
+ color: "var(--color-text-muted)", background: "var(--color-surface-muted)",
+ borderBottom: "1px solid var(--color-border)",
+};
+const ROW_GRID = "1.6fr 1.6fr 1fr 1fr 100px";
+
+interface ArchivedOrderTableProps {
+ orders: ArchivedOrder[];
+ loading: boolean;
+ search: string;
+ page: number;
+ pages: number;
+ onView: (order: ArchivedOrder) => void;
+ onPageChange: (p: number) => void;
+}
+
+export function ArchivedOrderTable({ orders, loading, search, page, pages, onView, onPageChange }: ArchivedOrderTableProps) {
+ return (
+
+
+ رقم الشحنة
+ المستلم
+ المبلغ
+ الحالة
+ إجراءات
+
+
+ {loading ? (
+
+
+ جارٍ التحميل…
+
+ ) : orders.length === 0 ? (
+
+
+ {search ? `لا توجد نتائج لـ "${search}"` : "لا توجد طلبات في الأرشيف."}
+
+
+ ) : (
+
+ {orders.map((o, i) => {
+ const s = ORDER_STATUS_MAP[o.currentStatus] ?? ORDER_STATUS_MAP.Created;
+ return (
+ -
+
+ {o.shipmentNumber}
+
+
+
{o.recipientName}
+
{o.recipientPhone}
+
+
+ {fmtAmount(o.subTotal)}
+
+
+
+ {s.label}
+
+
+
+
+
+ );
+ })}
+
+ )}
+
+ {pages > 1 && (
+
+
+ صفحة {page} من {pages}
+
+
+ {[
+ { label: "السابق", action: () => onPageChange(Math.max(1, page - 1)), disabled: page === 1 },
+ { label: "التالي", action: () => onPageChange(Math.min(pages, page + 1)), disabled: page === pages },
+ ].map(btn => (
+
+ ))}
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/Order/archive/ArchivedOrdersModal.tsx b/src/Components/Order/archive/ArchivedOrdersModal.tsx
new file mode 100644
index 0000000..0f85c1d
--- /dev/null
+++ b/src/Components/Order/archive/ArchivedOrdersModal.tsx
@@ -0,0 +1,115 @@
+"use client";
+
+import { useState } from "react";
+import { Alert } from "../../UI";
+import { ArchivedOrderTable } from "./ArchivedOrderTable";
+import { ArchivedOrderDetailModal } from "./ArchivedOrderDetailModal";
+import { useArchivedOrders } from "@/src/hooks/archive/useArchivedOrders";
+import type { ArchivedOrder } from "@/src/types/order";
+
+interface ArchivedOrdersModalProps {
+ onClose: () => void;
+}
+
+export function ArchivedOrdersModal({ onClose }: ArchivedOrdersModalProps) {
+ const [viewOrder, setViewOrder] = useState(null);
+
+ const {
+ orders, loading, total, pages, error,
+ page, search,
+ setPage, handleSearch, clearError,
+ } = useArchivedOrders();
+
+ 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",
+ }}
+ >
+ {viewOrder && (
+
setViewOrder(null)} />
+ )}
+
+ 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 */}
+
+
+
+ الأرشيف
+
+
+ الطلبات المؤرشفة
+
+ ({total})
+
+
+
+
+
+
+ {/* body */}
+
+ {/* search */}
+
+
+
handleSearch(e.target.value)}
+ dir="rtl"
+ style={{
+ width: "100%", height: 40,
+ paddingRight: 36, paddingLeft: 12,
+ borderRadius: "var(--radius-lg)",
+ border: "1px solid var(--color-border)",
+ background: "var(--color-surface)",
+ fontSize: 13, outline: "none",
+ fontFamily: "var(--font-sans)",
+ color: "var(--color-text-primary)",
+ }}
+ />
+
+
+ {error &&
}
+
+
setViewOrder(order)}
+ onPageChange={setPage}
+ />
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/Trip/archive/ArchivedTripList.tsx b/src/Components/Trip/archive/ArchivedTripList.tsx
new file mode 100644
index 0000000..cc6a9f6
--- /dev/null
+++ b/src/Components/Trip/archive/ArchivedTripList.tsx
@@ -0,0 +1,167 @@
+"use client";
+
+import { Spinner, Alert, EmptyState, Badge } from "@/src/Components/UI";
+import { useArchivedTrips } from "@/src/hooks/archive/useArchivedTrips";
+import { TRIP_STATUS_MAP } from "@/src/types/trip";
+import type { Trip } from "@/src/types/trip";
+
+interface ArchivedTripListProps {
+ /** Called when the user clicks a row to view trip details */
+ onView?: (trip: Trip) => void;
+}
+
+// ── Status badge — reuses the existing trip status color map ────────────────
+function TripStatusBadge({ status }: { status: Trip["status"] }) {
+ const s = TRIP_STATUS_MAP[status];
+ return (
+
+
+ {s.label}
+
+ );
+}
+
+// ── Date formatting helper ───────────────────────────────────────────────────
+function fmtDate(iso?: string | null): string {
+ if (!iso) return "—";
+ return new Date(iso).toLocaleDateString("ar-SA", { year: "numeric", month: "short", day: "numeric" });
+}
+
+/**
+ * ArchivedTripList
+ * Displays a paginated, searchable list of archived trips.
+ * Fetches data via useArchivedTrips (GET /trip/archived), handles loading,
+ * empty, and error states, and renders each trip with its key details.
+ *
+ * @example
+ * setViewTripId(trip.id)} />
+ */
+export function ArchivedTripList({ onView }: ArchivedTripListProps) {
+ const {
+ trips, loading, total, pages, page, search, error,
+ setPage, handleSearch, clearError,
+ } = useArchivedTrips();
+
+ return (
+
+ {/* ── Header: count + search ── */}
+
+
+ الرحلات المؤرشفة
+
+ ({total})
+
+
+
+
+
+
handleSearch(e.target.value)}
+ className="h-10 w-full rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-surface)] pr-9 pl-3 text-[13px] text-[var(--color-text-primary)] outline-none focus:border-[var(--color-brand-600)] focus:ring-2 focus:ring-[var(--color-brand-600)]/15"
+ />
+
+
+
+ {/* ── API error feedback ── */}
+ {error &&
}
+
+ {/* ── Card ── */}
+
+ {/* column headers */}
+
+ الرحلة
+ الحالة
+ البدء
+ الانتهاء
+ النقد المحصّل
+
+
+ {/* ── Loading state ── */}
+ {loading ? (
+
+
+ جارٍ التحميل…
+
+ ) : trips.length === 0 ? (
+ /* ── Empty state ── */
+
+ ) : (
+ /* ── Data rows ── */
+
+ {trips.map((trip, i) => (
+ - onView?.(trip)}
+ className={`grid grid-cols-[2fr_1.5fr_1fr_1fr_1fr] items-center gap-2 border-b border-[var(--color-border)] px-6 py-3.5 text-[13px] transition-colors hover:bg-[var(--color-surface-muted)] ${
+ onView ? "cursor-pointer" : ""
+ } ${i % 2 !== 0 ? "bg-[var(--color-surface-muted)]/40" : ""}`}
+ >
+
+
{trip.title}
+
{trip.tripNumber}
+
+
+
+
+
+ {fmtDate(trip.startTime)}
+
+
+ {fmtDate(trip.endTime)}
+
+
+ {trip.totalCashCollected != null
+ ? `${Number(trip.totalCashCollected).toLocaleString("ar-SA")} ر.س`
+ : "—"}
+
+
+ ))}
+
+ )}
+
+ {/* ── Pagination ── */}
+ {pages > 1 && (
+
+
+ صفحة {page} من{" "}
+ {pages}
+
+
+
+
+
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/UI/ArchiveButton.tsx b/src/Components/UI/ArchiveButton.tsx
new file mode 100644
index 0000000..6b5e6d7
--- /dev/null
+++ b/src/Components/UI/ArchiveButton.tsx
@@ -0,0 +1,61 @@
+"use client";
+
+interface ArchiveButtonProps {
+ onClick: () => void;
+ label?: string;
+ /** "floating" pins it bottom-right of the viewport; "inline" renders it in normal flow */
+ variant?: "floating" | "inline";
+}
+
+export function ArchiveButton({
+ onClick,
+ label = "الأرشيف",
+ variant = "floating",
+}: ArchiveButtonProps) {
+ const base: React.CSSProperties = {
+ height: 44,
+ padding: "0 1.25rem",
+ borderRadius: "var(--radius-full)",
+ border: "none",
+ background: "#EA580C", // orange-600, matches existing danger/success accent pattern
+ fontSize: 13,
+ fontWeight: 700,
+ color: "#FFF",
+ cursor: "pointer",
+ display: "inline-flex",
+ alignItems: "center",
+ gap: 8,
+ fontFamily: "var(--font-sans)",
+ boxShadow: "0 4px 14px rgba(234,88,12,.35)",
+ whiteSpace: "nowrap",
+ transition: "transform 150ms, box-shadow 150ms",
+ };
+
+ const floatingStyle: React.CSSProperties = variant === "floating"
+ ? {
+ position: "fixed",
+ bottom: 24,
+ insetInlineEnd: 24, // logical property — respects RTL/LTR automatically
+ zIndex: 40,
+ }
+ : {};
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/UI/index.ts b/src/Components/UI/index.ts
index 1421a94..14a7cc6 100644
--- a/src/Components/UI/index.ts
+++ b/src/Components/UI/index.ts
@@ -21,4 +21,6 @@ export { Modal } from "./ModalProps";
export { ConfirmDialog } from "./ConfirmDialog";
export { PageHeader } from "./PageHeader";
export { EmptyState } from "./EmptyState";
-export { Badge } from "./Badge";
\ No newline at end of file
+export { Badge } from "./Badge";
+
+export { ArchiveButton } from "./ArchiveButton"
\ No newline at end of file
diff --git a/src/Components/User/archive/Archiveduserdetailmodal.tsx b/src/Components/User/archive/Archiveduserdetailmodal.tsx
new file mode 100644
index 0000000..0bd9de1
--- /dev/null
+++ b/src/Components/User/archive/Archiveduserdetailmodal.tsx
@@ -0,0 +1,235 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { Spinner } from "../../UI";
+import { getStoredToken } from "@/src/lib/auth";
+import { archivedUserService } from "@/src/services/archive/archivedUser.service";
+import type { ArchivedUser } from "@/src/types/user";
+
+interface ArchivedUserDetailModalProps {
+ userId: string;
+ onClose: () => void;
+}
+
+// ── small helper components ───────────────────────────────────────────────────
+function DetailRow({ label, value }: { label: string; value?: string | null }) {
+ return (
+
+
+ {label}
+
+
+ {value || "—"}
+
+
+ );
+}
+
+function StatusBadge({ active }: { active: boolean }) {
+ return (
+
+
+ {active ? "نشط" : "معطل"}
+
+ );
+}
+
+function Avatar({ name }: { name: string }) {
+ const initials = name.trim().split(" ").slice(0, 2).map(w => w[0]).join("").toUpperCase();
+ return (
+
+ {initials}
+
+ );
+}
+
+// ── main component ────────────────────────────────────────────────────────────
+export function ArchivedUserDetailModal({ userId, onClose }: ArchivedUserDetailModalProps) {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ // close on Escape
+ useEffect(() => {
+ const handler = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); };
+ window.addEventListener("keydown", handler);
+ return () => window.removeEventListener("keydown", handler);
+ }, [onClose]);
+
+ // fetch archived user details on mount
+ useEffect(() => {
+ let cancelled = false;
+ (async () => {
+ try {
+ const token = getStoredToken();
+ const res = await archivedUserService.getById(userId, token);
+ if (!cancelled) setUser(res.data);
+ } catch {
+ if (!cancelled) setError("تعذّر تحميل بيانات المستخدم المؤرشف. يرجى المحاولة لاحقاً.");
+ } finally {
+ if (!cancelled) setLoading(false);
+ }
+ })();
+ return () => { cancelled = true; };
+ }, [userId]);
+
+ // ── helpers ───────────────────────────────────────────────────────────────
+ const fmt = (iso?: string | null) =>
+ iso ? new Date(iso).toLocaleDateString("ar-SA", { year: "numeric", month: "long", day: "numeric" }) : null;
+
+ // ── render ────────────────────────────────────────────────────────────────
+ return (
+ { if (e.target === e.currentTarget) onClose(); }}
+ style={{
+ position: "fixed", inset: 0, zIndex: 55,
+ background: "rgba(15,23,42,0.55)", backdropFilter: "blur(4px)",
+ display: "flex", alignItems: "center", justifyContent: "center", padding: "1rem",
+ }}
+ >
+
e.stopPropagation()}
+ style={{
+ width: "100%", maxWidth: 480,
+ background: "var(--color-surface)",
+ borderRadius: "var(--radius-2xl)",
+ border: "1px solid var(--color-border)",
+ boxShadow: "0 24px 64px rgba(0,0,0,.18)",
+ overflow: "hidden",
+ display: "flex", flexDirection: "column",
+ }}
+ >
+ {/* ── header ── */}
+
+
+
+ مستخدم مؤرشف
+
+
+ {user?.name ?? "عرض المستخدم"}
+
+
+
+
+
+ {/* ── body ── */}
+
+
+ {/* loading */}
+ {loading && (
+
+
+ جارٍ التحميل…
+
+ )}
+
+ {/* error */}
+ {!loading && error && (
+
+ {error}
+
+ )}
+
+ {/* content */}
+ {!loading && user && (
+
+
+ {/* avatar + name + status row */}
+
+
+
+
{user.name}
+ {user.userName && (
+
+ @{user.userName}
+
+ )}
+
+
+
+
+
+
+ {/* detail rows */}
+
+
+
+
+
+ )}
+
+
+ {/* ── footer ── */}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/User/archive/Archivedusersmodal.tsx b/src/Components/User/archive/Archivedusersmodal.tsx
new file mode 100644
index 0000000..f1e3703
--- /dev/null
+++ b/src/Components/User/archive/Archivedusersmodal.tsx
@@ -0,0 +1,114 @@
+"use client";
+
+import { useState } from "react";
+import { Alert } from "../../UI";
+import { ArchivedUserTable } from "./Archivedusertable";
+import { ArchivedUserDetailModal } from "./Archiveduserdetailmodal";
+import { useArchivedUsers } from "@/src/hooks/archive/useArchivedUsers";
+
+interface ArchivedUsersModalProps {
+ onClose: () => void;
+}
+
+export function ArchivedUsersModal({ onClose }: ArchivedUsersModalProps) {
+ const [viewUserId, setViewUserId] = useState(null);
+
+ const {
+ users, loading, total, pages, error,
+ page, search,
+ setPage, handleSearch, clearError,
+ } = useArchivedUsers();
+
+ 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",
+ }}
+ >
+ {viewUserId && (
+
setViewUserId(null)} />
+ )}
+
+ 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 */}
+
+
+
+ الأرشيف
+
+
+ المستخدمون المؤرشفون
+
+ ({total})
+
+
+
+
+
+
+ {/* body */}
+
+ {/* search */}
+
+
+
handleSearch(e.target.value)}
+ dir="rtl"
+ style={{
+ width: "100%", height: 40,
+ paddingRight: 36, paddingLeft: 12,
+ borderRadius: "var(--radius-lg)",
+ border: "1px solid var(--color-border)",
+ background: "var(--color-surface)",
+ fontSize: 13, outline: "none",
+ fontFamily: "var(--font-sans)",
+ color: "var(--color-text-primary)",
+ }}
+ />
+
+
+ {error &&
}
+
+
setViewUserId(user.id)}
+ onPageChange={setPage}
+ />
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/User/archive/Archivedusertable.tsx b/src/Components/User/archive/Archivedusertable.tsx
new file mode 100644
index 0000000..22f34f5
--- /dev/null
+++ b/src/Components/User/archive/Archivedusertable.tsx
@@ -0,0 +1,134 @@
+"use client";
+
+import { Spinner } from "../../UI";
+import type { ArchivedUser } from "@/src/types/user";
+
+// ── status badge ─────────────────────────────────────────────────────────────
+function StatusBadge({ active }: { active: boolean }) {
+ return (
+
+
+ {active ? "نشط" : "معطل"}
+
+ );
+}
+
+// ── icon button ──────────────────────────────────────────────────────────────
+function IconBtn({ onClick, title, color, bg, borderColor, children }: { onClick: () => void; title: string; color: string; bg: string; borderColor: string; children: React.ReactNode }) {
+ return (
+
+ );
+}
+
+// ── card / header styles ─────────────────────────────────────────────────────
+const cardStyle: React.CSSProperties = {
+ borderRadius: "var(--radius-xl)", border: "1px solid var(--color-border)",
+ background: "var(--color-surface)", overflow: "hidden", boxShadow: "var(--shadow-card)",
+};
+const thStyle: React.CSSProperties = {
+ padding: "0.75rem 1.5rem", fontSize: 11, fontWeight: 700,
+ textTransform: "uppercase", letterSpacing: "0.2em",
+ color: "var(--color-text-muted)", background: "var(--color-surface-muted)",
+ borderBottom: "1px solid var(--color-border)",
+};
+
+// ── Props ────────────────────────────────────────────────────────────────────
+interface ArchivedUserTableProps {
+ users: ArchivedUser[];
+ loading: boolean;
+ search: string;
+ page: number;
+ pages: number;
+ onView: (user: ArchivedUser) => void;
+ onPageChange: (p: number) => void;
+}
+
+// ── main table ───────────────────────────────────────────────────────────────
+export function ArchivedUserTable({ users, loading, search, page, pages, onView, onPageChange }: ArchivedUserTableProps) {
+ return (
+
+ {/* column headers */}
+
+ الاسم
+ اسم المستخدم
+ الحالة
+ تاريخ الإنشاء
+ آخر تحديث
+ إجراءات
+
+
+ {/* loading state */}
+ {loading ? (
+
+
+ جارٍ التحميل…
+
+ ) : users.length === 0 ? (
+ /* empty state */
+
+
+ {search ? `لا توجد نتائج لـ "${search}"` : "لا يوجد مستخدمون في الأرشيف."}
+
+
+ ) : (
+ /* data rows */
+
+ {users.map((u, i) => (
+ -
+
+ {u.userName ?? "—"}
+
+
+ {new Date(u.createdAt).toLocaleDateString("ar-SA", { year: "numeric", month: "short", day: "numeric" })}
+
+
+ {new Date(u.updatedAt).toLocaleDateString("ar-SA", { year: "numeric", month: "short", day: "numeric" })}
+
+
+ {/* view only — delete/restore not implemented yet */}
+
onView(u)}>
+
+
+
+
+ ))}
+
+ )}
+
+ {/* pagination */}
+ {pages > 1 && (
+
+
+ صفحة {page} من {pages}
+
+
+ {[
+ { label: "السابق", action: () => onPageChange(Math.max(1, page - 1)), disabled: page === 1 },
+ { label: "التالي", action: () => onPageChange(Math.min(pages, page + 1)), disabled: page === pages },
+ ].map(btn => (
+
+ ))}
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/car/archive/Archivedcardetailpanel.tsx b/src/Components/car/archive/Archivedcardetailpanel.tsx
new file mode 100644
index 0000000..34f7325
--- /dev/null
+++ b/src/Components/car/archive/Archivedcardetailpanel.tsx
@@ -0,0 +1,149 @@
+"use client";
+
+import { useEffect } from "react";
+import { Spinner } from "../../UI";
+import { STATUS_MAP, INS_MAP, fmtDate, isExpiringSoon } from "@/src/types/car";
+import type { Car, InsuranceStatus } from "@/src/types/car";
+
+interface ArchivedCarDetailPanelProps {
+ car: Car | null;
+ loading: boolean;
+ error: string | null;
+ onClose: () => void;
+}
+
+function DetailRow({ label, value, mono = false, warn = false }: {
+ label: string; value: string; mono?: boolean; warn?: boolean;
+}) {
+ return (
+
+ {label}
+
+ {warn && value !== "—" ? "⚠ " : ""}{value}
+
+
+ );
+}
+
+export function ArchivedCarDetailPanel({ car, loading, error, onClose }: ArchivedCarDetailPanelProps) {
+ 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: 80,
+ background: "rgba(15,23,42,0.55)", backdropFilter: "blur(4px)",
+ display: "flex", alignItems: "center", justifyContent: "center", padding: "1rem",
+ }}
+ >
+
e.stopPropagation()}
+ style={{
+ width: "100%", maxWidth: 480,
+ background: "var(--color-surface)",
+ borderRadius: "var(--radius-2xl)",
+ border: "1px solid var(--color-border)",
+ boxShadow: "0 24px 64px rgba(0,0,0,.18)",
+ overflow: "hidden",
+ display: "flex", flexDirection: "column",
+ }}
+ >
+ {/* header */}
+
+
+
+ مركبة مؤرشفة
+
+
+ {car ? `${car.manufacturer} ${car.model}` : "عرض المركبة"}
+
+
+
+
+
+ {/* body */}
+
+ {loading && (
+
+
+ جارٍ التحميل…
+
+ )}
+
+ {!loading && error && (
+
+ {error}
+
+ )}
+
+ {!loading && car && (
+ <>
+ {/* status badges */}
+
+ {(() => {
+ const s = STATUS_MAP[car.currentStatus];
+ return (
+
+ {s.label}
+
+ );
+ })()}
+ {car.insuranceStatus && (() => {
+ const ins = INS_MAP[car.insuranceStatus as InsuranceStatus];
+ return (
+
+ تأمين: {ins.label}
+
+ );
+ })()}
+
+ مؤرشفة
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+ {/* footer */}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/car/archive/Archivedcarsmodal.tsx b/src/Components/car/archive/Archivedcarsmodal.tsx
new file mode 100644
index 0000000..ab4c858
--- /dev/null
+++ b/src/Components/car/archive/Archivedcarsmodal.tsx
@@ -0,0 +1,120 @@
+"use client";
+
+import { useState } from "react";
+import { Alert } from "../../UI";
+import { ArchivedCarTable } from "./Archivedcartable";
+import { ArchivedCarDetailPanel } from "./Archivedcardetailpanel";
+import { useArchivedCars } from "@/src/hooks/archive/Usearchivedcars";
+import type { Car } from "@/src/types/car";
+
+interface ArchivedCarsModalProps {
+ onClose: () => void;
+}
+
+export function ArchivedCarsModal({ onClose }: ArchivedCarsModalProps) {
+ const [viewCar, setViewCar] = useState(null);
+
+ const {
+ cars, loading, total, pages, error,
+ page, search,
+ setPage, handleSearch, setError,
+ } = useArchivedCars();
+
+ 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",
+ }}
+ >
+ {viewCar && (
+
setViewCar(null)}
+ />
+ )}
+
+ e.stopPropagation()}
+ style={{
+ width: "100%", maxWidth: 980,
+ 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 */}
+
+
+
+ الأرشيف
+
+
+ المركبات المؤرشفة
+
+ ({total})
+
+
+
+
+
+
+ {/* body */}
+
+ {/* search */}
+
+
+
handleSearch(e.target.value)}
+ dir="rtl"
+ style={{
+ width: "100%", height: 40,
+ paddingRight: 36, paddingLeft: 12,
+ borderRadius: "var(--radius-lg)",
+ border: "1px solid var(--color-border)",
+ background: "var(--color-surface)",
+ fontSize: 13, outline: "none",
+ fontFamily: "var(--font-sans)",
+ color: "var(--color-text-primary)",
+ }}
+ />
+
+
+ {error &&
setError(null)} />}
+
+ setViewCar(car)}
+ onPageChange={setPage}
+ />
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/car/archive/Archivedcartable.tsx b/src/Components/car/archive/Archivedcartable.tsx
new file mode 100644
index 0000000..8b89883
--- /dev/null
+++ b/src/Components/car/archive/Archivedcartable.tsx
@@ -0,0 +1,133 @@
+"use client";
+
+import { Spinner } from "../../UI";
+import { STATUS_MAP, fmtDateShort } from "@/src/types/car";
+import type { Car } from "@/src/types/car";
+
+// ── card / header styles ─────────────────────────────────────────────────────
+const cardStyle: React.CSSProperties = {
+ borderRadius: "var(--radius-xl)", border: "1px solid var(--color-border)",
+ background: "var(--color-surface)", overflow: "hidden", boxShadow: "var(--shadow-card)",
+};
+const thStyle: React.CSSProperties = {
+ padding: "0.75rem 1.5rem", fontSize: 11, fontWeight: 700,
+ textTransform: "uppercase", letterSpacing: "0.2em",
+ color: "var(--color-text-muted)", background: "var(--color-surface-muted)",
+ borderBottom: "1px solid var(--color-border)",
+};
+
+// ── icon button ──────────────────────────────────────────────────────────────
+function IconBtn({ onClick, title, color, bg, borderColor, children }: { onClick: () => void; title: string; color: string; bg: string; borderColor: string; children: React.ReactNode }) {
+ return (
+
+ );
+}
+
+// ── Props ────────────────────────────────────────────────────────────────────
+interface ArchivedCarTableProps {
+ cars: Car[];
+ loading: boolean;
+ search: string;
+ page: number;
+ pages: number;
+ onView: (car: Car) => void;
+ onPageChange: (p: number) => void;
+}
+
+// ── main table ───────────────────────────────────────────────────────────────
+export function ArchivedCarTable({ cars, loading, search, page, pages, onView, onPageChange }: ArchivedCarTableProps) {
+ return (
+
+ {/* column headers */}
+
+ المركبة
+ رقم اللوحة
+ الفرع
+ آخر حالة
+ تاريخ الإضافة
+ إجراءات
+
+
+ {/* loading state */}
+ {loading ? (
+
+
+ جارٍ التحميل…
+
+ ) : cars.length === 0 ? (
+ /* empty state */
+
+
+ {search ? `لا توجد نتائج لـ "${search}"` : "لا توجد مركبات في الأرشيف."}
+
+
+ ) : (
+ /* data rows */
+
+ {cars.map((c, i) => {
+ const status = STATUS_MAP[c.currentStatus];
+ return (
+ -
+
+
+ {c.manufacturer} {c.model}
+
+
{c.year}{c.color ? ` · ${c.color}` : ""}
+
+
+ {c.plateLetters} {c.plateNumber}
+
+ {c.branch?.name ?? "—"}
+
+
+ {status.label}
+
+
+ {fmtDateShort(c.createdAt)}
+
+
+ {/* view only — delete/restore not implemented yet */}
+
onView(c)}>
+
+
+
+
+ );
+ })}
+
+ )}
+
+ {/* pagination */}
+ {pages > 1 && (
+
+
+ صفحة {page} من {pages}
+
+
+ {[
+ { label: "السابق", action: () => onPageChange(Math.max(1, page - 1)), disabled: page === 1 },
+ { label: "التالي", action: () => onPageChange(Math.min(pages, page + 1)), disabled: page === pages },
+ ].map(btn => (
+
+ ))}
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/hooks/archive/Usearchivedcars.ts b/src/hooks/archive/Usearchivedcars.ts
new file mode 100644
index 0000000..ace0811
--- /dev/null
+++ b/src/hooks/archive/Usearchivedcars.ts
@@ -0,0 +1,65 @@
+"use client";
+
+import { useEffect, useMemo, useState } from "react";
+import { getStoredToken } from "@/src/lib/auth";
+import { carService } from "@/src/services/car.service";
+import type { Car } from "@/src/types/car";
+
+const PAGE_SIZE = 12;
+
+// ── useArchivedCars ───────────────────────────────────────────────────────────
+// GET /cars/archived returns the full archived list at once (no page/search
+// params on the backend), so pagination + search are done client-side here —
+// mirrors the page/search/pages contract of useCars for a consistent UI.
+
+export function useArchivedCars() {
+ const [allCars, setAllCars] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [page, setPage] = useState(1);
+ const [search, setSearch] = useState("");
+
+ const load = () => {
+ const token = getStoredToken();
+ setLoading(true);
+ setError(null);
+ carService
+ .getArchived(token)
+ .then((res) => {
+ const payload = (res as unknown as { data: { data: Car[] } }).data ?? res;
+ setAllCars((payload as { data: Car[] }).data ?? []);
+ })
+ .catch((err: Error) => setError(err.message))
+ .finally(() => setLoading(false));
+ };
+
+ useEffect(() => { load(); }, []);
+
+ const filtered = useMemo(() => {
+ if (!search.trim()) return allCars;
+ const q = search.trim().toLowerCase();
+ return allCars.filter(c =>
+ c.manufacturer.toLowerCase().includes(q) ||
+ c.model.toLowerCase().includes(q) ||
+ c.plateNumber.toLowerCase().includes(q) ||
+ c.plateLetters.toLowerCase().includes(q),
+ );
+ }, [allCars, search]);
+
+ const total = filtered.length;
+ const pages = Math.max(1, Math.ceil(total / PAGE_SIZE));
+ const cars = useMemo(
+ () => filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE),
+ [filtered, page],
+ );
+
+ const handleSearch = (value: string) => {
+ setSearch(value);
+ setPage(1);
+ };
+
+ return {
+ cars, loading, error, total, pages, page, search,
+ setPage, handleSearch, refresh: load, setError,
+ };
+}
\ No newline at end of file
diff --git a/src/hooks/archive/useArchivedDrivers.ts b/src/hooks/archive/useArchivedDrivers.ts
new file mode 100644
index 0000000..2ffe8aa
--- /dev/null
+++ b/src/hooks/archive/useArchivedDrivers.ts
@@ -0,0 +1,49 @@
+import { useCallback, useEffect, useState } from "react";
+import { getStoredToken } from "@/src/lib/auth";
+import { archivedDriverService } from "@/src/services/archive/archivedDriver.service";
+import type { ArchivedDriver } from "@/src/types/driver";
+
+/**
+ * Loads and paginates the archived drivers list, mirroring useArchivedUsers.
+ * Re-fetches whenever `page` or `search` changes.
+ */
+export function useArchivedDrivers() {
+ const [drivers, setDrivers] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [total, setTotal] = useState(0);
+ const [pages, setPages] = useState(1);
+ const [page, setPage] = useState(1);
+ const [search, setSearch] = useState("");
+ const [error, setError] = useState(null);
+
+ /** Fetches the current page/search slice of archived drivers from the API. */
+ const load = useCallback(async () => {
+ setLoading(true);
+ try {
+ const token = getStoredToken();
+ const res = await archivedDriverService.getAll(page, search, token);
+ setDrivers(res.data.data);
+ setTotal(res.data.meta.total);
+ setPages(res.data.meta.totalPages);
+ setError(null);
+ } catch {
+ setError("تعذّر تحميل قائمة السائقين المؤرشفين. يرجى المحاولة لاحقاً.");
+ } finally {
+ setLoading(false);
+ }
+ }, [page, search]);
+
+ useEffect(() => { load(); }, [load]);
+
+ /** Updates the search term and resets to page 1. */
+ const handleSearch = (value: string) => {
+ setSearch(value);
+ setPage(1);
+ };
+
+ return {
+ drivers, loading, total, pages, page, search, error,
+ setPage, handleSearch, clearError: () => setError(null),
+ refresh: load,
+ };
+}
\ No newline at end of file
diff --git a/src/hooks/archive/useArchivedOrders.ts b/src/hooks/archive/useArchivedOrders.ts
new file mode 100644
index 0000000..357019c
--- /dev/null
+++ b/src/hooks/archive/useArchivedOrders.ts
@@ -0,0 +1,58 @@
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { getStoredToken } from "@/src/lib/auth";
+import { archivedOrderService } from "@/src/services/archive/archivedOrder.service";
+import type { ArchivedOrder } from "@/src/types/order";
+
+const PAGE_SIZE = 10;
+
+export function useArchivedOrders() {
+ const [orders, setOrders] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [page, setPage] = useState(1);
+ const [search, setSearch] = useState("");
+ const [error, setError] = useState(null);
+
+ const load = useCallback(async () => {
+ setLoading(true);
+ try {
+ const token = getStoredToken();
+ const res = await archivedOrderService.getAll(token);
+ setOrders(res.data);
+ setError(null);
+ } catch {
+ setError("تعذّر تحميل الطلبات المؤرشفة. يرجى المحاولة لاحقاً.");
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => { load(); }, [load]);
+
+ // Client-side search — endpoint has no `?search=` param in the sample payload
+ const filtered = useMemo(() => {
+ if (!search.trim()) return orders;
+ const q = search.trim().toLowerCase();
+ return orders.filter(
+ o =>
+ o.shipmentNumber.toLowerCase().includes(q) ||
+ o.recipientName.toLowerCase().includes(q) ||
+ o.recipientPhone.includes(q),
+ );
+ }, [orders, search]);
+
+ const pages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
+ const paginated = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
+
+ const handleSearch = (value: string) => {
+ setSearch(value);
+ setPage(1);
+ };
+
+ return {
+ orders: paginated,
+ total: filtered.length,
+ loading, pages, page, search, error,
+ setPage, handleSearch, clearError: () => setError(null),
+ refresh: load,
+ };
+}
\ No newline at end of file
diff --git a/src/hooks/archive/useArchivedTrips.ts b/src/hooks/archive/useArchivedTrips.ts
new file mode 100644
index 0000000..0320b16
--- /dev/null
+++ b/src/hooks/archive/useArchivedTrips.ts
@@ -0,0 +1,45 @@
+import { useCallback, useEffect, useState } from "react";
+import { getStoredToken } from "@/src/lib/auth";
+import { archivedTripService } from "@/src/services/archive/archivedTrip.service";
+import type { Trip } from "@/src/types/trip";
+
+export function useArchivedTrips() {
+ const [trips, setTrips] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [total, setTotal] = useState(0);
+ const [pages, setPages] = useState(1);
+ const [page, setPage] = useState(1);
+ const [search, setSearch] = useState("");
+ const [error, setError] = useState(null);
+
+ // ── Load archived trips for the current page/search ───────────────────────
+ const load = useCallback(async () => {
+ setLoading(true);
+ try {
+ const token = getStoredToken();
+ const res = await archivedTripService.getAll(page, search, token);
+ setTrips(res.data.data);
+ setTotal(res.data.meta.total);
+ setPages(res.data.meta.totalPages);
+ setError(null);
+ } catch {
+ // Network/API failure — surface a friendly Arabic message, matching useArchivedUsers
+ setError("تعذّر تحميل قائمة الرحلات المؤرشفة. يرجى المحاولة لاحقاً.");
+ } finally {
+ setLoading(false);
+ }
+ }, [page, search]);
+
+ useEffect(() => { load(); }, [load]);
+
+ const handleSearch = (value: string) => {
+ setSearch(value);
+ setPage(1);
+ };
+
+ return {
+ trips, loading, total, pages, page, search, error,
+ setPage, handleSearch, clearError: () => setError(null),
+ refresh: load,
+ };
+}
\ No newline at end of file
diff --git a/src/hooks/archive/useArchivedUsers.ts b/src/hooks/archive/useArchivedUsers.ts
new file mode 100644
index 0000000..9dd8bce
--- /dev/null
+++ b/src/hooks/archive/useArchivedUsers.ts
@@ -0,0 +1,43 @@
+import { useCallback, useEffect, useState } from "react";
+import { getStoredToken } from "@/src/lib/auth";
+import { archivedUserService } from "@/src/services/archive/archivedUser.service";
+import type { ArchivedUser } from "@/src/types/user";
+
+export function useArchivedUsers() {
+ const [users, setUsers] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [total, setTotal] = useState(0);
+ const [pages, setPages] = useState(1);
+ const [page, setPage] = useState(1);
+ const [search, setSearch] = useState("");
+ const [error, setError] = useState(null);
+
+ const load = useCallback(async () => {
+ setLoading(true);
+ try {
+ const token = getStoredToken();
+ const res = await archivedUserService.getAll(page, search, token);
+ setUsers(res.data.data);
+ setTotal(res.data.meta.total);
+ setPages(res.data.meta.totalPages);
+ setError(null);
+ } catch {
+ setError("تعذّر تحميل قائمة الأرشيف. يرجى المحاولة لاحقاً.");
+ } finally {
+ setLoading(false);
+ }
+ }, [page, search]);
+
+ useEffect(() => { load(); }, [load]);
+
+ const handleSearch = (value: string) => {
+ setSearch(value);
+ setPage(1);
+ };
+
+ return {
+ users, loading, total, pages, page, search, error,
+ setPage, handleSearch, clearError: () => setError(null),
+ refresh: load,
+ };
+}
\ No newline at end of file
diff --git a/src/services/archive/archivedDriver.service.ts b/src/services/archive/archivedDriver.service.ts
new file mode 100644
index 0000000..9e6840c
--- /dev/null
+++ b/src/services/archive/archivedDriver.service.ts
@@ -0,0 +1,46 @@
+import { get } from "../api";
+import type {
+ ArchivedDriverListResponse,
+ ArchivedDriverResponse,
+ ArchivedDriverStatusHistoryResponse,
+} from "@/src/types/driver";
+
+/**
+ * Builds the query string for the archived drivers list endpoint.
+ * @param page - 1-indexed page number
+ * @param search - free-text search term (name or phone), omitted if empty
+ * @returns a query string starting with "?"
+ */
+function buildArchivedQuery(page: number, search: string): string {
+ return `?page=${page}&limit=10${search ? `&search=${encodeURIComponent(search)}` : ""}`;
+}
+
+export const archivedDriverService = {
+ /**
+ * Fetches the paginated list of archived drivers.
+ * @param page - page number to fetch
+ * @param search - optional search term
+ * @param token - auth token, or null if unauthenticated
+ */
+ getAll: (page: number, search: string, token: string | null) =>
+ get(
+ `driver/archived${buildArchivedQuery(page, search)}`,
+ token,
+ ),
+
+ /**
+ * Fetches a single archived driver by id.
+ * @param id - archived driver id
+ * @param token - auth token, or null if unauthenticated
+ */
+ getById: (id: string, token: string | null) =>
+ get(`driver/archived/${id}`, token),
+
+ /**
+ * Fetches the status history for an archived driver.
+ * @param id - archived driver id
+ * @param token - auth token, or null if unauthenticated
+ */
+ getStatusHistory: (id: string, token: string | null) =>
+ get(`driver/archived/driverStatus/${id}`, token),
+};
\ No newline at end of file
diff --git a/src/services/archive/archivedOrder.service.ts b/src/services/archive/archivedOrder.service.ts
new file mode 100644
index 0000000..8043925
--- /dev/null
+++ b/src/services/archive/archivedOrder.service.ts
@@ -0,0 +1,8 @@
+import { get } from "../api";
+import type { ArchivedOrderListResponse } from "@/src/types/order";
+
+export const archivedOrderService = {
+ /** Fetching the full archived order list (endpoint returns no pagination meta) */
+ getAll: (token: string | null) =>
+ get("orders/archived", token),
+};
\ No newline at end of file
diff --git a/src/services/archive/archivedTrip.service.ts b/src/services/archive/archivedTrip.service.ts
new file mode 100644
index 0000000..c8a36d2
--- /dev/null
+++ b/src/services/archive/archivedTrip.service.ts
@@ -0,0 +1,20 @@
+import { get } from "../api";
+import type { ArchivedTripListResponse, ArchivedTripResponse } from "@/src/types/trip";
+
+/** Building a query string to fetch archived trips with pagination */
+function buildArchivedQuery(page: number, search: string): string {
+ return `?page=${page}&limit=10${search ? `&search=${encodeURIComponent(search)}` : ""}`;
+}
+
+export const archivedTripService = {
+ /** Fetching the archived trip list, paginated */
+ getAll: (page: number, search: string, token: string | null) =>
+ get(
+ `trip/archived${buildArchivedQuery(page, search)}`,
+ token,
+ ),
+
+ /** Get a single archived trip by id */
+ getById: (id: string, token: string | null) =>
+ get(`trip/archived/${id}`, token),
+};
\ No newline at end of file
diff --git a/src/services/archive/archivedUser.service.ts b/src/services/archive/archivedUser.service.ts
new file mode 100644
index 0000000..387ecb4
--- /dev/null
+++ b/src/services/archive/archivedUser.service.ts
@@ -0,0 +1,25 @@
+import { get } from "../api";
+import type {
+ ArchivedUserListResponse,
+ ArchivedUserResponse,
+} from "@/src/types/user";
+
+/** Building a query string to fetch archived users with pagination */
+function buildArchivedQuery(page: number, search: string): string {
+ return `?page=${page}&limit=10${search ? `&search=${encodeURIComponent(search)}` : ""}`;
+}
+
+export const archivedUserService = {
+ /** Fetching the archived user list, paginated */
+ getAll: (page: number, search: string, token: string | null) =>
+ get(
+ `users/archived${buildArchivedQuery(page, search)}`,
+ token,
+ ),
+
+ /** Get a single archived user by id */
+ getById: (id: string, token: string | null) =>
+ get(`users/archived/${id}`, token),
+
+ // NOTE: delete/restore intentionally left out for now — see ticket follow-up.
+};
\ No newline at end of file
diff --git a/src/types/driver.ts b/src/types/driver.ts
index c0fe1a3..421aba9 100644
--- a/src/types/driver.ts
+++ b/src/types/driver.ts
@@ -126,3 +126,61 @@ export const DRIVER_CARD_TYPE_MAP: Record = {
Restricted: "مقيدة",
};
+// ── Archived Driver ──────────────────────────────────────────────────────────
+// Resource returned by the /driver/archived endpoints. Kept as a distinct
+// shape (rather than reusing Driver) since the archive endpoints return a
+// flatter, slightly different set of fields — mirrors the ArchivedUser
+// pattern in src/types/user.ts.
+
+export interface ArchivedDriver {
+ id: string;
+ name: string;
+ userName?: string | null;
+ email?: string | null;
+ phone: string;
+ address?: string | null;
+ nationality?: string | null;
+ nationalId?: string | null;
+ nationalIdType?: NationalIdType | null;
+ gosiNumber?: string | null;
+ licenseNumber?: string | null;
+ licenseType?: string | null;
+ licenseExpiry?: string | null;
+ photo?: string | null;
+ photoUrl?: string | null;
+ nationalPhoto?: string | null;
+ nationalPhotoUrl?: string | null;
+ driverCardPhoto?: string | null;
+ driverCardPhotoUrl?: string | null;
+ driverCardNumber?: string | null;
+ driverCardType?: DriverCardType | null;
+ status: DriverStatus;
+ isActive: boolean;
+ isDeleted: boolean;
+ driverType?: string | null;
+ createdAt: string;
+ updatedAt: string;
+}
+
+// GET /driver/archived
+export interface ArchivedDriverListResponse {
+ data: {
+ data: ArchivedDriver[];
+ meta: {
+ total: number;
+ page: number;
+ limit: number;
+ totalPages: number;
+ };
+ };
+}
+
+// GET /driver/archived/{id}
+export interface ArchivedDriverResponse {
+ data: ArchivedDriver;
+}
+
+// GET /driver/archived/driverStatus/{id}
+export interface ArchivedDriverStatusHistoryResponse {
+ data: DriverStatusHistoryEntry[];
+}
\ No newline at end of file
diff --git a/src/types/order.ts b/src/types/order.ts
index ab727a3..4ce3975 100644
--- a/src/types/order.ts
+++ b/src/types/order.ts
@@ -106,4 +106,34 @@ export interface OrdersResponse {
limit: number;
totalPages: number;
};
+}
+// Archived order resource returned by /orders/archived.
+// NOTE: sample payload has no pagination meta — adjust if backend confirms otherwise.
+export interface ArchivedOrder {
+ id: string;
+ shipmentNumber: string;
+ recipientName: string;
+ recipientPhone: string;
+ clientId: string;
+ tripId?: string | null;
+ pickupAddressId?: string | null;
+ deliveryAddressId?: string | null;
+ currentStatus: OrderStatus;
+ quantity: number;
+ subTotal?: number | null;
+ createdAt: string;
+ updatedAt: string;
+ statusHistory?: Array<{
+ id: string;
+ status: OrderStatus;
+ reason?: string | null;
+ createdAt: string;
+ }>;
+}
+
+export interface ArchivedOrderListResponse {
+ success: boolean;
+ message: string;
+ responseAt: string;
+ data: ArchivedOrder[];
}
\ No newline at end of file
diff --git a/src/types/trip.ts b/src/types/trip.ts
index d963be9..8719798 100644
--- a/src/types/trip.ts
+++ b/src/types/trip.ts
@@ -155,3 +155,21 @@ export const TRIP_STATUS_MAP: Record<
Cancelled: { label: "ملغاة", color: "#991B1B", bg: "#FEF2F2", border: "#FECACA", dot: "#DC2626" },
};
+// ── Archived Trip types (mirrors ArchivedUser pattern in types/user.ts) ─────
+// GET /trip/archived/{id}
+export interface ArchivedTripResponse {
+ data: Trip;
+}
+
+// GET /trip/archived — paginated list
+export interface ArchivedTripListResponse {
+ data: {
+ data: Trip[];
+ meta: {
+ total: number;
+ page: number;
+ limit: number;
+ totalPages: number;
+ };
+ };
+}
\ No newline at end of file
diff --git a/src/types/user.ts b/src/types/user.ts
index 5c6f4c4..7479c3d 100644
--- a/src/types/user.ts
+++ b/src/types/user.ts
@@ -65,4 +65,50 @@ export type TableAction =
passwordChangedAt: string | null;
role: { id?: string; name: string; description?: string };
branch: { id?: string; name: string };
+}
+
+// Archived user resource returned by the archive endpoints
+export interface ArchivedUser {
+ id: string;
+ name: string;
+ userName?: string;
+ email?: string;
+ phone: string;
+ photo: string | null;
+ isActive: boolean;
+ isDeleted: boolean;
+ createdAt: string;
+ updatedAt: string;
+}
+
+// GET /v1/users/archived/{id}
+export interface ArchivedUserResponse {
+ data: ArchivedUser;
+}
+
+// GET /v1/users/archived (note: this endpoint uses `meta`, not `pagination`,
+// unlike the live users list — kept as a distinct shape rather than reusing
+// ApiListResponse from user.ts)
+export interface ArchivedUserListResponse {
+ data: {
+ data: ArchivedUser[];
+ meta: {
+ total: number;
+ page: number;
+ limit: number;
+ totalPages: number;
+ };
+ };
+}
+
+// Shared API error shape (validation_failed etc.)
+export interface ApiErrorResponse {
+ success: false;
+ message: string;
+ responseAt: string;
+ error: {
+ code: string;
+ path: string;
+ details: { field: string; code: string }[];
+ };
}
\ No newline at end of file