187 lines
7.7 KiB
TypeScript
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 }; |