diff --git a/app/dashboard/clients/[clientId]/addresses/[addressesId]/page.tsx b/app/dashboard/clients/[clientId]/addresses/[addressesId]/page.tsx index 5291e4c..39a3e02 100644 --- a/app/dashboard/clients/[clientId]/addresses/[addressesId]/page.tsx +++ b/app/dashboard/clients/[clientId]/addresses/[addressesId]/page.tsx @@ -1,339 +1,100 @@ +// app/dashboard/[clientId]/addresses/[addressesId]/page.tsx "use client"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useParams, useRouter } from "next/navigation"; -import { Alert, ConfirmDialog, Toast } from "@/src/Components/UI"; -import { AddressFormModal } from "@/src/Components/Client"; -import { AddressDetails } from "@/src/Components/Client_Adress/AddressDetails"; -import { clientAddressService } from "@/src/services/clientAddress.service"; -import { clientService } from "@/src/services/client.service"; -import { getStoredToken } from "@/src/lib/auth"; +import { Alert, Spinner, Button } from "@/src/Components/UI"; +import { AddressDetails } from "@/src/Components/Client_Adress/AddressDetails"; +import { clientAddressService } from "@/src/services/clientAddress.service"; +import { getStoredToken } from "@/src/lib/auth"; +import type { ClientAddress } from "@/src/types/client_adresses"; -import type { Client } from "@/src/types/client"; -import type { ClientAddress } from "@/src/types/client_adresses"; -import type { ToastNotification } from "@/src/Components/UI"; -import type { - CreateAddressFormValues, - UpdateAddressFormValues, -} from "@/src/validations/client_address.validator"; +export default function AddressDetailsPage() { + const params = useParams(); + const router = useRouter(); -// ─── Page ────────────────────────────────────────────────────────────────── + const clientId = params?.clientId as string | undefined; + const addressId = params?.addressesId as string | undefined; -export default function AddressDetailPage() { - const params = useParams(); - const router = useRouter(); - - const clientId = (params?.clientId ?? params?.id) as string | undefined; - const addressId = params?.addressId as string | undefined; - - // ── Data state ─────────────────────────────────────────────────────────── const [address, setAddress] = useState(null); - const [client, setClient] = useState(null); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const [error, setError] = useState(null); - // ── Modal state ────────────────────────────────────────────────────────── - const [editOpen, setEditOpen] = useState(false); - const [deleteOpen, setDeleteOpen] = useState(false); - const [deleting, setDeleting] = useState(false); - - // ── Toast ──────────────────────────────────────────────────────────────── - const [notification, setNotification] = useState(null); - - const notify = (n: ToastNotification) => { - setNotification(n); - setTimeout(() => setNotification(null), 4000); - }; - - // ── Fetch ──────────────────────────────────────────────────────────────── - useEffect(() => { + const loadAddress = useCallback(async () => { if (!clientId || !addressId) return; setLoading(true); - - Promise.all([ - clientAddressService.getById(clientId, addressId, getStoredToken()), - clientService.getById(clientId, getStoredToken()), - ]) - .then(([addrRes, clientRes]) => { - const raw = (addrRes as any).data ?? addrRes; - setAddress({ ...raw, id: raw.id ?? raw._id }); - setClient(clientRes?.data); - }) - .catch(() => setError("تعذّر تحميل بيانات العنوان. يرجى المحاولة مجدداً.")) - .finally(() => setLoading(false)); + setError(null); + try { + const token = getStoredToken(); + const res = await clientAddressService.getById(clientId, addressId, token); + // normalize in case the API returns _id instead of id + const raw = res.data as ClientAddress & { _id?: string }; + setAddress({ ...raw, id: raw.id ?? raw._id ?? "" }); + } catch { + setError("تعذّر تحميل بيانات العنوان. يرجى المحاولة لاحقاً."); + } finally { + setLoading(false); + } }, [clientId, addressId]); - // ── Update ─────────────────────────────────────────────────────────────── - const handleUpdateSubmit = async ( - data: CreateAddressFormValues | UpdateAddressFormValues - ): Promise => { - if (!clientId || !addressId) return false; - try { - const res = await clientAddressService.update( - clientId, - addressId, - data as UpdateAddressFormValues, - getStoredToken() - ); - const raw = (res as any).data ?? res; - setAddress({ ...raw, id: raw.id ?? raw._id }); - notify({ type: "success", message: "تم تحديث العنوان بنجاح." }); - return true; - } catch (err) { - notify({ - type: "error", - message: err instanceof Error ? err.message : "تعذّر تحديث العنوان.", - }); - return false; - } - }; + useEffect(() => { + loadAddress(); + }, [loadAddress]); - // ── Delete ─────────────────────────────────────────────────────────────── - const handleDeleteConfirm = async () => { - if (!clientId || !addressId || deleting) return; - setDeleting(true); - try { - await clientAddressService.delete(clientId, addressId, getStoredToken()); - notify({ type: "success", message: "تم حذف العنوان بنجاح." }); - // short delay so the toast shows before navigation - setTimeout(() => { - router.replace(`/dashboard/clients/${clientId}/addresses`); - }, 700); - } catch (err) { - notify({ - type: "error", - message: err instanceof Error ? err.message : "تعذّر حذف العنوان.", - }); - setDeleting(false); - } - }; - - // ── Guards ─────────────────────────────────────────────────────────────── + // ── Loading ────────────────────────────────────────────────────────────── if (loading) { return ( -
-
+
+ + جارٍ تحميل بيانات العنوان…
); } + // ── Error ──────────────────────────────────────────────────────────────── if (error || !address) { return ( -
- - -
+ العودة إلى العناوين + +
); } - // ── Render ──────────────────────────────────────────────────────────────── + // ── Content ────────────────────────────────────────────────────────────── return ( - <> - +
+ - {/* Edit modal */} - {editOpen && ( - setEditOpen(false)} - onSubmit={handleUpdateSubmit} - /> - )} - - {/* Delete confirmation */} - { if (!deleting) setDeleteOpen(false); }} - onConfirm={handleDeleteConfirm} - title="حذف العميل" - description={`هل أنت متأكد من حذف ${address.label}؟ سيتم حذف جميع عناوينه أيضاً. لا يمكن التراجع عن هذا الإجراء.`} - /> - -
- - {/* ── Page header ── */} -
- {/* Back link */} - - -

