+
+ إدارة الكوادر
+
+
+
+
+
+ السائقون
+
+
+ إجمالي{" "}
+ {total} {" "}
+ سائق مسجل
+
+
- {/* ── Notifications ── */}
-
- {error &&
}
-
- {/* ── Table ── */}
-
-
- السائق
- الفرع
- الجنسية
- انتهاء الرخصة
- انتهاء الهوية
- الحالة
- إجراءات
-
-
- {loading ? (
-
-
- جارٍ التحميل…
-
- ) : drivers.length === 0 ? (
-
- لا توجد نتائج {search && `لـ "${search}"`}
-
- ) : (
-
- {drivers.map((d, i) => {
- const statusCfg =
- DRIVER_STATUS_MAP[d.status] ?? DRIVER_STATUS_MAP.Inactive;
- const licWarn = expirySoon(d.licenseExpiry);
- const idWarn = expirySoon(
- (d as Driver & { nationalIdExpiry?: string })
- .nationalIdExpiry,
- );
-
- return (
- setSelectedDriverId(d.id)}
+
+ {/* Search */}
+
+
+
handleSearch(e.target.value)}
+ dir="rtl"
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",
+ 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)",
}}
- 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")
- }
- >
-
-
- {d.name}
-
-
- {d.phone}
-
-
-
- {d.branch?.name ?? "—"}
-
-
- {d.nationality ?? "—"}
-
-
- {licWarn && "⚠ "}
- {fmtDate(d.licenseExpiry)}
-
-
- {idWarn && "⚠ "}
- {fmtDate(
- (d as Driver & { nationalIdExpiry?: string })
- .nationalIdExpiry,
- )}
-
-
-
- {statusCfg.label}
-
+ />
+
- {/* ── Inline row actions: edit / delete ──
- IconBtn already calls stopPropagation internally so
- the click doesn't bubble up to the
onClick and
- open the detail panel as well. */}
-
-
handleEdit(d)}
- >
-
-
-
-
-
-
-
handleDelete(d)}
- >
-
-
-
-
-
-
-
-
-
- );
- })}
-
- )}
-
- {/* ── Pagination ── */}
- {pages > 1 && (
-
-
- صفحة{" "}
-
- {page}
- {" "}
- من{" "}
-
- {pages}
-
-
-
- setPage((p) => Math.max(1, p - 1))}
- disabled={page === 1}
+ {/* Add button */}
+ setFormDriver("new")}
+ style={{
+ height: 40, padding: "0 1.25rem",
+ borderRadius: "var(--radius-lg)",
+ border: "none",
+ background: "var(--color-brand-600)",
+ fontSize: 13, fontWeight: 700, color: "#FFF",
+ cursor: "pointer",
+ display: "flex", alignItems: "center", gap: 8,
+ fontFamily: "var(--font-sans)",
+ flexShrink: 0,
+ }}
>
- السابق
-
- setPage((p) => Math.min(pages, p + 1))}
- disabled={page === pages}
- >
- التالي
-
+
+ إضافة سائق
+
- )}
-
+
+
+
+ {/* ── Notifications ── */}
+ {notification && (
+
+ )}
+ {error && (
+
+ )}
+
+ {/* ── Table ── */}
+
setSelectedDriverId(d.id)}
+ onEdit={handleEdit}
+ onDelete={handleDelete}
+ />
{/* ── Detail panel — key forces re-fetch after successful edit ── */}
@@ -466,7 +213,9 @@ export default function DriversPage() {
/>
{/* ── Archive browser modal ── */}
- {archiveOpen && setArchiveOpen(false)} />}
+ {archiveOpen && (
+ setArchiveOpen(false)} />
+ )}
{/* ── Floating button to open the archive browser ── */}
setArchiveOpen(true)} />
diff --git a/app/dashboard/orders/page.tsx b/app/dashboard/orders/page.tsx
index 30260af..2438daa 100644
--- a/app/dashboard/orders/page.tsx
+++ b/app/dashboard/orders/page.tsx
@@ -1,18 +1,17 @@
"use client";
+
+
import { useState, useCallback } from "react";
import { Alert, Toast, ArchiveButton, ConfirmDialog } from "@/src/Components/UI";
+import { ORDER_STATUS_MAP } from "@/src/Components/Order/OrderDetailModel";
+import { OrderTable } from "@/src/Components/Order/OrderTable";
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";
+import { OrderFormModal , OrderDetailPanel} from "@/src/Components/Order";
// ── 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 {
@@ -93,18 +92,97 @@ export default function OrderComponent() {
{/* ── Header ── */}
- {}}
- setPage={setPage}
- isAudit={false}
- onAdd={() => setFormOrder("new")}/>
+
+
+ إدارة الشحنات
+
+
+
+
+
+ الطلبات
+
+
+ إجمالي{" "}
+ {total} {" "}
+ طلب مسجل
+
+
+
+ {/* Status filter — ported from app/dashboard/orders/page.tsx */}
+
handleStatusFilter(e.target.value)}
+ dir="rtl"
+ style={{
+ height: 40, borderRadius: "var(--radius-lg)",
+ border: "1px solid var(--color-border)",
+ background: "var(--color-surface)",
+ fontSize: 13, color: "var(--color-text-secondary)",
+ padding: "0 0.75rem", outline: "none",
+ fontFamily: "var(--font-sans)", cursor: "pointer",
+ }}
+ >
+ كل الحالات
+ {Object.entries(ORDER_STATUS_MAP).map(([k, v]) => (
+ {v.label}
+ ))}
+
+
+ {/* 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 */}
+
setFormOrder("new")}
+ style={{
+ height: 40, padding: "0 1.25rem",
+ borderRadius: "var(--radius-lg)",
+ border: "none",
+ background: "var(--color-brand-600)",
+ fontSize: 13, fontWeight: 700, color: "#FFF",
+ cursor: "pointer",
+ display: "flex", alignItems: "center", gap: 8,
+ fontFamily: "var(--font-sans)",
+ flexShrink: 0,
+ }}
+ >
+
+ إضافة طلب
+
+
+
+
+
{/* ── Notifications: page-level error banner (e.g. failed list load)
dismissible via clearError() from the hook. Action-result
@@ -124,7 +202,7 @@ export default function OrderComponent() {
page={page}
pages={pages}
setPage={setPage}
- onRowClick={setSelectedOrderId}
+ onRowClick={(orderId) => setSelectedOrderId(orderId)}
onEdit={handleEdit}
onDelete={handleDelete}
/>
@@ -183,5 +261,6 @@ 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.
+// pattern of re-exporting each piece for use elsewhere (e.g. a future
+// app/dashboard/orders/page.tsx importing { OrderComponent }).
export { OrderComponent };
\ No newline at end of file
diff --git a/app/dashboard/trips/[tripId]/page.tsx b/app/dashboard/trips/[tripId]/page.tsx
index 77855ab..cdd4731 100644
--- a/app/dashboard/trips/[tripId]/page.tsx
+++ b/app/dashboard/trips/[tripId]/page.tsx
@@ -1,13 +1,6 @@
"use client";
-// app/dashboard/trips/[tripId]/page.tsx
-// CHANGE: TripFormModal now imported from the barrel (@/src/Components/Trip)
-// instead of a direct path — avoids the Tripformmodal.tsx case-sensitivity
-// mismatch that breaks on case-sensitive filesystems (Linux/Docker builds).
-// CHANGE: loadTrip's catch block now uses the same extractApiMessage helper
-// already defined in this file (and used by handleEditSubmit/handleConfirmDelete)
-// instead of `err instanceof Error ? err.message : ...` — so backend
-// validation messages surface correctly here too, not just on edit/delete.
+
import { useCallback, useEffect, useRef, useState } from "react";
import { useParams, useRouter } from "next/navigation";
@@ -336,9 +329,7 @@ export default function TripDetailPage() {
onClick={() => router.back()}
style={{ height: "auto", padding: 0, background: "none", marginBottom: "1rem" }}
>
-
-
-
+
العودة إلى قائمة الرحلات
@@ -437,10 +428,7 @@ export default function TripDetailPage() {
fontFamily: "var(--font-sans)",
}}
>
-
-
-
-
+
تعديل الرحلة
diff --git a/app/dashboard/trips/archived/[tripId]/page.tsx b/app/dashboard/trips/archived/[tripId]/page.tsx
index 5134eab..9640eda 100644
--- a/app/dashboard/trips/archived/[tripId]/page.tsx
+++ b/app/dashboard/trips/archived/[tripId]/page.tsx
@@ -1,16 +1,7 @@
"use client";
import { ArchivedTripDetailModal } from "@/src/Components/Trip/archive/ArchivedtripDetailModal";
-// app/dashboard/trips/archived/[tripId]/page.tsx
-// CHANGE: rebuilt as a thin route wrapper around the shared
-// ArchivedTripDetailModal instead of its own independent fetch/loading/error/
-// render logic. This route now shows the exact same full detail view as the
-// in-app archive browser (driver + car + counts + cash + notes + endReason,
-// not just the handful of fields this page used to render on its own), and
-// stays in sync automatically with any future changes to that component.
-// `onClose` navigates back to the trips list (via router.push) instead of
-// closing an in-place modal, since this route has no page behind it to
-// reveal.
+
import { useParams, useRouter } from "next/navigation";
diff --git a/app/forbidden/page.tsx b/app/forbidden/page.tsx
index c108b2f..c0beea6 100644
--- a/app/forbidden/page.tsx
+++ b/app/forbidden/page.tsx
@@ -21,11 +21,7 @@ export default function ForbiddenPage() {
border: "2px solid #FECACA",
display: "flex", alignItems: "center", justifyContent: "center",
}}>
-
-
-
-
-
+
-
-
-
-
+
لوحة التحكم
diff --git a/src/Components/Branch/BranchDetailModal.tsx b/src/Components/Branch/BranchDetailModal.tsx
index 87b3cfa..d62f154 100644
--- a/src/Components/Branch/BranchDetailModal.tsx
+++ b/src/Components/Branch/BranchDetailModal.tsx
@@ -39,10 +39,7 @@ function BranchIcon() {
flexShrink: 0,
boxShadow: "0 4px 12px rgba(37,99,235,.3)",
}}>
-
-
-
-
+
);
}
diff --git a/src/Components/Branch/BranchTable.tsx b/src/Components/Branch/BranchTable.tsx
index 17f4682..a1deb24 100644
--- a/src/Components/Branch/BranchTable.tsx
+++ b/src/Components/Branch/BranchTable.tsx
@@ -1,134 +1,96 @@
"use client";
-import { Badge, Button, EmptyState, IconBtn, Spinner } from "../UI";
+// src/Components/Branch/BranchTable.tsx
+// MIGRATED to ReusableTable + ActionButtons — same props, same visual
+// result as the original hand-rolled version. No changes required in
+// app/dashboard/branches/page.tsx.
+
+import { Badge, Button, ReusableTable, ActionButtons } from "../UI";
+import type { TableColumn } from "@/src/types/models";
import type { Branch } from "@/src/types/branch";
-// ── card / header styles ─────────────────────────────────────────────────────
-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)",
-};
-
-// ── Props ────────────────────────────────────────────────────────────────────
interface BranchTableProps {
- branches: Branch[];
- loading: boolean;
- search: string;
- page: number;
- pages: number;
- onEdit: (branch: Branch) => void;
- onDelete: (branch: Branch) => void;
- onView: (branch: Branch) => void;
- onAddFirst: () => void;
+ branches: Branch[];
+ loading: boolean;
+ search: string;
+ page: number;
+ pages: number;
+ onEdit: (branch: Branch) => void;
+ onDelete: (branch: Branch) => void;
+ onView: (branch: Branch) => void;
+ onAddFirst: () => void;
onPageChange: (p: number) => void;
}
-// ── main table ───────────────────────────────────────────────────────────────
-export function BranchTable({ branches, loading, search, page, pages, onEdit, onDelete, onView, onAddFirst, onPageChange }: BranchTableProps) {
+export function BranchTable({
+ branches, loading, search, page, pages,
+ onEdit, onDelete, onView, onAddFirst, onPageChange,
+}: BranchTableProps) {
+ const columns: TableColumn[] = [
+ {
+ key: "name",
+ header: "اسم الفرع",
+ width: "2fr",
+ render: (b) => (
+
+
{b.name}
+
{b.street}
+
+ ),
+ },
+ { key: "city", header: "المدينة", width: "1.5fr", render: (b) => b.city || "—" },
+ {
+ key: "phone",
+ header: "رقم الجوال",
+ width: "1.5fr",
+ render: (b) => (
+
+ {b.phone ?? "—"}
+
+ ),
+ },
+ {
+ key: "status",
+ header: "الحالة",
+ width: "1fr",
+ render: (b) => ,
+ },
+ {
+ key: "createdAt",
+ header: "تاريخ الانشاء",
+ width: "1fr",
+ align: "center",
+ render: (b) =>
+ new Date(b.createdAt).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" }),
+ },
+ ];
+
return (
-
- {/* column headers */}
-
- الفرع
- المدينة
- الهاتف
- الحالة
- تاريخ الإنشاء
- إجراءات
-
-
- {/* loading state */}
- {loading ? (
-
-
- جارٍ التحميل…
-
- ) : branches.length === 0 ? (
- /* empty state */
-
- أضف أول فرع
-
- )
- }
+
+ Add first branch
+
+ )
+ }
+ renderActions={(b) => (
+ onView(b)}
+ onEdit={() => onEdit(b)}
+ onDelete={() => onDelete(b)}
/>
- ) : (
- /* data rows */
-
- {branches.map((b, i) => (
-
-
-
{b.name}
-
{b.street}
-
- {b.city || "—"}
- {b.phone ?? "—"}
-
-
- {new Date(b.createdAt).toLocaleDateString("ar-SA", { year: "numeric", month: "short", day: "numeric" })}
-
-
- {/* view */}
-
onView(b)}>
-
-
-
-
-
- {/* edit */}
-
onEdit(b)}>
-
-
-
-
-
- {/* delete */}
-
onDelete(b)}>
-
-
-
-
-
-
-
- ))}
-
)}
-
- {/* pagination */}
- {pages > 1 && (
-
-
- صفحة {page} من {pages}
-
-
- {[
- { label: "السابق", action: () => onPageChange(Math.max(1, page - 1)), disabled: page === 1 },
- { label: "التالي", action: () => onPageChange(Math.min(pages, page + 1)), disabled: page === pages },
- ].map(btn => (
-
- {btn.label}
-
- ))}
-
-
- )}
-
+ />
);
}
\ No newline at end of file
diff --git a/src/Components/Branch/archive/ArchivedBranchDetailModal.tsx b/src/Components/Branch/archive/ArchivedBranchDetailModal.tsx
index bf7c549..9b25f91 100644
--- a/src/Components/Branch/archive/ArchivedBranchDetailModal.tsx
+++ b/src/Components/Branch/archive/ArchivedBranchDetailModal.tsx
@@ -35,10 +35,7 @@ function BranchIcon() {
flexShrink: 0,
boxShadow: "0 4px 12px rgba(234,88,12,.3)",
}}>
-
-
-
-
+
);
}
diff --git a/src/Components/Branch/archive/ArchivedBranchTable.tsx b/src/Components/Branch/archive/ArchivedBranchTable.tsx
index ded557e..f77b41f 100644
--- a/src/Components/Branch/archive/ArchivedBranchTable.tsx
+++ b/src/Components/Branch/archive/ArchivedBranchTable.tsx
@@ -1,121 +1,70 @@
"use client";
-import { Badge, Button, IconBtn, Spinner } from "../../UI";
+// src/Components/Branch/archive/ArchivedBranchTable.tsx
+// MIGRATED to ReusableTable + ActionButtons. View-only, no row click in
+// the original.
+
+import { Badge, ReusableTable, ActionButtons } from "../../UI";
+import type { TableColumn } from "@/src/types/models";
import type { ArchivedBranch } from "@/src/types/branch";
-// ── card / header styles ─────────────────────────────────────────────────────
-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)",
-};
-
-// ── Props ────────────────────────────────────────────────────────────────────
interface ArchivedBranchTableProps {
- branches: ArchivedBranch[];
- loading: boolean;
- search: string;
- page: number;
- pages: number;
- onView: (branch: ArchivedBranch) => void;
+ branches: ArchivedBranch[];
+ loading: boolean;
+ search: string;
+ page: number;
+ pages: number;
+ onView: (branch: ArchivedBranch) => void;
onPageChange: (p: number) => void;
}
-// ── main table ───────────────────────────────────────────────────────────────
export function ArchivedBranchTable({ branches, loading, search, page, pages, onView, onPageChange }: ArchivedBranchTableProps) {
+ const columns: TableColumn[] = [
+ {
+ key: "name",
+ header: "اسم الفرع",
+ width: "2fr",
+ render: (b) => (
+
+
{b.name}
+
{b.street}
+
+ ),
+ },
+ { key: "city", header: "المدينة", width: "1.5fr", render: (b) => b.city || "—" },
+ {
+ key: "status",
+ header: "الحالة",
+ width: "1fr",
+ render: (b) => ,
+ },
+ {
+ key: "createdAt",
+ header: "تاريخ الانشاء",
+ width: "1fr",
+ align: "center",
+ render: (b) => new Date(b.createdAt).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" }),
+ },
+ {
+ key: "updatedAt",
+ header: "تاريخ التعديل",
+ width: "1fr",
+ align: "center",
+ render: (b) => new Date(b.updatedAt).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" }),
+ },
+ ];
+
return (
-
- {/* column headers */}
-
- الفرع
- المدينة
- الحالة
- تاريخ الإنشاء
- آخر تحديث
- إجراءات
-
-
- {/* loading state */}
- {loading ? (
-
-
- جارٍ التحميل…
-
- ) : branches.length === 0 ? (
- /* empty state */
-
-
- {search ? `لا توجد نتائج لـ "${search}"` : "لا توجد فروع في الأرشيف."}
-
-
- ) : (
- /* data rows */
-
- {branches.map((b, i) => (
-
-
-
{b.name}
-
{b.street}
-
- {b.city || "—"}
-
-
- {new Date(b.createdAt).toLocaleDateString("ar-SA", { year: "numeric", month: "short", day: "numeric" })}
-
-
- {new Date(b.updatedAt).toLocaleDateString("ar-SA", { year: "numeric", month: "short", day: "numeric" })}
-
-
- {/* view only — delete/restore not implemented yet */}
-
onView(b)}>
-
-
-
-
-
-
-
- ))}
-
- )}
-
- {/* pagination */}
- {pages > 1 && (
-
-
- صفحة {page} من {pages}
-
-
- onPageChange(Math.max(1, page - 1))}
- disabled={page === 1}
- >
- السابق
-
- onPageChange(Math.min(pages, page + 1))}
- disabled={page === pages}
- >
- التالي
-
-
-
- )}
-
+ onView(b)} />}
+ />
);
}
\ No newline at end of file
diff --git a/src/Components/Branch/archive/ArchivedBranchesModal.tsx b/src/Components/Branch/archive/ArchivedBranchesModal.tsx
index 8abaec8..739d504 100644
--- a/src/Components/Branch/archive/ArchivedBranchesModal.tsx
+++ b/src/Components/Branch/archive/ArchivedBranchesModal.tsx
@@ -12,9 +12,7 @@ interface ArchivedBranchesModalProps {
}
const SearchIcon = (
-
-
-
+
);
export function ArchivedBranchesModal({ onClose }: ArchivedBranchesModalProps) {
diff --git a/src/Components/Car_Maintanance/CarMaintananceDetailPanel.tsx b/src/Components/Car_Maintanance/CarMaintananceDetailPanel.tsx
index 1313df4..d1e2e81 100644
--- a/src/Components/Car_Maintanance/CarMaintananceDetailPanel.tsx
+++ b/src/Components/Car_Maintanance/CarMaintananceDetailPanel.tsx
@@ -1,7 +1,3 @@
-// src/Components/Car_Maintanance/CarMaintananceDetailPanel.tsx
-// CHANGE: replaced CarMaintenanceDeleteModal with ConfirmDialog per Issue 1.
-// Description string rebuilt inline using fmtCost, matching the original
-// modal's wording exactly (see Issue 1 edge-case note on preserving fmtCost formatting).
"use client";
import { useCallback, useEffect, useState } from "react";
@@ -252,9 +248,7 @@ export function CarMaintenanceDetailPanel({ carId, carLabel, onClose, onCarStatu
setFormTarget(null)}
style={{ width: "100%", height: 40, borderRadius: "var(--radius-md)", border: "none", background: "var(--color-brand-600)", fontSize: 13, fontWeight: 700, color: "#FFF", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: 8, fontFamily: "var(--font-sans)" }}>
-
-
-
+
إضافة سجل صيانة
diff --git a/src/Components/Car_Maintanance/archive/ArchivedCarMaintananceTable.tsx b/src/Components/Car_Maintanance/archive/ArchivedCarMaintananceTable.tsx
index db9364d..cb4c3a4 100644
--- a/src/Components/Car_Maintanance/archive/ArchivedCarMaintananceTable.tsx
+++ b/src/Components/Car_Maintanance/archive/ArchivedCarMaintananceTable.tsx
@@ -1,19 +1,11 @@
"use client";
-import { Button, EmptyState, IconBtn, Spinner } from "../../UI";
-import { fmtDateShort, fmtCost } from "@/src/types/carMaintanance";
-import type { CarMaintenance } from "@/src/types/carMaintanance";
-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)",
-};
+
+import { IconBtn, ReusableTable } from "../../UI";
+import { fmtDateShort, fmtCost } from "@/src/types/carMaintanance";
+import type { TableColumn } from "@/src/types/models";
+import type { CarMaintenance } from "@/src/types/carMaintanance";
interface ArchivedCarMaintenanceTableProps {
records: CarMaintenance[];
@@ -30,85 +22,78 @@ interface ArchivedCarMaintenanceTableProps {
export function ArchivedCarMaintenanceTable({
records, loading, search, page, pages, showCar = false, onView, onPageChange,
}: ArchivedCarMaintenanceTableProps) {
- const columns = showCar ? "2fr 1fr 1fr 1fr 1fr 100px" : "2fr 1fr 1fr 1fr 100px";
+ const columns: TableColumn[] = [
+ {
+ key: "reason",
+ header: "سبب الصيانة",
+ width: "2fr",
+ render: (r) => (
+
+
{r.reason}
+
أضيف {fmtDateShort(r.createdAt)}
+
+ ),
+ },
+ // Only shown in the global (cross-car) archive view.
+ ...(showCar
+ ? [{
+ key: "car",
+ header: "المركبة",
+ width: "1fr",
+ render: (r: CarMaintenance) => (
+
+ {r.car ? `${r.car.manufacturer} ${r.car.model}` : "—"}
+
+ ),
+ } as TableColumn]
+ : []),
+ {
+ key: "cost",
+ header: "التكلفة",
+ width: "1fr",
+ render: (r) => (
+
+ {fmtCost(r.cost)}
+
+ ),
+ },
+ { key: "startAt", header: "تاريخ البدء", width: "1fr", render: (r) => (
+ {fmtDateShort(r.startAt)}
+ ),
+ },
+ {
+ key: "endAt",
+ header: "تاريخ الانتهاء",
+ width: "1fr",
+ render: (r) => (
+
+ {fmtDateShort(r.endAt)}
+
+ ),
+ },
+ ];
return (
-
-
- سبب الصيانة
- {showCar && المركبة }
- التكلفة
- تاريخ البدء
- تاريخ الانتهاء
- إجراءات
-
-
- {loading ? (
-
-
- جارٍ التحميل…
-
- ) : records.length === 0 ? (
-
- ) : (
-
- {records.map((r, i) => (
-
-
-
{r.reason}
-
أضيف {fmtDateShort(r.createdAt)}
-
- {showCar && (
-
- {r.car ? `${r.car.manufacturer} ${r.car.model}` : "—"}
-
- )}
-
- {fmtCost(r.cost)}
-
- {fmtDateShort(r.startAt)}
-
- {fmtDateShort(r.endAt)}
-
-
-
onView(r)}>
-
-
-
-
-
-
-
- ))}
-
+
(
+ onView(r)}
+ >
+
+
)}
-
- {pages > 1 && (
-
-
- صفحة {page} من {pages}
-
-
- {[
- { label: "السابق", action: () => onPageChange(Math.max(1, page - 1)), disabled: page === 1 },
- { label: "التالي", action: () => onPageChange(Math.min(pages, page + 1)), disabled: page === pages },
- ].map(btn => (
-
- {btn.label}
-
- ))}
-
-
- )}
-
+ />
);
}
\ No newline at end of file
diff --git a/src/Components/Car_Maintanance/archive/ArchivedCarsMaintananceModal.tsx b/src/Components/Car_Maintanance/archive/ArchivedCarsMaintananceModal.tsx
index 69b00e2..846e9d5 100644
--- a/src/Components/Car_Maintanance/archive/ArchivedCarsMaintananceModal.tsx
+++ b/src/Components/Car_Maintanance/archive/ArchivedCarsMaintananceModal.tsx
@@ -51,9 +51,7 @@ export function ArchivedCarsMaintenanceModal({ carId, carLabel, onClose }: Archi
onChange={e => handleSearch(e.target.value)}
dir="rtl"
icon={
-
-
-
+
}
/>
diff --git a/src/Components/Client/Clienttable.tsx b/src/Components/Client/Clienttable.tsx
index fd198a4..982528e 100644
--- a/src/Components/Client/Clienttable.tsx
+++ b/src/Components/Client/Clienttable.tsx
@@ -1,18 +1,35 @@
"use client";
+// src/Components/Client/Clienttable.tsx
+// MIGRATED to ReusableTable — this is the one table in the project with
+// extra behavior (a click-to-expand detail panel below the row), so it
+// relies on ReusableTable's `renderExpanded` prop (added specifically to
+// cover this case) instead of the plain `renderActions`/`onRowClick` pair
+// every other table uses. AddressBadge and ClientDetailPanel are kept
+// exactly as in the original — purpose-built layouts with no shared-UI
+// equivalent, same rationale as the original file's comments.
-import { useState } from "react";
-import { Button, EmptyState, IconBtn, Spinner } from "../UI";
+import { IconBtn, ReusableTable, Button } from "../UI";
+import type { TableColumn } from "@/src/types/models";
import type { Client } from "@/src/types/client";
// ── Address count badge ────────────────────────────────────────────────────
-function AddressBadge({ count, onClick }: { count: number; onClick: () => void }) {
+function AddressBadge({
+ count,
+ onClick,
+}: {
+ count: number;
+ onClick: () => void;
+}) {
const hasAddr = count > 0;
return (
{ e.stopPropagation(); onClick(); }}
- title="إدارة العناوين"
+ onClick={(e) => {
+ e.stopPropagation();
+ onClick();
+ }}
+ title="ادارة العناوين"
style={{
display: "inline-flex",
alignItems: "center",
@@ -29,25 +46,13 @@ function AddressBadge({ count, onClick }: { count: number; onClick: () => void }
transition: "opacity 150ms",
}}
>
- {count} {count === 1 ? "عنوان" : "عناوين"}
- {/* Small arrow icon to hint navigation */}
-
-
-
+ {count} {count === 1 ? "address" : "العناوين"}
+
);
}
-// ── Client detail panel (inline expandable) ────────────────────────────────
-/**
- * ClientDetailPanel — shown below the clicked row.
- * Displays all client fields (name, email, phone, taxId, notes, createdAt)
- * and provides a direct link to the addresses sub-page.
- *
- * @param client The client whose details are rendered.
- * @param onGoAddresses Callback that navigates to /dashboard/clients/[id]/addresses.
- * @param onEdit Opens the edit modal for this client.
- */
+// ── Client detail panel (the expandable content) ───────────────────────────
function ClientDetailPanel({
client,
onGoAddresses,
@@ -85,10 +90,8 @@ function ClientDetailPanel({
return (
- {/* ── Top row: name + quick actions ── */}
- {/* Action buttons */}
- {/* Edit client */}
{ e.stopPropagation(); onEdit(); }}
+ onClick={(e) => {
+ e.stopPropagation();
+ onEdit();
+ }}
>
-
-
-
-
- تعديل البيانات
+
+ تعديل التفاصيل
-
- {/*
- * "Addresses" button — KEY FEATURE:
- * Navigates to /dashboard/clients/[clientId]/addresses
- */}
{ e.stopPropagation(); onGoAddresses(); }}
+ onClick={(e) => {
+ e.stopPropagation();
+ onGoAddresses();
+ }}
>
-
-
-
-
+
العناوين
{client.addresses?.length !== undefined && (
- {/* ── Info grid ── */}
{infoItem(
- "البريد الإلكتروني",
+ "البريد الالكتروني",
e.stopPropagation()}
@@ -207,27 +201,26 @@ function ClientDetailPanel({
{client.email}
,
)}
- {infoItem("الهاتف", client.phone)}
+ {infoItem("رقم الجوال", client.phone)}
{infoItem(
- "تاريخ الإضافة",
- new Date(client.createdAt).toLocaleDateString("ar-SA", {
+ "تاريخ الانشاء",
+ new Date(client.createdAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
}),
)}
{infoItem(
- "آخر تحديث",
- new Date(client.updatedAt).toLocaleDateString("ar-SA", {
+ "اخر تحديث",
+ new Date(client.updatedAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
}),
)}
- {client.notes && infoItem("ملاحظات", client.notes)}
+ {client.notes && infoItem("Notes", client.notes)}
- {/* Keyframe animation injected via a style tag (scoped) */}