fix: resolve permission fallback bug, remove debug code, and cleanup duplication
Logic Errors ------------ - Sidebar: fix permissions fallback so a user with no permissions gets zero access instead of falling back to the full nav list (user?.permissions ?? [] instead of ?? navSections.flatMap(...)) - carMaintanance.service / UseCarsMaintanance: add pagination to the maintenance records fetch instead of loading the entire list at once - api.ts: redact sensitive fields (password, token) before logging the request body in console.debug, to avoid leaking credentials/PII Code Flow Problems ------------------- - OrderFormModal: remove leftover console.log/debug statements and the redundant onClick handler on the submit button - auth: remove the unused src/service/auth.service.ts (axios) and keep only src/services/auth.service.ts (fetch); fix useAuth.ts import path from
This commit is contained in:
@@ -1,12 +1,21 @@
|
||||
// app/components/layout/ConditionalNavbar.tsx
|
||||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import { getStoredUser } from "@/src/lib/auth";
|
||||
import { useStoredUser } from "@/src/hooks/useStoredUser";
|
||||
import Navbar from "./Navbar";
|
||||
|
||||
export default function ConditionalNavbar() {
|
||||
const pathname = usePathname();
|
||||
const user = getStoredUser();
|
||||
|
||||
// استبدلنا getStoredUser() المباشر بالـ hook عشان يبقى SSR-safe
|
||||
const { user, loading } = useStoredUser();
|
||||
|
||||
// لحد ما نتأكد من حالة تسجيل الدخول من الـ storage، منوريش/مخفيش
|
||||
// الناف بار عشان نتجنب وميض (flash) قبل الـ hydration يخلص. مفيش
|
||||
// حاجة تتعرض هنا أصلاً (مفيش avatar/اسم في الناف بار العام)، فـ null
|
||||
// كافية ومفيش داعي لـ Spinner/InlineLoader في المكان ده.
|
||||
if (loading) return null;
|
||||
|
||||
// لو المستخدم مسجل دخول، أو الصفحة الحالية جوه الداشبورد، منظهرش الناف بار العام
|
||||
const isDashboardRoute = pathname?.startsWith("/dashboard");
|
||||
|
||||
@@ -363,6 +363,7 @@ function SidebarContent({
|
||||
pathname.startsWith(item.href + "/"));
|
||||
return (
|
||||
<Link
|
||||
suppressHydrationWarning
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
title={compact ? item.label : undefined}
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
// app/components/layout/Topbar.tsx
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { clearAuth, getStoredUser } from "@/src/lib/auth";
|
||||
import { clearAuth } from "@/src/lib/auth";
|
||||
import { useStoredUser } from "@/src/hooks/useStoredUser";
|
||||
import { Spinner } from "@/src/Components/UI/Spinner";
|
||||
import { useSidebarDrawer, BrandIconButton } from "./Sidebar";
|
||||
|
||||
export function Topbar() {
|
||||
const router = useRouter();
|
||||
const { setOpen } = useSidebarDrawer();
|
||||
|
||||
// كان بيتقرأ بشكل sync عن طريق getStoredUser() جوه الرندر — استبدلناه بالـ hook
|
||||
// عشان يبقى SSR-safe ومايعملش hydration mismatch، وكمان يعمل subscribe
|
||||
// لأي تغيير في الـ storage (تسجيل دخول/خروج من تاب تاني).
|
||||
const { user, loading } = useStoredUser();
|
||||
|
||||
async function handleLogout() {
|
||||
await clearAuth();
|
||||
router.replace("/login");
|
||||
}
|
||||
|
||||
const user = getStoredUser();
|
||||
|
||||
return (
|
||||
<header
|
||||
suppressHydrationWarning
|
||||
style={{
|
||||
height: "var(--topbar-height)",
|
||||
background: "#FFFFFF",
|
||||
@@ -52,18 +57,33 @@ export function Topbar() {
|
||||
حسابي
|
||||
</button>
|
||||
|
||||
{/*
|
||||
Loading guard: InlineLoader is meant for card/section-level loading
|
||||
(py-4 + message text) and doesn't fit a small inline pill here, so
|
||||
we use the same underlying <Spinner size="sm" /> the project already
|
||||
ships, just placed inside the existing badge shell.
|
||||
*/}
|
||||
<div
|
||||
suppressHydrationWarning
|
||||
role="status"
|
||||
aria-label={loading ? "جارِ التحميل" : undefined}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: 12,
|
||||
color: "var(--color-text-secondary)",
|
||||
background: "var(--color-surface-muted)",
|
||||
border: "1px solid var(--color-border)",
|
||||
borderRadius: "var(--radius-full)",
|
||||
padding: "4px 12px",
|
||||
padding: loading ? "4px 10px" : "4px 12px",
|
||||
minWidth: 40,
|
||||
}}
|
||||
>
|
||||
{user?.role ?? "—"}
|
||||
{loading ? (
|
||||
<Spinner size="sm" className="text-[var(--color-text-muted)]" />
|
||||
) : (
|
||||
user?.role ?? "—"
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Alert, Toast, ArchiveButton } from "@/src/Components/UI";
|
||||
import { BranchTable } from "@/src/Components/Branch/BranchTable";
|
||||
import { BranchFormModal } from "@/src/Components/Branch/BranchFormModal";
|
||||
import { BranchDetailModal } from "@/src/Components/Branch/BranchDetailModal";
|
||||
import { DeleteConfirmModal } from "@/src/Components/Branch/DeleteConfirmModal";
|
||||
import { Alert, Toast, ArchiveButton ,ConfirmDialog } from "@/src/Components/UI";
|
||||
import { BranchDetailModal, BranchTable ,BranchFormModal } from "@/src/Components/Branch";
|
||||
import { useBranches } from "@/src/hooks/useBranch";
|
||||
import type { Branch, BranchFormData } from "@/src/types/branch";
|
||||
import { ArchivedBranchesModal } from "@/src/Components/Branch/archive/ArchivedBranchesModal";
|
||||
@@ -74,16 +71,14 @@ export default function BranchesPage() {
|
||||
)}
|
||||
|
||||
{/* Delete confirmation dialog */}
|
||||
{deleteTarget && (
|
||||
<DeleteConfirmModal
|
||||
branch={deleteTarget}
|
||||
deleting={deleting}
|
||||
onCancel={() => {
|
||||
if (!deleting) setDeleteTarget(null);
|
||||
}}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
loading={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
title="حذف الفرع"
|
||||
description={`هل أنت متأكد من حذف الفرع ${deleteTarget?.name ?? ""}؟ لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
{/* Archive browser modal */}
|
||||
{archiveOpen && (
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { Alert, ArchiveButton, PageHeader, Spinner, Toast } from "@/src/Components/UI";
|
||||
import { Alert, ArchiveButton, ConfirmDialog, PageHeader, Spinner, Toast } from "@/src/Components/UI";
|
||||
import { CarMaintenanceFormModal } from "@/src/Components/Car_Maintanance/CarMaintananceFormModal";
|
||||
import { CarMaintenanceDeleteModal } from "@/src/Components/Car_Maintanance/CarMaintananceDeleteModal";
|
||||
import { ArchivedCarsMaintenanceModal } from "@/src/Components/Car_Maintanance/archive/ArchivedCarsMaintananceModal";
|
||||
import {
|
||||
useCarMaintenanceList,
|
||||
@@ -135,14 +134,14 @@ export default function CarMaintenancePage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{deleteTarget && (
|
||||
<CarMaintenanceDeleteModal
|
||||
record={deleteTarget}
|
||||
deleting={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={() => handleDeleteConfirm(deleteTarget)}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
loading={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={() => deleteTarget && handleDeleteConfirm(deleteTarget)}
|
||||
title="حذف سجل الصيانة"
|
||||
description={`هل أنت متأكد من حذف سجل ${deleteTarget?.reason ?? ""} (${deleteTarget ? fmtCost(deleteTarget.cost) : ""})؟ لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
{archiveOpen && (
|
||||
<ArchivedCarsMaintenanceModal
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { Spinner, Alert, ArchiveButton } from "@/src/Components/UI";
|
||||
import { CarFormModal } from "@/src/Components/car/CarFormModal";
|
||||
import { CarDetailPanel } from "@/src/Components/car/CarDetailPanel";
|
||||
import { CarDeleteModal } from "@/src/Components/car/CarDeleteModal";
|
||||
import { Spinner, Alert, ArchiveButton, ConfirmDialog } 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";
|
||||
@@ -283,14 +281,14 @@ export default function CarsPage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{deleteTarget && (
|
||||
<CarDeleteModal
|
||||
car={deleteTarget}
|
||||
deleting={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={handleDelete}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
loading={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={handleDelete}
|
||||
title="حذف المركبة"
|
||||
description={`هل أنت متأكد من حذف ${deleteTarget?.manufacturer ?? ""} ${deleteTarget?.model ?? ""} (${deleteTarget?.plateLetters ?? ""} ${deleteTarget?.plateNumber ?? ""})؟ لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
{maintenanceTarget && (
|
||||
<CarMaintenanceFormModal
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
"use client";
|
||||
|
||||
import { 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 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";
|
||||
|
||||
// ─── Page ──────────────────────────────────────────────────────────────────
|
||||
|
||||
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<ClientAddress | null>(null);
|
||||
const [client, setClient] = useState<Client | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// ── Modal state ──────────────────────────────────────────────────────────
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
|
||||
// ── Toast ────────────────────────────────────────────────────────────────
|
||||
const [notification, setNotification] = useState<ToastNotification | null>(null);
|
||||
|
||||
const notify = (n: ToastNotification) => {
|
||||
setNotification(n);
|
||||
setTimeout(() => setNotification(null), 4000);
|
||||
};
|
||||
|
||||
// ── Fetch ────────────────────────────────────────────────────────────────
|
||||
useEffect(() => {
|
||||
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));
|
||||
}, [clientId, addressId]);
|
||||
|
||||
// ── Update ───────────────────────────────────────────────────────────────
|
||||
const handleUpdateSubmit = async (
|
||||
data: CreateAddressFormValues | UpdateAddressFormValues
|
||||
): Promise<boolean> => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// ── 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 ───────────────────────────────────────────────────────────────
|
||||
if (loading) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minHeight: "60vh",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: "50%",
|
||||
border: "3px solid var(--color-border)",
|
||||
borderTopColor: "var(--color-brand-600)",
|
||||
animation: "spin 0.7s linear infinite",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !address) {
|
||||
return (
|
||||
<section style={{ padding: "2rem", display: "flex", flexDirection: "column", gap: "1rem" }}>
|
||||
<Alert type="error" message={error ?? "لم يتم العثور على العنوان."} />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
color: "var(--color-brand-600)",
|
||||
background: "none",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
fontFamily: "var(--font-sans)",
|
||||
}}
|
||||
>
|
||||
← رجوع
|
||||
</button>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Render ────────────────────────────────────────────────────────────────
|
||||
return (
|
||||
<>
|
||||
<Toast notification={notification} />
|
||||
|
||||
{/* Edit modal */}
|
||||
{editOpen && (
|
||||
<AddressFormModal
|
||||
editAddress={address}
|
||||
onClose={() => setEditOpen(false)}
|
||||
onSubmit={handleUpdateSubmit}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Delete confirmation */}
|
||||
<ConfirmDialog
|
||||
open={deleteOpen}
|
||||
loading={deleting}
|
||||
onCancel={() => { if (!deleting) setDeleteOpen(false); }}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
title="حذف العميل"
|
||||
description={`هل أنت متأكد من حذف ${address.label}؟ سيتم حذف جميع عناوينه أيضاً. لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
<section style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
||||
|
||||
{/* ── Page header ── */}
|
||||
<header
|
||||
style={{
|
||||
borderRadius: "var(--radius-xl)",
|
||||
border: "1px solid var(--color-border)",
|
||||
background: "var(--color-surface)",
|
||||
padding: "1.5rem 2rem",
|
||||
boxShadow: "var(--shadow-card)",
|
||||
}}
|
||||
>
|
||||
{/* Back link */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push(`/dashboard/clients/${clientId}/addresses`)}
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: "var(--color-text-muted)",
|
||||
background: "none",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
marginBottom: 12,
|
||||
fontFamily: "var(--font-sans)",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</svg>
|
||||
{client ? `${client.name} — العناوين` : "العناوين"}
|
||||
</button>
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontSize: 11,
|
||||
letterSpacing: "0.3em",
|
||||
textTransform: "uppercase",
|
||||
color: "#2563EB",
|
||||
fontWeight: 600,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
تفاصيل العنوان
|
||||
</p>
|
||||
|
||||
<div className="mt-3 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<h1
|
||||
style={{
|
||||
fontSize: "1.5rem",
|
||||
fontWeight: 700,
|
||||
color: "var(--color-text-primary)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{address.label}
|
||||
</h1>
|
||||
{client && (
|
||||
<p style={{ marginTop: "0.25rem", fontSize: 13, color: "var(--color-text-muted)" }}>
|
||||
{client.name}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action buttons */}
|
||||
<div style={{ display: "flex", gap: "0.5rem" }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditOpen(true)}
|
||||
style={{
|
||||
height: 40,
|
||||
padding: "0 1rem",
|
||||
borderRadius: "var(--radius-lg)",
|
||||
border: "1px solid #BFDBFE",
|
||||
background: "#EFF6FF",
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
color: "#1D4ED8",
|
||||
cursor: "pointer",
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: 7,
|
||||
fontFamily: "var(--font-sans)",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||
</svg>
|
||||
تعديل العنوان
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDeleteOpen(true)}
|
||||
style={{
|
||||
height: 40,
|
||||
padding: "0 1rem",
|
||||
borderRadius: "var(--radius-lg)",
|
||||
border: "1px solid #FECACA",
|
||||
background: "#FEF2F2",
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
color: "#DC2626",
|
||||
cursor: "pointer",
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: 7,
|
||||
fontFamily: "var(--font-sans)",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<polyline points="3 6 5 6 21 6" />
|
||||
<path d="M19 6l-1 14H6L5 6" />
|
||||
<path d="M10 11v6M14 11v6" />
|
||||
<path d="M9 6V4h6v2" />
|
||||
</svg>
|
||||
حذف
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* ── Address details component ── */}
|
||||
<AddressDetails address={address} />
|
||||
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
|
||||
import { Alert, 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";
|
||||
@@ -12,7 +12,7 @@ import { getStoredToken } from "@/src/lib/auth";
|
||||
import type { Client, ClientFormData } from "@/src/types/client";
|
||||
import type { ClientAddress } from "@/src/types/client_adresses";
|
||||
|
||||
import { AddressFormModal, DeleteConfirmModal, ClientFormModal } from "@/src/Components/Client";
|
||||
import { AddressFormModal, ClientFormModal } from "@/src/Components/Client";
|
||||
import { ArchivedClientsAddressesModal } from "@/src/Components/Client_Adress/archive/ArchivedClientsAddressesModal";
|
||||
import type {
|
||||
CreateAddressFormValues,
|
||||
@@ -443,23 +443,14 @@ export default function ClientAddressesPage() {
|
||||
)}
|
||||
|
||||
{/* Delete confirmation */}
|
||||
{deleteTarget && (
|
||||
<DeleteConfirmModal
|
||||
client={
|
||||
{
|
||||
...deleteTarget,
|
||||
name: deleteTarget.label,
|
||||
email: "",
|
||||
phone: "",
|
||||
createdAt: deleteTarget.createdAt,
|
||||
updatedAt: deleteTarget.updatedAt,
|
||||
} as Client
|
||||
}
|
||||
deleting={deleting}
|
||||
onCancel={() => { if (!deleting) setDeleteTarget(null); }}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
loading={deleting}
|
||||
onCancel={() => { if (!deleting) setDeleteTarget(null); }}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
title="حذف العميل"
|
||||
description={`هل أنت متأكد من حذف ${deleteTarget?.label ?? ""}؟ سيتم حذف جميع عناوينه أيضاً. لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
{/* Client edit modal */}
|
||||
{editingClient && client && (
|
||||
|
||||
@@ -6,14 +6,12 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
// ── UI components ──────────────────────────────────────────────────────────
|
||||
// NOTE: Toast is now imported from the canonical UI barrel, NOT from Client/Toast.
|
||||
import { Alert, Toast, ArchiveButton } from "@/src/Components/UI";
|
||||
import { Alert, Toast, ArchiveButton, ConfirmDialog } from "@/src/Components/UI";
|
||||
|
||||
// ── Client-specific components ─────────────────────────────────────────────
|
||||
import { useClients } from "@/src/hooks/useClients";
|
||||
import type { Client, ClientFormData } from "@/src/types/client";
|
||||
import { ClientFormModal } from "@/src/Components/Client/Clientformmodal";
|
||||
import { ClientTable } from "@/src/Components/Client/Clienttable";
|
||||
import { DeleteConfirmModal } from "@/src/Components/Client/Deleteconfirmmodal";
|
||||
import { ClientFormModal , ClientTable } from "@/src/Components/Client";
|
||||
import { ArchivedClientsModal } from "@/src/Components/Client/archive/ArchivedClientsModal";
|
||||
|
||||
|
||||
@@ -82,14 +80,14 @@ export default function ClientsPage() {
|
||||
)}
|
||||
|
||||
{/* Delete confirmation dialog */}
|
||||
{deleteTarget && (
|
||||
<DeleteConfirmModal
|
||||
client={deleteTarget}
|
||||
deleting={deleting}
|
||||
onCancel={() => { if (!deleting) setDeleteTarget(null); }}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
loading={deleting}
|
||||
onCancel={() => { if (!deleting) setDeleteTarget(null); }}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
title="حذف العميل"
|
||||
description={`هل أنت متأكد من حذف ${deleteTarget?.name ?? ""}؟ سيتم حذف جميع عناوينه أيضاً. لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
{/* Archive browser modal */}
|
||||
{archiveOpen && (
|
||||
@@ -258,4 +256,4 @@ export default function ClientsPage() {
|
||||
<ArchiveButton onClick={() => setArchiveOpen(true)} label="أرشيف العملاء" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,14 @@
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { Spinner } from "@/src/Components/UI";
|
||||
import { DriverFormModal } from "@/src/Components/Driver/DriverFormModal";
|
||||
import { DriverDeleteModal } from "@/src/Components/Driver/DriverDeleteModal";
|
||||
import { ConfirmDialog, Spinner } 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";
|
||||
import { DRIVER_STATUS_MAP, DRIVER_CARD_TYPE_MAP, NATIONAL_ID_TYPE_MAP } from "@/src/types/driver";
|
||||
import { PhotoCard } from "@/src/Components/Driver/DriverPhotos";
|
||||
import { driverService } from "@/src/services";
|
||||
import { getStoredToken } from "@/src/lib/auth";
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function fmtDate(iso?: string | null): string {
|
||||
if (!iso) return "—";
|
||||
return new Date(iso).toLocaleDateString("ar-SA", {
|
||||
@@ -26,8 +22,6 @@ function isExpiringSoon(iso?: string | null): boolean {
|
||||
return (new Date(iso).getTime() - Date.now()) <= 90 * 86_400_000;
|
||||
}
|
||||
|
||||
// ── Sub-components ────────────────────────────────────────────────────────────
|
||||
|
||||
function SectionCard({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div style={{
|
||||
@@ -73,10 +67,6 @@ function DetailRow({ label, value, mono = false, warn = false }: {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ── Page ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function DriverDetailPage() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
@@ -86,11 +76,9 @@ export default function DriverDetailPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [avatarError, setAvatarError] = useState(false);
|
||||
// Reset avatar error when photo changes after an update
|
||||
useEffect(() => {
|
||||
queueMicrotask(() => setAvatarError(false));
|
||||
}, [driver?.photoUrl]);
|
||||
// Toast shown after edit/delete actions on this page
|
||||
const [toast, setToast] = useState<{ type: "success" | "error"; message: string } | null>(null);
|
||||
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
@@ -102,15 +90,14 @@ export default function DriverDetailPage() {
|
||||
setTimeout(() => setToast(null), 4000);
|
||||
}, []);
|
||||
|
||||
// ── Load driver ───────────────────────────────────────────────────────────
|
||||
const loadDriver = useCallback(async () => {
|
||||
if (!driverId) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const token = getStoredToken();
|
||||
const res = await driverService.getById(driverId, token);
|
||||
setDriver((res as unknown as { data: Driver }).data);
|
||||
const data = await driverService.getById(driverId, token);
|
||||
setDriver(data);
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : "تعذّر تحميل بيانات السائق.");
|
||||
} finally {
|
||||
@@ -120,7 +107,6 @@ export default function DriverDetailPage() {
|
||||
|
||||
useEffect(() => { queueMicrotask(loadDriver); }, [loadDriver]);
|
||||
|
||||
// ── Edit submit ───────────────────────────────────────────────────────────
|
||||
const handleEditSubmit = useCallback(
|
||||
async (payload: CreateDriverPayload | UpdateDriverPayload): Promise<boolean> => {
|
||||
if (!driver) return false;
|
||||
@@ -132,13 +118,11 @@ export default function DriverDetailPage() {
|
||||
const hasFiles = typedPayload.photo || typedPayload.nationalPhoto || typedPayload.driverCardPhoto;
|
||||
|
||||
if (hasFiles) {
|
||||
// Use multipart — JSON.stringify converts File to {} which fails validation
|
||||
await driverService.updateWithImages(driver.id, typedPayload, token);
|
||||
} else {
|
||||
await driverService.update(driver.id, typedPayload, token);
|
||||
}
|
||||
|
||||
// Re-fetch to reflect updated status, name, photos, etc.
|
||||
await loadDriver();
|
||||
showToast("success", "تم تحديث بيانات السائق بنجاح.");
|
||||
return true;
|
||||
@@ -151,7 +135,6 @@ export default function DriverDetailPage() {
|
||||
[driver, loadDriver, showToast],
|
||||
);
|
||||
|
||||
// ── Delete confirm ────────────────────────────────────────────────────────
|
||||
const handleConfirmDelete = useCallback(async () => {
|
||||
if (!driver) return;
|
||||
setDeleting(true);
|
||||
@@ -168,7 +151,6 @@ export default function DriverDetailPage() {
|
||||
|
||||
const statusConfig = driver ? DRIVER_STATUS_MAP[driver.status] : null;
|
||||
|
||||
// ── Render: Loading ───────────────────────────────────────────────────────
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 12, padding: "6rem 0", color: "var(--color-text-muted)" }}>
|
||||
@@ -178,7 +160,6 @@ export default function DriverDetailPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// ── Render: Error ─────────────────────────────────────────────────────────
|
||||
if (error || !driver) {
|
||||
return (
|
||||
<div style={{ maxWidth: 480, margin: "4rem auto", borderRadius: "var(--radius-xl)", border: "1px solid #FECACA", background: "#FEF2F2", padding: "1.5rem", textAlign: "center" }}>
|
||||
@@ -195,12 +176,10 @@ export default function DriverDetailPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// ── Render: Driver detail ─────────────────────────────────────────────────
|
||||
return (
|
||||
<>
|
||||
<section style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
||||
|
||||
{/* ── Toast notification ── */}
|
||||
{toast && (
|
||||
<div style={{
|
||||
borderRadius: "var(--radius-lg)",
|
||||
@@ -215,7 +194,6 @@ export default function DriverDetailPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Page header ── */}
|
||||
<header style={{
|
||||
borderRadius: "var(--radius-xl)", border: "1px solid var(--color-border)",
|
||||
background: "var(--color-surface)", padding: "1.5rem 2rem", boxShadow: "var(--shadow-card)",
|
||||
@@ -294,7 +272,6 @@ export default function DriverDetailPage() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* ── Content grid ── */}
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1.5rem" }}>
|
||||
<SectionCard title="البيانات الشخصية">
|
||||
<DetailRow label="الاسم الكامل" value={driver.name} />
|
||||
@@ -380,14 +357,14 @@ export default function DriverDetailPage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{deleteOpen && (
|
||||
<DriverDeleteModal
|
||||
driver={driver}
|
||||
deleting={deleting}
|
||||
onCancel={() => setDeleteOpen(false)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={deleteOpen}
|
||||
loading={deleting}
|
||||
onCancel={() => setDeleteOpen(false)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
title="حذف السائق"
|
||||
description={`هل أنت متأكد من حذف ${driver.name} (${driver.phone})؟ لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback } from "react";
|
||||
import { Alert, Spinner, ArchiveButton } from "@/src/Components/UI";
|
||||
import { DriverFormModal } from "@/src/Components/Driver/DriverFormModal";
|
||||
import { DriverDeleteModal } from "@/src/Components/Driver/DriverDeleteModal";
|
||||
import { Alert, Spinner, ArchiveButton, ConfirmDialog } from "@/src/Components/UI";
|
||||
import { ArchivedDrivers } from "@/src/Components/Driver/archive/ArchivedDrivers";
|
||||
import { useDrivers } from "@/src/hooks/useDriver";
|
||||
import { CreateDriverPayload, Driver, DRIVER_STATUS_MAP, UpdateDriverPayload } from "@/src/types/driver";
|
||||
import { DriverDetailPanel } from "@/src/Components/Driver/DriverDetailPanel";
|
||||
import { DriverDetailPanel, DriverFormModal , } from "@/src/Components/Driver";
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -413,14 +411,14 @@ export default function DriversPage() {
|
||||
)}
|
||||
|
||||
{/* ── Delete modal ── */}
|
||||
{deleteTarget && (
|
||||
<DriverDeleteModal
|
||||
driver={deleteTarget}
|
||||
deleting={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
loading={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
title="حذف السائق"
|
||||
description={`هل أنت متأكد من حذف ${deleteTarget?.name ?? ""} (${deleteTarget?.phone ?? ""})؟ لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
{/* ── Archive browser modal ── */}
|
||||
{archiveOpen && (
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
|
||||
|
||||
import { useState, useCallback } from "react";
|
||||
import { Alert, Spinner, Toast, ArchiveButton } from "@/src/Components/UI";
|
||||
import { OrderFormModal } from "@/src/Components/Order/OrderFormModal";
|
||||
import { OrderDeleteModal } from "@/src/Components/Order/OrderDeleteModal";
|
||||
import { OrderDetailPanel, ORDER_STATUS_MAP } from "@/src/Components/Order/OrderDetailBanel";
|
||||
import { Alert, Spinner, Toast, ArchiveButton, ConfirmDialog } from "@/src/Components/UI";
|
||||
import { ORDER_STATUS_MAP } from "@/src/Components/Order/OrderDetailBanel";
|
||||
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";
|
||||
|
||||
// ── Helpers — copied verbatim from app/dashboard/drivers/page.tsx ───────
|
||||
|
||||
@@ -441,14 +440,14 @@ export default function OrderComponent() {
|
||||
)}
|
||||
|
||||
{/* ── Delete modal ── */}
|
||||
{deleteTarget && (
|
||||
<OrderDeleteModal
|
||||
order={deleteTarget}
|
||||
deleting={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
loading={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
title="حذف الطلب"
|
||||
description={`هل أنت متأكد من حذف الطلب ${deleteTarget?.shipmentNumber ?? ""} الخاص بـ ${deleteTarget?.recipientName ?? ""}؟ لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
{/* ── Archive browser modal ── */}
|
||||
{archiveOpen && (
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Alert, ArchiveButton } from "@/src/Components/UI";
|
||||
import { Alert, ArchiveButton, ConfirmDialog,Toast } from "@/src/Components/UI";
|
||||
import { RoleTable } from "@/src/Components/role/RoleTable";
|
||||
import { RoleFormModal } from "@/src/Components/role/RoleFormModal";
|
||||
import { RoleDetailModal } from "@/src/Components/role/RoleDetailModal";
|
||||
import { DeleteRoleModal } from "@/src/Components/role/DeleteRoleModal";
|
||||
import { Toast } from "@/src/Components/UI";
|
||||
import { RoleFormModal,RoleDetailModal } from "@/src/Components/role";
|
||||
import { useRoles } from "@/src/hooks/useRole";
|
||||
import { Role, RoleFormData } from "@/src/types/role";
|
||||
import { ArchivedRolesModal } from "@/src/Components/role/archive/ArchivedRolesModal";
|
||||
@@ -77,14 +74,14 @@ export default function RolesPage() {
|
||||
)}
|
||||
|
||||
{/* Delete confirmation */}
|
||||
{deleteTarget && (
|
||||
<DeleteRoleModal
|
||||
role={deleteTarget}
|
||||
deleting={deleting}
|
||||
onCancel={() => { if (!deleting) setDeleteTarget(null); }}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
loading={deleting}
|
||||
onCancel={() => { if (!deleting) setDeleteTarget(null); }}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
title="حذف الدور"
|
||||
description={`هل أنت متأكد من حذف دور ${deleteTarget?.name ?? ""}؟ لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
{/* Archive browser modal */}
|
||||
{archiveOpen && (
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { Spinner } from "@/src/Components/UI";
|
||||
import { ConfirmDialog, Spinner } from "@/src/Components/UI";
|
||||
import { TripFormModal } from "@/src/Components/Trip/Tripformmodal";
|
||||
import { TripDeleteModal } from "@/src/Components/Trip/Tripdeletemodal";
|
||||
import { TripReportPanel } from "@/src/Components/Trip_Report/Tripreportpanel";
|
||||
import { tripService } from "@/src/services/trip.service";
|
||||
import { getStoredToken } from "@/src/lib/auth";
|
||||
@@ -206,44 +205,42 @@ export default function TripDetailPage() {
|
||||
|
||||
// ── Load trip ─────────────────────────────────────────────────────────────
|
||||
const loadTrip = useCallback(async () => {
|
||||
if (!tripId) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const token = getStoredToken();
|
||||
const res = await tripService.getById(tripId, token);
|
||||
setTrip((res as unknown as { data: Trip }).data);
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : "تعذّر تحميل بيانات الرحلة.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [tripId]);
|
||||
|
||||
if (!tripId) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const token = getStoredToken();
|
||||
const data = await tripService.getById(tripId, token);
|
||||
setTrip(data);
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : "تعذّر تحميل بيانات الرحلة.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [tripId]);
|
||||
useEffect(() => {
|
||||
queueMicrotask(loadTrip);
|
||||
}, [loadTrip]);
|
||||
|
||||
// ── Edit submit ───────────────────────────────────────────────────────────
|
||||
const handleEditSubmit = useCallback(
|
||||
async (
|
||||
payload: CreateTripPayload | UpdateTripPayload,
|
||||
): Promise<boolean> => {
|
||||
if (!trip) return false;
|
||||
try {
|
||||
const token = getStoredToken();
|
||||
const res = await tripService.update(trip.id, payload as UpdateTripPayload, token);
|
||||
const updated = (res as unknown as { data: Trip }).data;
|
||||
setTrip(updated);
|
||||
notify({ type: "success", message: "تم تحديث بيانات الرحلة بنجاح." });
|
||||
return true;
|
||||
} catch (err) {
|
||||
notify({ type: "error", message: extractApiMessage(err, "تعذّر تحديث الرحلة.") });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[trip, notify],
|
||||
);
|
||||
async (
|
||||
payload: CreateTripPayload | UpdateTripPayload,
|
||||
): Promise<boolean> => {
|
||||
if (!trip) return false;
|
||||
try {
|
||||
const token = getStoredToken();
|
||||
const updated = await tripService.update(trip.id, payload as UpdateTripPayload, token);
|
||||
setTrip(updated);
|
||||
notify({ type: "success", message: "تم تحديث بيانات الرحلة بنجاح." });
|
||||
return true;
|
||||
} catch (err) {
|
||||
notify({ type: "error", message: extractApiMessage(err, "تعذّر تحديث الرحلة.") });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[trip, notify],
|
||||
);
|
||||
|
||||
// ── Delete confirm ────────────────────────────────────────────────────────
|
||||
const handleConfirmDelete = useCallback(async () => {
|
||||
@@ -558,14 +555,14 @@ export default function TripDetailPage() {
|
||||
)}
|
||||
|
||||
{/* ── Delete modal ── */}
|
||||
{deleteOpen && (
|
||||
<TripDeleteModal
|
||||
trip={trip}
|
||||
deleting={deleting}
|
||||
onCancel={() => setDeleteOpen(false)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={deleteOpen}
|
||||
loading={deleting}
|
||||
onCancel={() => setDeleteOpen(false)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
title="حذف الرحلة"
|
||||
description={`هل أنت متأكد من حذف رحلة ${trip.title} (${trip.tripNumber})؟ لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -31,10 +31,8 @@ export default function ArchivedTripDetailPage() {
|
||||
setError(null);
|
||||
try {
|
||||
const token = getStoredToken();
|
||||
// Archived trips must go through the archived endpoint —
|
||||
// the normal /trip/:id endpoint won't return soft-deleted trips.
|
||||
const res = await tripService.getArchivedById(tripId, token);
|
||||
if (!cancelled) setTrip((res as unknown as { data: Trip }).data);
|
||||
const data = await tripService.getArchivedById(tripId, token);
|
||||
if (!cancelled) setTrip(data);
|
||||
} catch {
|
||||
if (!cancelled) setError("لم يتم العثور على هذه الرحلة في الأرشيف.");
|
||||
} finally {
|
||||
@@ -54,7 +52,6 @@ export default function ArchivedTripDetailPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Edge case: invalid/missing/unreachable trip ID
|
||||
if (error || !trip) {
|
||||
return (
|
||||
<EmptyState
|
||||
|
||||
@@ -4,10 +4,8 @@ import { useCallback, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTrips } from "@/src/hooks/useTrip";
|
||||
import { TripFormModal } from "@/src/Components/Trip/Tripformmodal";
|
||||
import { TripDeleteModal } from "@/src/Components/Trip/Tripdeletemodal";
|
||||
import { ArchivedTripList } from "@/src/Components/Trip/archive/ArchivedTripList";
|
||||
import { Alert, Spinner, ArchiveButton } from "@/src/Components/UI";
|
||||
import { Toast } from "@/src/Components/UI/Toast";
|
||||
import { Alert, Spinner, ArchiveButton, ConfirmDialog , Toast } from "@/src/Components/UI";
|
||||
import type {
|
||||
Trip,
|
||||
TripStatus,
|
||||
@@ -748,14 +746,14 @@ export default function TripsPage() {
|
||||
)}
|
||||
|
||||
{/* ── Delete modal ── */}
|
||||
{deleteTarget && (
|
||||
<TripDeleteModal
|
||||
trip={deleteTarget}
|
||||
deleting={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
loading={deleting}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
title="حذف الرحلة"
|
||||
description={`هل أنت متأكد من حذف رحلة ${deleteTarget?.title ?? ""} (${deleteTarget?.tripNumber ?? ""})؟ لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
{/* ── Archive browser modal ── */}
|
||||
{archiveOpen && (
|
||||
@@ -768,4 +766,4 @@ export default function TripsPage() {
|
||||
<Toast notification={notification} onDismiss={dismissNotification} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Alert, Toast, ArchiveButton } from "@/src/Components/UI";
|
||||
import { UserTable } from "@/src/Components/User/UserTable";
|
||||
import { UserFormModal } from "@/src/Components/User/UserFormModal";
|
||||
import { UserDetailModal } from "@/src/Components/User/UserDetailModal";
|
||||
import { DeleteConfirmModal } from "@/src/Components/User/DeleteConfirmModal";
|
||||
import { Alert, Toast, ArchiveButton, ConfirmDialog } from "@/src/Components/UI";
|
||||
import { UserFormModal,UserDetailModal,UserTable } from "@/src/Components/User";
|
||||
import { useUsers } from "@/src/hooks/useUser";
|
||||
import type { User, UserFormData } from "@/src/types/user";
|
||||
import { ArchivedUsersModal } from "@/src/Components/User/archive/Archivedusersmodal";
|
||||
@@ -76,16 +73,14 @@ export default function UsersPage() {
|
||||
)}
|
||||
|
||||
{/* Delete confirmation dialog */}
|
||||
{deleteTarget && (
|
||||
<DeleteConfirmModal
|
||||
user={deleteTarget}
|
||||
deleting={deleting}
|
||||
onCancel={() => {
|
||||
if (!deleting) setDeleteTarget(null);
|
||||
}}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
loading={deleting}
|
||||
onCancel={() => { if (!deleting) setDeleteTarget(null); }}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
title="حذف المستخدم"
|
||||
description={`هل أنت متأكد من حذف ${deleteTarget?.name ?? ""}؟ لا يمكن التراجع عن هذا الإجراء.`}
|
||||
/>
|
||||
|
||||
{/* Archive browser modal */}
|
||||
{archiveOpen && (
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function RootLayout({
|
||||
return (
|
||||
// ⚠️ اتصلح: الـ Navbar والـ footer كانوا برة <html>/<body> وده HTML غير صالح
|
||||
<html lang="ar" dir="rtl">
|
||||
<body className="app-shell">
|
||||
<body className="app-shell" suppressHydrationWarning>
|
||||
{/* الناف بار بيظهر بس لو مفيش يوزر مسجل دخول */}
|
||||
<ConditionalNavbar />
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user