- تفاصيل العنوان -

- -
-
-

- {address.label} -

- {client && ( -

- {client.name} -

- )} -
- - {/* Action buttons */} -
- - - -
-
-
- - {/* ── Address details component ── */} - - -
- + +
); } \ No newline at end of file diff --git a/app/dashboard/clients/[clientId]/addresses/page.tsx b/app/dashboard/clients/[clientId]/addresses/page.tsx index 4021d1e..8bf5fe1 100644 --- a/app/dashboard/clients/[clientId]/addresses/page.tsx +++ b/app/dashboard/clients/[clientId]/addresses/page.tsx @@ -3,14 +3,19 @@ import { useCallback, useEffect, useState } from "react"; import { useParams, useRouter } from "next/navigation"; -import { Alert, ConfirmDialog, Toast, ArchiveButton } from "@/src/Components/UI"; +import { + Alert, + ConfirmDialog, + Toast, + ArchiveButton, +} from "@/src/Components/UI"; import { useClientAddresses } from "@/src/hooks/useClientAddresses"; -import { clientService } from "@/src/services/client.service"; -import { getStoredToken } from "@/src/lib/auth"; +import { clientService } from "@/src/services/client.service"; +import { getStoredToken } from "@/src/lib/auth"; import type { Client, ClientFormData } from "@/src/types/client"; -import type { ClientAddress } from "@/src/types/client_adresses"; +import type { ClientAddress } from "@/src/types/client_adresses"; import { AddressFormModal, ClientFormModal } from "@/src/Components/Client"; import { ArchivedClientsAddressesModal } from "@/src/Components/Client_Adress/archive/ArchivedClientsAddressesModal"; @@ -23,16 +28,16 @@ import type { function labelIcon(label: string): string { const map: Record = { - "فوترة": "💳", - "شحن": "📦", - "المقر الرئيسي": "🏢", - "فرع": "🏬", - "مستودع": "🏭", - billing: "💳", - shipping: "📦", - "head office": "🏢", - branch: "🏬", - warehouse: "🏭", + فوترة: "💳", + شحن: "📦", + "المقر الرئيسي": "🏢", + فرع: "🏬", + مستودع: "🏭", + billing: "💳", + shipping: "📦", + "head office": "🏢", + branch: "🏬", + warehouse: "🏭", }; return map[label.toLowerCase()] ?? "📍"; } @@ -40,14 +45,21 @@ function labelIcon(label: string): string { // ─── AddressCard ─────────────────────────────────────────────────────────── interface AddressCardProps { - address: ClientAddress; - onEdit: () => void; - onDelete: () => void; - onSetPrimary: () => void; + address: ClientAddress; + onView: () => void; + onEdit: () => void; + onDelete: () => void; + onSetPrimary: () => void; settingPrimary: boolean; // true only while THIS card's request is in flight } -function AddressCard({ address, onEdit, onDelete, onSetPrimary, settingPrimary }: AddressCardProps) { +function AddressCard({ + address, + onEdit, + onDelete, + onSetPrimary, + settingPrimary, +}: AddressCardProps) { const { details, contactPerson } = address; const { street, @@ -73,6 +85,7 @@ function AddressCard({ address, onEdit, onDelete, onSetPrimary, settingPrimary } padding: "1.25rem", background: "var(--color-surface)", boxShadow: "var(--shadow-card)", + cursor: "pointer", }} > {/* Label row */} @@ -136,7 +149,13 @@ function AddressCard({ address, onEdit, onDelete, onSetPrimary, settingPrimary } gap: 2, }} > -

+

{street}

@@ -149,7 +168,14 @@ function AddressCard({ address, onEdit, onDelete, onSetPrimary, settingPrimary }

{(buildingNo || unitNo || additionalNo) && ( -

+

{buildingNo && `مبنى ${buildingNo}`} {unitNo && ` · وحدة ${unitNo}`} {additionalNo && ` · رقم إضافي ${additionalNo}`} @@ -185,6 +211,7 @@ function AddressCard({ address, onEdit, onDelete, onSetPrimary, settingPrimary } {/* Actions */}

e.stopPropagation()} style={{ display: "flex", flexWrap: "wrap", @@ -194,7 +221,12 @@ function AddressCard({ address, onEdit, onDelete, onSetPrimary, settingPrimary } borderTop: "1px solid var(--color-border)", }} > - + تعديل @@ -233,13 +265,13 @@ function ActionBtn({ disabled, children, }: { - onClick: () => void; - color: string; - bg: string; - border: string; - style?: React.CSSProperties; + onClick: () => void; + color: string; + bg: string; + border: string; + style?: React.CSSProperties; disabled?: boolean; - children: React.ReactNode; + children: React.ReactNode; }) { return ( -

+

إدارة العناوين

-

+

{client ? `${client.name} — العناوين` : "العناوين"}

-

+

إجمالي{" "} - {addresses.length}{" "} + + {addresses.length} + {" "} عنوان

@@ -541,7 +618,14 @@ export default function ClientAddressesPage() { fontFamily: "var(--font-sans)", }} > - + @@ -570,7 +654,14 @@ export default function ClientAddressesPage() { whiteSpace: "nowrap", }} > - + @@ -594,8 +685,15 @@ export default function ClientAddressesPage() { borderTop: "1px solid var(--color-border)", }} > - {client.name} - + + {client.name} + + {client.email} {client.phone} @@ -620,7 +718,17 @@ export default function ClientAddressesPage() { {/* ── Address grid ── */} {addrLoading ? ( -
+

📍

-

+

لا توجد عناوين بعد

-

- أضف عنواناً واحداً على الأقل حتى يتمكن العميل من استقبال الشحنات والفواتير. +

+ أضف عنواناً واحداً على الأقل حتى يتمكن العميل من استقبال الشحنات + والفواتير.

@@ -668,22 +799,26 @@ export default function ClientAddressesPage() { gap: "1rem", }} > - {addresses.map((addr) => ( - setAddrFormTarget(addr)} - onDelete={() => setDeleteTarget(addr)} - onSetPrimary={() => handleSetPrimary(addr)} - settingPrimary={settingPrimaryId === addr.id} - /> - ))} + {addresses.map((addr) => ( + router.push(`/dashboard/${clientId}/addresses/${addr.id}`)} + onEdit={() => setAddrFormTarget(addr)} + onDelete={() => setDeleteTarget(addr)} + onSetPrimary={() => handleSetPrimary(addr)} + settingPrimary={settingPrimaryId === addr.id} + /> +))}
)} {/* Floating button to open the address archive for this client */} - setArchiveOpen(true)} label="أرشيف العناوين" /> + setArchiveOpen(true)} + label="أرشيف العناوين" + /> ); -} \ No newline at end of file +} diff --git a/app/dashboard/clients/page.tsx b/app/dashboard/clients/page.tsx index e64e8a5..44c09ab 100644 --- a/app/dashboard/clients/page.tsx +++ b/app/dashboard/clients/page.tsx @@ -131,7 +131,7 @@ export default function ClientsPage() { {/* Floating button to open the archive browser */} - setArchiveOpen(true)} label="أرشيف العملاء" /> + setArchiveOpen(true)} label="الأرشيف" /> ); } \ No newline at end of file diff --git a/app/dashboard/orders/page.tsx b/app/dashboard/orders/page.tsx index 88ae19d..30260af 100644 --- a/app/dashboard/orders/page.tsx +++ b/app/dashboard/orders/page.tsx @@ -1,68 +1,18 @@ "use client"; - - import { useState, useCallback } from "react"; -import { Alert, Spinner, Toast, ArchiveButton, ConfirmDialog } from "@/src/Components/UI"; -import { ORDER_STATUS_MAP } from "@/src/Components/Order/OrderDetailBanel"; +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} from "@/src/Components/Order"; +import { OrderFormModal, OrderDetailPanel, OrderTable } from "@/src/Components/Order"; import Header from "@/src/Components/UI/Header"; -// ── 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 ─────────────────────────────────────────────────────── +// 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 { @@ -167,171 +117,17 @@ export default function OrderComponent() { )} {/* ── 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 @@ -387,6 +183,5 @@ export default function OrderComponent() { } // 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 }). +// pattern of re-exporting each piece for use elsewhere. export { OrderComponent }; \ No newline at end of file diff --git a/src/Components/Client/Toast.tsx b/src/Components/Client/Toast.tsx deleted file mode 100644 index 183241d..0000000 --- a/src/Components/Client/Toast.tsx +++ /dev/null @@ -1,5 +0,0 @@ - - -// Re-export the canonical implementation so existing imports keep working. -export { Toast } from "@/src/Components/UI"; -export type { ToastNotification as Notification } from "@/src/Components/UI"; \ No newline at end of file diff --git a/src/Components/Client/archive/ArchivedClientDetailModal.tsx b/src/Components/Client/archive/ArchivedClientDetailModal.tsx index 690c6af..ec9c0d4 100644 --- a/src/Components/Client/archive/ArchivedClientDetailModal.tsx +++ b/src/Components/Client/archive/ArchivedClientDetailModal.tsx @@ -129,7 +129,7 @@ export function ArchivedClientDetailModal({ clientId, onClose }: ArchivedClientD fontSize: 12, color: "var(--color-text-secondary)", }}> {addr.label} - {" — "}{addr.street}، {addr.city} + {" — "}{addr.details?.street}، {addr.details?.city}، {addr.details?.state}، {addr.details?.country}
))}
diff --git a/src/Components/Client/index.ts b/src/Components/Client/index.ts index 50d4f21..5b31610 100644 --- a/src/Components/Client/index.ts +++ b/src/Components/Client/index.ts @@ -3,4 +3,3 @@ export { ClientFormModal } from "./Clientformmodal"; export { ClientTable } from "./Clienttable"; export { AddressFormModal } from "../Client_Adress/Addressformmodal"; -export { Toast } from "./Toast"; \ No newline at end of file diff --git a/src/Components/Order/OrderDetailBanel.tsx b/src/Components/Order/OrderDetailModel.tsx similarity index 100% rename from src/Components/Order/OrderDetailBanel.tsx rename to src/Components/Order/OrderDetailModel.tsx diff --git a/src/Components/Order/OrderFormModal.tsx b/src/Components/Order/OrderFormModal.tsx index b4cdd0d..b466289 100644 --- a/src/Components/Order/OrderFormModal.tsx +++ b/src/Components/Order/OrderFormModal.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import Link from "next/link"; -import { useForm } from "react-hook-form"; +import { useForm, type Resolver } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import { Alert, Button, Input, Modal, Select } from "../UI"; import { getStoredToken } from "@/src/lib/auth"; @@ -115,9 +115,20 @@ export function OrderFormModal({ setFocus, formState: { errors, isSubmitting }, } = useForm({ - // Cast the schema itself — create/update schemas differ structurally in - // which fields are required, so yup can't unify them into one type. - resolver: yupResolver((isNew ? createOrderSchema : updateOrderSchema) as any), + // create/update schemas differ structurally in which fields are + // required (e.g. tripId), so yup can't unify them into one type on + // its own — the ternary alone won't typecheck as a single schema + // argument. We cast in two places for two separate reasons: + // 1. `(...) as any` on the schema — lets the ternary's two + // differently-shaped ObjectSchemas pass as a single argument. + // 2. `as unknown as Resolver` on the whole call — + // stops TS from inferring the Resolver's generics from the + // schema's own (slightly different) inferred shape, which is + // what caused the earlier "quantity optional vs required" + // mismatch. + resolver: yupResolver( + (isNew ? createOrderSchema : updateOrderSchema) as any, + ) as unknown as Resolver, defaultValues: { shipmentNumber: editOrder?.shipmentNumber ?? "", recipientName: editOrder?.recipientName ?? "", diff --git a/src/Components/Order/OrderTable.tsx b/src/Components/Order/OrderTable.tsx new file mode 100644 index 0000000..42c3b54 --- /dev/null +++ b/src/Components/Order/OrderTable.tsx @@ -0,0 +1,248 @@ +"use client"; + +import { Spinner } from "../UI"; +import type { Order } from "@/src/types/order"; +import { ORDER_STATUS_MAP } from "./OrderDetailModel"; + +// ── 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, +}; + +interface OrderTableProps { + orders: Order[]; + loading: boolean; + search: string; + page: number; + pages: number; + setPage: React.Dispatch>; + onRowClick: (orderId: string) => void; + onEdit: (order: Order) => void; + onDelete: (order: Order) => void; +} + +export function OrderTable({ + orders, + loading, + search, + page, + pages, + setPage, + onRowClick, + onEdit, + onDelete, +}: OrderTableProps) { + return ( +
+
+ رقم الشحنة + المستلم + العميل + المبلغ + الحالة + إجراءات +
+ + {loading ? ( +
+ + جارٍ التحميل… +
+ ) : orders.length === 0 ? ( +

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

+ ) : ( +
    + {orders.map((o, i) => { + const statusCfg = ORDER_STATUS_MAP[o.currentStatus] ?? ORDER_STATUS_MAP.Created; + + return ( +
  • onRowClick(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 => ( + + ))} +
+
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/Components/Order/archive/ArchivedOrderDetailModal.tsx b/src/Components/Order/archive/ArchivedOrderDetailModal.tsx index f965be2..b4e528d 100644 --- a/src/Components/Order/archive/ArchivedOrderDetailModal.tsx +++ b/src/Components/Order/archive/ArchivedOrderDetailModal.tsx @@ -1,7 +1,7 @@ "use client"; import { Button, Modal } from "../../UI"; -import { ORDER_STATUS_MAP } from "../OrderDetailBanel"; +import { ORDER_STATUS_MAP } from "../OrderDetailModel"; import type { ArchivedOrder } from "@/src/types/order"; interface ArchivedOrderDetailModalProps { @@ -40,6 +40,7 @@ export function ArchivedOrderDetailModal({ order, onClose }: ArchivedOrderDetail title={order.shipmentNumber} subtitle="طلب مؤرشف" onClose={onClose} + zIndex={60} size="md" footer={