Files
LogisicsApp_Client/app/dashboard/orders/page.tsx

187 lines
7.7 KiB
TypeScript

"use client";
import { useState, useCallback } from "react";
import { Alert, Toast, ArchiveButton, ConfirmDialog } from "@/src/Components/UI";
import { ArchivedOrdersModal } from "@/src/Components/Order/archive/ArchivedOrdersModal";
import { useOrders } from "@/src/hooks/useOrder";
import type { CreateOrderPayload, Order, UpdateOrderPayload } from "@/src/types/order";
import { OrderFormModal, OrderDetailPanel, OrderTable } from "@/src/Components/Order";
import Header from "@/src/Components/UI/Header";
// ── Page Component ───────────────────────────────────────────────────────
// Table markup/styles now live in src/Components/Order/orderTable.tsx;
// detail panel in OrderDetailModel.tsx (renamed from OrderDetailBanel.tsx);
// form modal unchanged in OrderFormModal.tsx. This page only wires state
// and handlers between the three.
export default function OrderComponent() {
const {
orders, loading, error, total, pages, page,
search, statusFilter, setPage, handleSearch, handleStatusFilter, clearError,
createOrder, updateOrder, 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<string | null>(null);
const [formOrder, setFormOrder] = useState<Order | null | "new">(null);
const [deleteTarget, setDeleteTarget] = useState<Order | null>(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 <Alert>. ────────────────
const handleFormSubmit = useCallback(
async (
payload: CreateOrderPayload | UpdateOrderPayload,
isNew: boolean,
): Promise<boolean> => {
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 (
<>
<section style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
{/* ── Header ── */}
<Header mainTitle="إدارة الطلبات"
title="الطلبات"
name="طلب"
state={{ total }}
search={search}
setSearch={handleSearch}
module=""
setModule={() => {}}
setPage={setPage}
isAudit={false}
onAdd={() => setFormOrder("new")}/>
{/* ── 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 <Toast>, matching DriversPage's split
between a persistent <Alert> for fetch errors and a transient
<Toast> for action feedback. ── */}
{error && (
<Alert type="error" message={error} onClose={clearError} />
)}
{/* ── Table ── */}
<OrderTable
orders={orders}
loading={loading}
search={search}
page={page}
pages={pages}
setPage={setPage}
onRowClick={setSelectedOrderId}
onEdit={handleEdit}
onDelete={handleDelete}
/>
</section>
{/* ── 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 && (
<OrderDetailPanel
key={`${selectedOrderId}-${panelRefreshKey}`}
orderId={selectedOrderId}
onClose={() => setSelectedOrderId(null)}
onEdit={handleEdit}
onDelete={handleDelete}
onStatusChanged={handleStatusChanged}
/>
)}
{/* ── Form modal ── */}
{formOrder !== null && (
<OrderFormModal
editOrder={formOrder === "new" ? null : formOrder}
onClose={() => setFormOrder(null)}
onSubmit={handleFormSubmit}
/>
)}
{/* ── Delete modal ── */}
<ConfirmDialog
open={!!deleteTarget}
loading={deleting}
onCancel={() => setDeleteTarget(null)}
onConfirm={handleConfirmDelete}
title="حذف الطلب"
description={`هل أنت متأكد من حذف الطلب ${deleteTarget?.shipmentNumber ?? ""} الخاص بـ ${deleteTarget?.recipientName ?? ""}؟ لا يمكن التراجع عن هذا الإجراء.`}
/>
{/* ── 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)} />
</>
);
}
// Named export alongside the default, mirroring src/Components/Driver/index.ts's
// pattern of re-exporting each piece for use elsewhere.
export { OrderComponent };