add archive layout pages to user car driver trip order
This commit is contained in:
@@ -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)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user