diff --git a/app/dashboard/cars/[carId]/maintenance/[maintenanceId]/page.tsx b/app/dashboard/cars/[carId]/maintenance/[maintenanceId]/page.tsx
index 69ab459..9745255 100644
--- a/app/dashboard/cars/[carId]/maintenance/[maintenanceId]/page.tsx
+++ b/app/dashboard/cars/[carId]/maintenance/[maintenanceId]/page.tsx
@@ -1,8 +1,16 @@
"use client";
+import { useCallback, useState } from "react";
import { useParams, useRouter } from "next/navigation";
-import { Alert, PageHeader, Spinner } from "@/src/Components/UI";
-import { useCarMaintenanceList } from "@/src/hooks/UseCarsMaintanance";
+import { Alert, ArchiveButton, Button, ConfirmDialog, EmptyState, PageHeader, Spinner, Toast } from "@/src/Components/UI";
+import { CarMaintenanceFormModal } from "@/src/Components/Car_Maintanance/CarMaintananceFormModal";
+import { ArchivedCarsMaintenanceModal } from "@/src/Components/Car_Maintanance/archive/ArchivedCarsMaintananceModal";
+import {
+ useCarMaintenanceList,
+ useCarMaintenanceMutations,
+ useMaintenanceToast,
+} from "@/src/hooks/UseCarsMaintanance";
+import { useCarDetail } from "@/src/hooks/useCars";
import {
MAINTENANCE_STATUS_MAP,
fmtDate,
@@ -10,92 +18,188 @@ import {
durationDays,
getMaintenanceStatus,
} from "@/src/types/carMaintanance";
+import type {
+ CarMaintenance,
+ CreateMaintenancePayload,
+ UpdateMaintenancePayload,
+} from "@/src/types/carMaintanance";
+
+function MaintenanceCard({
+ record, onView, onEdit, onDelete,
+}: {
+ record: CarMaintenance;
+ onView: () => void;
+ onEdit: () => void;
+ onDelete: () => void;
+}) {
+ const status = MAINTENANCE_STATUS_MAP[getMaintenanceStatus(record)];
-function DetailRow({ label, value, mono = false }: { label: string; value: string; mono?: boolean }) {
return (
-
- {label}
-
- {value}
-
-
+
+
+
+
+ {record.reason}
+
+
+ {status.label}
+
+
+
+
+
+ التكلفة
+ {fmtCost(record.cost)}
+
+
+ المدة
+ {durationDays(record.startAt, record.endAt)} يوم
+
+
+ تاريخ البدء
+ {fmtDate(record.startAt)}
+
+
+ تاريخ الانتهاء
+ {fmtDate(record.endAt)}
+
+
+
+
+
+ إظهار التفاصيل
+
+
+ تعديل
+
+ {/* inline style keeps the original light-red look; Button's own
+ variant="danger" is solid red and would change the visual */}
+
+ أرشفة
+
+
+
+
);
}
-export default function CarMaintenanceDetailPage() {
- const { carId, maintenanceId } = useParams<{ carId: string; maintenanceId: string }>();
+export default function CarMaintenancePage() {
+ const { carId } = useParams<{ carId: string }>();
const router = useRouter();
- // NOTE: GET /cars/:carId/maintenance/:maintenanceId isn't wired up on the
- // backend yet (returns "Route not found"), so this page resolves the
- // record from the already-loaded list instead of a dedicated single-record
- // call. includeDeleted: true so an archived record still resolves to a
- // real "مؤرشفة" status instead of silently disappearing.
- const { records, loading, error } = useCarMaintenanceList(carId, { includeDeleted: true });
- const record = records.find((r) => r.id === maintenanceId) ?? null;
+ const { car, refetch: refetchCar } = useCarDetail(carId);
+ const carLabel = car ? `${car.manufacturer} ${car.model} — ${car.plateLetters} ${car.plateNumber}` : undefined;
+
+ const { records, loading, error, loadRecords, removeRecord, setError } = useCarMaintenanceList(carId);
+ const { toast, notify } = useMaintenanceToast();
+
+ const [formTarget, setFormTarget] = useState(false); // false = closed
+ const [deleteTarget, setDeleteTarget] = useState(null);
+ const [archiveOpen, setArchiveOpen] = useState(false);
+
+ const getEditTarget = useCallback(
+ () => (formTarget instanceof Object && formTarget !== null ? (formTarget as CarMaintenance) : null),
+ [formTarget],
+ );
+
+ const { deleting, handleFormSubmit, handleDeleteConfirm } = useCarMaintenanceMutations({
+ carId,
+ onSuccess: (msg) => { notify({ type: "success", message: msg }); loadRecords(); refetchCar(); },
+ onError: (msg) => notify({ type: "error", message: msg }),
+ onDeleted: (id) => { removeRecord(id); setDeleteTarget(null); refetchCar(); },
+ getEditTarget,
+ });
return (
-
-
+
+
+ {formTarget !== false && (
+ setFormTarget(false)}
+ onSubmit={(payload: CreateMaintenancePayload | UpdateMaintenancePayload, isNew: boolean) =>
+ handleFormSubmit(payload, isNew).then(ok => { if (ok) setFormTarget(false); return ok; })
+ }
+ />
+ )}
+
+ setDeleteTarget(null)}
+ onConfirm={() => deleteTarget && handleDeleteConfirm(deleteTarget)}
+ title="حذف سجل الصيانة"
+ description={`هل أنت متأكد من حذف سجل ${deleteTarget?.reason ?? ""} (${deleteTarget ? fmtCost(deleteTarget.cost) : ""})؟ لا يمكن التراجع عن هذا الإجراء.`}
/>
-
- {loading && (
-
-
-
جارٍ التحميل…
+ {archiveOpen && (
+
setArchiveOpen(false)}
+ />
+ )}
+
+
+ setFormTarget(null)}>
+
+
+
+ إضافة سجل صيانة
+
+ }
+ />
+
+ {error && setError(null)} />}
+
+ {loading ? (
+
+
+ جارٍ تحميل سجلات الصيانة…
+
+ ) : records.length === 0 ? (
+
+ ) : (
+
+ {records.map(record => (
+ router.push(`/dashboard/cars/${carId}/maintenance/${record.id}`)}
+ onEdit={() => setFormTarget(record)}
+ onDelete={() => setDeleteTarget(record)}
+ />
+ ))}
)}
+
- {error && }
-
- {!loading && !error && !record && (
-
- لم يتم العثور على سجل الصيانة.
-
- )}
-
- {!loading && !error && record && (() => {
- const status = MAINTENANCE_STATUS_MAP[getMaintenanceStatus(record)];
- return (
- <>
-
-
- {status.label}
-
-
-
-
-
-
-
-
- {record.car && (
-
- )}
-
-
- >
- );
- })()}
-
-
-
- router.push(`/dashboard/cars/${carId}/maintenance`)}
- style={{ height: 40, padding: "0 1.5rem", borderRadius: "var(--radius-md)", border: "1px solid var(--color-border)", background: "var(--color-surface)", fontSize: 13, fontWeight: 600, color: "var(--color-text-secondary)", cursor: "pointer", fontFamily: "var(--font-sans)" }}>
- العودة
-
-
-
+
setArchiveOpen(true)} label="أرشيف الصيانة" />
+ >
);
}
\ No newline at end of file
diff --git a/app/dashboard/cars/page.tsx b/app/dashboard/cars/page.tsx
index 709579d..75a2319 100644
--- a/app/dashboard/cars/page.tsx
+++ b/app/dashboard/cars/page.tsx
@@ -1,46 +1,17 @@
"use client";
-
import { useCallback, useState } from "react";
-import { Spinner, Alert, ArchiveButton, ConfirmDialog } from "@/src/Components/UI";
+import { Spinner, Alert, ArchiveButton, ConfirmDialog, Toast, EmptyState, Button } from "@/src/Components/UI";
import { CarFormModal,CarDetailPanel } from "@/src/Components/car";
import { ArchivedCarsModal } from "@/src/Components/car/archive/Archivedcarsmodal";
import { CarMaintenanceFormModal } from "@/src/Components/Car_Maintanance/CarMaintananceFormModal";
import { useCars, useCarMutations, useToast } from "@/src/hooks/useCars";
import { useCarMaintenanceMutations } from "@/src/hooks/UseCarsMaintanance";
import { fmtDateShort, isExpiringSoon, STATUS_MAP, INS_MAP } from "@/src/types/car";
-import type { Car, CreateCarPayload, ToastMsg, UpdateCarPayload } from "@/src/types/car";
+import type { Car, CreateCarPayload, UpdateCarPayload } from "@/src/types/car";
import type { CreateMaintenancePayload, UpdateMaintenancePayload } from "@/src/types/carMaintanance";
import Header from "@/src/Components/UI/Header";
-// ── Toast ─────────────────────────────────────────────────────────────────────
-
-function CarToast({ notification }: { notification: ToastMsg | null }) {
- if (!notification) return null;
- const ok = notification.type === "success";
- return (
-
-
- {ok ? "✓" : "⚠"}
- {notification.message}
-
-
- );
-}
-
// ── CarCard ───────────────────────────────────────────────────────────────────
function CarCard({
@@ -172,7 +143,11 @@ function CarCard({
- {/* Send to Maintenance quick action */}
+ {/* Send to Maintenance quick action — kept custom (amber accent isn't
+ one of Button's variants: primary/secondary/ghost/danger). Same
+ rationale as IconBtn: forcing it into an existing variant would
+ misrepresent this as a routine action rather than the
+ maintenance-triggering one it is. */}
-
+
{detailId && (
جارٍ تحميل المركبات…
) : cars.length === 0 ? (
-
-
🚗
-
- {search ? `لا توجد مركبات تطابق "${search}"` : "لا توجد مركبات بعد"}
-
-
- اضغط على "إضافة مركبة" لإضافة أول مركبة في الأسطول.
-
- {!search && (
-
setFormTarget(null)}
- style={{
- marginTop: 16, height: 40, padding: "0 1.5rem",
- borderRadius: "var(--radius-lg)",
- border: "none", background: "var(--color-brand-600)",
- fontSize: 13, fontWeight: 700, color: "#FFF",
- cursor: "pointer", fontFamily: "var(--font-sans)",
- }}
- >
+ setFormTarget(null)}>
إضافة مركبة
-
+
)}
-
+ />
) : (
/* ── Card grid ── */
setPage(p => Math.max(1, p - 1)), disabled: page === 1 },
{ label: "التالي →", action: () => setPage(p => Math.min(pages, p + 1)), disabled: page === pages },
].map((btn) => (
-
{btn.label}
-
+
))}
diff --git a/app/dashboard/drivers/[driverId]/page.tsx b/app/dashboard/drivers/[driverId]/page.tsx
index 7484f7a..d132014 100644
--- a/app/dashboard/drivers/[driverId]/page.tsx
+++ b/app/dashboard/drivers/[driverId]/page.tsx
@@ -2,7 +2,7 @@
import { useCallback, useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
-import { ConfirmDialog, Spinner } from "@/src/Components/UI";
+import { ConfirmDialog, Spinner, Button, Toast } from "@/src/Components/UI";
import { DriverFormModal , PhotoCard } from "@/src/Components/Driver";
import { DriverReportPanel } from "@/src/Components/Driver_Report/driverReport";
import type { Driver, CreateDriverPayload, UpdateDriverPayload } from "@/src/types/driver";
@@ -164,14 +164,9 @@ export default function DriverDetailPage() {
return (
⚠ {error ?? "السائق غير موجود"}
-
router.back()} style={{
- marginTop: "1rem", height: 38, padding: "0 1.25rem",
- borderRadius: "var(--radius-md)", border: "1px solid var(--color-border)",
- background: "var(--color-surface)", fontSize: 13, fontWeight: 600,
- color: "var(--color-text-secondary)", cursor: "pointer", fontFamily: "var(--font-sans)",
- }}>
+ router.back()} className="mt-4">
← رجوع
-
+
);
}
@@ -180,35 +175,18 @@ export default function DriverDetailPage() {
<>
- {toast && (
-
- {toast.type === "success" ? "✓ " : "⚠ "}{toast.message}
-
- )}
+ setToast(null)} />
diff --git a/app/dashboard/drivers/page.tsx b/app/dashboard/drivers/page.tsx
index d9a24a2..ba8b90a 100644
--- a/app/dashboard/drivers/page.tsx
+++ b/app/dashboard/drivers/page.tsx
@@ -7,6 +7,8 @@ import {
ArchiveButton,
ConfirmDialog,
Toast,
+ Button,
+ IconBtn,
} from "@/src/Components/UI";
import { ArchivedDrivers } from "@/src/Components/Driver/archive/ArchivedDrivers";
import { useDrivers } from "@/src/hooks/useDriver";
@@ -59,17 +61,6 @@ const thStyle: React.CSSProperties = {
// Shared across header + rows — keep these in sync or columns will misalign.
const ROW_GRID_COLUMNS = "2fr 1.2fr 1fr 1.1fr 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 DriversPage() {
@@ -346,24 +337,16 @@ export default function DriversPage() {
{/* ── Inline row actions: edit / delete ──
- stopPropagation is required on both buttons so the
- click doesn't bubble up to the onClick and
+ IconBtn already calls stopPropagation internally so
+ the click doesn't bubble up to the onClick and
open the detail panel as well. */}
-
{
- e.stopPropagation();
- handleEdit(d);
- }}
- style={{
- ...iconBtnBase,
- border: "1px solid var(--color-brand-200)",
- background: "var(--color-brand-50, #EFF6FF)",
- color: "var(--color-brand-600)",
- }}
+ color="var(--color-brand-600)"
+ bg="var(--color-brand-50, #EFF6FF)"
+ borderColor="var(--color-brand-200)"
+ onClick={() => handleEdit(d)}
>
-
+
-
{
- e.stopPropagation();
- handleDelete(d);
- }}
- style={{
- ...iconBtnBase,
- border: "1px solid #FECACA",
- background: "#FEF2F2",
- color: "#DC2626",
- }}
+ color="#DC2626"
+ bg="#FEF2F2"
+ borderColor="#FECACA"
+ onClick={() => handleDelete(d)}
>
-
+
);
@@ -437,37 +412,22 @@ export default function DriversPage() {
- {[
- {
- 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) => (
-
- {btn.label}
-
- ))}
+ setPage((p) => Math.max(1, p - 1))}
+ disabled={page === 1}
+ >
+ السابق
+
+ setPage((p) => Math.min(pages, p + 1))}
+ disabled={page === pages}
+ >
+ التالي
+
)}
diff --git a/app/dashboard/trips/[tripId]/page.tsx b/app/dashboard/trips/[tripId]/page.tsx
index 7df8f52..77855ab 100644
--- a/app/dashboard/trips/[tripId]/page.tsx
+++ b/app/dashboard/trips/[tripId]/page.tsx
@@ -11,7 +11,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useParams, useRouter } from "next/navigation";
-import { ConfirmDialog, Spinner } from "@/src/Components/UI";
+import { Button, ConfirmDialog, Spinner } from "@/src/Components/UI";
import { TripFormModal } from "@/src/Components/Trip";
import { TripReportPanel } from "@/src/Components/Trip_Report/Tripreportpanel";
import { tripService } from "@/src/services/trip.service";
@@ -304,25 +304,9 @@ export default function TripDetailPage() {
⚠ {error ?? "الرحلة غير موجودة"}
- router.back()}
- style={{
- marginTop: "1rem",
- height: 38,
- padding: "0 1.25rem",
- borderRadius: "var(--radius-md)",
- border: "1px solid var(--color-border)",
- background: "var(--color-surface)",
- fontSize: 13,
- fontWeight: 600,
- color: "var(--color-text-secondary)",
- cursor: "pointer",
- fontFamily: "var(--font-sans)",
- }}
- >
+ router.back()} style={{ marginTop: "1rem" }}>
← رجوع
-
+
);
}
@@ -346,29 +330,17 @@ export default function TripDetailPage() {
}}
>
{/* Back link */}
- router.back()}
- style={{
- display: "inline-flex",
- alignItems: "center",
- gap: 6,
- fontSize: 12,
- fontWeight: 600,
- color: "var(--color-text-muted)",
- background: "none",
- border: "none",
- cursor: "pointer",
- padding: 0,
- marginBottom: "1rem",
- fontFamily: "var(--font-sans)",
- }}
+ style={{ height: "auto", padding: 0, background: "none", marginBottom: "1rem" }}
>
العودة إلى قائمة الرحلات
-
+
{/* Icon + title */}
diff --git a/src/Components/UI/Header.tsx b/src/Components/UI/Header.tsx
index 37a1520..880fd9b 100644
--- a/src/Components/UI/Header.tsx
+++ b/src/Components/UI/Header.tsx
@@ -1,3 +1,7 @@
+import { Button } from "./Button";
+import { Input } from "./Input";
+import { Select } from "./Select";
+
export default function Header({
state,
search,
@@ -75,50 +79,30 @@ export default function Header({
display: "flex",
gap: "0.5rem",
flexWrap: "wrap",
- alignItems: "center",
+ alignItems: "flex-end",
}}
>
- {
setSearch(e.target.value);
setPage(1);
}}
dir="rtl"
- style={{
- width: 220,
- height: 40,
- padding: "0 0.75rem",
- 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)",
- }}
+ className="w-[220px]"
/>
{isAudit ? (
- {
setModule(e.target.value);
setPage(1);
}}
dir="rtl"
- style={{
- height: 40,
- padding: "0 0.75rem",
- borderRadius: "var(--radius-lg)",
- border: "1px solid var(--color-border)",
- background: "var(--color-surface)",
- fontSize: 13,
- color: "var(--color-text-secondary)",
- outline: "none",
- fontFamily: "var(--font-sans)",
- }}
+ className="w-[180px]"
>
كل الوحدات
{[
@@ -135,29 +119,9 @@ export default function Header({
{m}
))}
-
+
) : (
-
+
إضافة {name}
-
+
)}