"use client"; import { useState, useCallback } from "react"; 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"; // ── Helpers — copied verbatim from app/dashboard/drivers/page.tsx ─────── function fmtDate(iso?: string | null): string { if (!iso) return "—"; return new Date(iso).toLocaleDateString("ar-SA", { year: "numeric", month: "short", day: "numeric", }); } function fmtAmount(n?: number | null): string { if (n == null) return "—"; return `${n.toFixed(2)} ر.س`; } // ── Styles — identical tokens/values to the Drivers page, per the // "no new styles" constraint; only column widths differ to fit Order's // field set (shipment / recipient / client / amount / status / actions). 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)", }; // Shared across header + rows — keep these in sync or columns will misalign. const ROW_GRID_COLUMNS = "1.6fr 1.6fr 1.2fr 1.1fr 1fr 0.8fr"; const iconBtnBase: React.CSSProperties = { width: 30, height: 30, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "var(--radius-md)", cursor: "pointer", flexShrink: 0, }; // ── Page Component ─────────────────────────────────────────────────────── export default function OrderComponent() { const { orders, loading, error, total, pages, page, search, statusFilter, setPage, handleSearch, handleStatusFilter, clearError, createOrder, updateOrder, updateStatus, deleteOrder, notification, reload, } = useOrders(); // ── Panel / modal state — same three-state shape as DriversPage // (selected id for the detail panel, "new" | Order | null for the // form modal, Order | null for the delete modal). ─────────────────── const [selectedOrderId, setSelectedOrderId] = useState(null); const [formOrder, setFormOrder] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const [deleting, setDeleting] = useState(false); // 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) => { setSelectedOrderId(null); setFormOrder(order); }, []); const handleDelete = useCallback((order: Order) => { setSelectedOrderId(null); setDeleteTarget(order); }, []); // ── Notifications: create/update success or failure surfaces as a // toast via useOrders()' notify() — see useOrders.ts createOrder / // updateOrder. Field-level / request errors additionally render // inside the form modal itself via its own . ──────────────── const handleFormSubmit = useCallback( async ( payload: CreateOrderPayload | UpdateOrderPayload, isNew: boolean, ): Promise => { let ok: boolean; if (isNew) { ok = await createOrder(payload as CreateOrderPayload); } else { const id = (formOrder as Order).id; ok = await updateOrder(id, payload as UpdateOrderPayload); // Re-fetch detail panel so updated fields are visible immediately if (ok && selectedOrderId) setPanelRefreshKey((k) => k + 1); } return ok; }, [formOrder, createOrder, updateOrder, selectedOrderId], ); // ── Notification: status change made from the detail panel bubbles up // here and fires the same success toast the table-level actions use, // keeping feedback consistent regardless of where the action started. ── const handleStatusChanged = useCallback(() => { setPanelRefreshKey((k) => k + 1); reload(); }, [reload]); const handleConfirmDelete = useCallback(async () => { if (!deleteTarget) return; setDeleting(true); const ok = await deleteOrder(deleteTarget.id); setDeleting(false); if (ok) setDeleteTarget(null); }, [deleteTarget, deleteOrder]); // ── Render ─────────────────────────────────────────────────────────── return ( <>
{/* ── Header ── */}

إدارة الشحنات

الطلبات

إجمالي{" "} {total}{" "} طلب مسجل

{/* Status filter — ported from app/dashboard/orders/page.tsx */} {/* 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, color: "var(--color-text-primary)", outline: "none", fontFamily: "var(--font-sans)", }} />
{/* Add button */}
{/* ── Notifications: page-level error banner (e.g. failed list load) dismissible via clearError() from the hook. Action-result toasts (create/update/delete/status success or failure) render separately below via , matching DriversPage's split between a persistent for fetch errors and a transient for action feedback. ── */} {error && ( )} {/* ── Table ── */}
رقم الشحنة المستلم العميل المبلغ الحالة إجراءات
{loading ? (
جارٍ التحميل…
) : orders.length === 0 ? (

لا توجد نتائج {search && `لـ "${search}"`}

) : (
    {orders.map((o, i) => { const statusCfg = ORDER_STATUS_MAP[o.currentStatus] ?? ORDER_STATUS_MAP.Created; return (
  • setSelectedOrderId(o.id)} 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", transition: "background 0.15s", }} onMouseEnter={(e) => (e.currentTarget.style.background = "var(--color-surface-hover, #F8FAFC)")} onMouseLeave={(e) => (e.currentTarget.style.background = i % 2 !== 0 ? "var(--color-surface-muted)" : "transparent")} >

    {o.shipmentNumber}

    {fmtDate(o.createdAt)}

    {o.recipientName}

    {o.recipientPhone}

    {o.client?.name ?? "—"} {fmtAmount(o.totalPrice)} {statusCfg.label} {/* ── Inline row actions: edit / delete ── stopPropagation is required on both buttons so the click doesn't bubble up to the
  • onClick and open the detail panel as well. */}
  • ); })}
)} {/* ── Pagination ── */} {pages > 1 && (
صفحة{" "} {page}{" "} من{" "} {pages}
{[ { label: "السابق", action: () => setPage(p => Math.max(1, p - 1)), disabled: page === 1 }, { label: "التالي", action: () => setPage(p => Math.min(pages, p + 1)), disabled: page === pages }, ].map(btn => ( ))}
)}
{/* ── Detail panel — key forces re-fetch after a successful edit or status change. Status updates triggered inside the panel call updateStatus() from useOrders() (via handleStatusChanged), which fires the same toast as a table-level action. ── */} {selectedOrderId && ( setSelectedOrderId(null)} onEdit={handleEdit} onDelete={handleDelete} onStatusChanged={handleStatusChanged} /> )} {/* ── Form modal ── */} {formOrder !== null && ( setFormOrder(null)} onSubmit={handleFormSubmit} /> )} {/* ── Delete modal ── */} {deleteTarget && ( setDeleteTarget(null)} onConfirm={handleConfirmDelete} /> )} {/* ── 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)} /> ); } // Named export alongside the default, mirroring src/Components/Driver/index.ts's // pattern of re-exporting each piece for use elsewhere (e.g. a future // app/dashboard/orders/page.tsx importing { OrderComponent }). export { OrderComponent };