diff --git a/app/dashboard/clients/[clientId]/addresses/[addressesId]/page.tsx b/app/dashboard/clients/[clientId]/addresses/[addressesId]/page.tsx deleted file mode 100644 index 39a3e02..0000000 --- a/app/dashboard/clients/[clientId]/addresses/[addressesId]/page.tsx +++ /dev/null @@ -1,100 +0,0 @@ -// app/dashboard/[clientId]/addresses/[addressesId]/page.tsx -"use client"; - -import { useCallback, useEffect, useState } from "react"; -import { useParams, useRouter } from "next/navigation"; - -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"; - -export default function AddressDetailsPage() { - const params = useParams(); - const router = useRouter(); - - const clientId = params?.clientId as string | undefined; - const addressId = params?.addressesId as string | undefined; - - const [address, setAddress] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - const loadAddress = useCallback(async () => { - if (!clientId || !addressId) return; - setLoading(true); - 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]); - - useEffect(() => { - loadAddress(); - }, [loadAddress]); - - // ── Loading ────────────────────────────────────────────────────────────── - if (loading) { - return ( -
- - جارٍ تحميل بيانات العنوان… -
- ); - } - - // ── Error ──────────────────────────────────────────────────────────────── - if (error || !address) { - return ( -
- - -
- ); - } - - // ── Content ────────────────────────────────────────────────────────────── - return ( -
- - - -
- ); -} \ 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 8bf5fe1..b736620 100644 --- a/app/dashboard/clients/[clientId]/addresses/page.tsx +++ b/app/dashboard/clients/[clientId]/addresses/page.tsx @@ -8,16 +8,21 @@ import { ConfirmDialog, Toast, ArchiveButton, + Button, + EmptyState, + PageLoader, + InlineLoader, } 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 { AddressDetailModal } from "@/src/Components/Client_Adress/AddressDetailModal"; import { ArchivedClientsAddressesModal } from "@/src/Components/Client_Adress/archive/ArchivedClientsAddressesModal"; import type { CreateAddressFormValues, @@ -28,38 +33,36 @@ 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()] ?? "📍"; } // ─── AddressCard ─────────────────────────────────────────────────────────── +// CHANGE: clicking the card now opens AddressDetailModal — same pattern as +// UserTable → UserDetailModal (viewUserId). Action buttons stop propagation +// so they don't trigger the modal. `ActionBtn` (custom component) removed in +// favor of the shared {!address.isPrimary && ( - - {settingPrimary ? "جارٍ التعيين…" : "تعيين كأساسي"} - + تعيين كأساسي + )} - حذف - - - - ); -} - -function ActionBtn({ - onClick, - color, - bg, - border, - style, - disabled, - children, -}: { - onClick: () => void; - color: string; - bg: string; - border: string; - style?: React.CSSProperties; - disabled?: boolean; - children: React.ReactNode; -}) { - return ( - - ); -} - -// ─── ClientEditModal ─────────────────────────────────────────────────────── - -interface ClientEditModalProps { - client: Client; - onClose: () => void; - onSubmit: (data: ClientFormData, isNew: boolean) => Promise; -} - -function ClientEditModal({ client, onClose, onSubmit }: ClientEditModalProps) { - return ( -
{ - if (e.target === e.currentTarget) onClose(); - }} - style={{ - position: "fixed", - inset: 0, - zIndex: 50, - background: "rgba(15,23,42,0.55)", - backdropFilter: "blur(4px)", - display: "flex", - alignItems: "flex-start", - justifyContent: "center", - padding: "2rem 1rem", - overflowY: "auto", - }} - > -
e.stopPropagation()} - style={{ - width: "100%", - maxWidth: 520, - background: "var(--color-surface)", - borderRadius: "var(--radius-2xl)", - border: "1px solid var(--color-border)", - boxShadow: "0 24px 64px rgba(0,0,0,.18)", - overflow: "hidden", - }} - > - +
); @@ -354,8 +248,8 @@ function ClientEditModal({ client, onClose, onSubmit }: ClientEditModalProps) { // ─── Page ────────────────────────────────────────────────────────────────── export default function ClientAddressesPage() { - const params = useParams(); - const router = useRouter(); + const params = useParams(); + const router = useRouter(); const clientId = (params?.clientId ?? params?.id) as string | undefined; @@ -366,7 +260,7 @@ export default function ClientAddressesPage() { }, [clientId]); // ── Parent client ──────────────────────────────────────────────────────── - const [client, setClient] = useState(null); + const [client, setClient] = useState(null); const [clientLoading, setClientLoading] = useState(true); const loadClient = useCallback(() => { @@ -400,21 +294,20 @@ export default function ClientAddressesPage() { } = useClientAddresses(clientId ?? ""); // ── Modal state ────────────────────────────────────────────────────────── - const [addrFormTarget, setAddrFormTarget] = useState< - ClientAddress | null | false - >(false); - const [deleteTarget, setDeleteTarget] = useState(null); - const [deleting, setDeleting] = useState(false); - const [editingClient, setEditingClient] = useState(false); + const [addrFormTarget, setAddrFormTarget] = useState(false); + const [deleteTarget, setDeleteTarget] = useState(null); + const [deleting, setDeleting] = useState(false); + const [editingClient, setEditingClient] = useState(false); // Archive browser modal open/closed — scoped to this client's addresses - const [archiveOpen, setArchiveOpen] = useState(false); + const [archiveOpen, setArchiveOpen] = useState(false); + // Address whose detail modal is open — same pattern as viewUserId/UserDetailModal + const [viewAddress, setViewAddress] = useState(null); // ── Address form handler ───────────────────────────────────────────────── const handleAddressSubmit = async ( - data: CreateAddressFormValues | UpdateAddressFormValues, + data: CreateAddressFormValues | UpdateAddressFormValues ): Promise => { - if (addrFormTarget === null) - return createAddress(data as CreateAddressFormValues); + if (addrFormTarget === null) return createAddress(data as CreateAddressFormValues); if (addrFormTarget === false) return false; return updateAddress(addrFormTarget.id, data as UpdateAddressFormValues); }; @@ -448,28 +341,9 @@ export default function ClientAddressesPage() { }; // ── Loading guard ──────────────────────────────────────────────────────── + // CHANGE: swapped the hand-rolled spinning
for the template's PageLoader. if (!clientId || clientLoading) { - return ( -
-
-
- ); + return ; } // ── Render ──────────────────────────────────────────────────────────────── @@ -477,6 +351,14 @@ export default function ClientAddressesPage() { <> + {/* Address detail modal — same pattern as viewUserId → UserDetailModal */} + {viewAddress && ( + setViewAddress(null)} + /> + )} + {/* Address create / edit modal */} {addrFormTarget !== false && ( { - if (!deleting) setDeleteTarget(null); - }} + onCancel={() => { if (!deleting) setDeleteTarget(null); }} onConfirm={handleDeleteConfirm} - title="حذف العميل" - description={`هل أنت متأكد من حذف ${deleteTarget?.label ?? ""}؟ سيتم حذف جميع عناوينه أيضاً. لا يمكن التراجع عن هذا الإجراء.`} + title="حذف العنوان" + description={`هل أنت متأكد من حذف ${deleteTarget?.label ?? ""}؟ لا يمكن التراجع عن هذا الإجراء.`} /> - {/* Client edit modal */} + {/* + Client edit modal + CHANGE: removed the custom `ClientEditModal` wrapper that built its + own fixed-position backdrop by hand. `ClientFormModal` already + renders the shared internally, so wrapping it in another + backdrop produced two stacked overlays. We now render it directly. + */} {editingClient && client && ( - setEditingClient(false)} onSubmit={handleClientEditSubmit} /> @@ -515,9 +401,8 @@ export default function ClientAddressesPage() { /> )} -
+
+ {/* ── Header ── */}
- + -

+

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

-

+

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

-

+

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

+ {/* CHANGE: custom )} -
@@ -685,15 +481,8 @@ export default function ClientAddressesPage() { borderTop: "1px solid var(--color-border)", }} > - - {client.name} - - + {client.name} + {client.email} {client.phone} @@ -718,79 +507,20 @@ export default function ClientAddressesPage() { {/* ── Address grid ── */} {addrLoading ? ( -
-
- جارٍ تحميل العناوين… -
+ // CHANGE: hand-rolled spinning
→ InlineLoader + ) : addresses.length === 0 ? ( -
-

📍

-

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

-

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

- -
+ // CHANGE: custom empty-state
→ EmptyState + setAddrFormTarget(null)}> + أضف أول عنوان + + } + /> ) : (
- {addresses.map((addr) => ( - router.push(`/dashboard/${clientId}/addresses/${addr.id}`)} - onEdit={() => setAddrFormTarget(addr)} - onDelete={() => setDeleteTarget(addr)} - onSetPrimary={() => handleSetPrimary(addr)} - settingPrimary={settingPrimaryId === addr.id} - /> -))} + {addresses.map((addr) => ( + setViewAddress(addr)} + 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/src/Components/Client_Adress/AddressDetails.tsx b/src/Components/Client_Adress/AddressDetailModal.tsx similarity index 87% rename from src/Components/Client_Adress/AddressDetails.tsx rename to src/Components/Client_Adress/AddressDetailModal.tsx index db9f641..9e9af7c 100644 --- a/src/Components/Client_Adress/AddressDetails.tsx +++ b/src/Components/Client_Adress/AddressDetailModal.tsx @@ -1,5 +1,6 @@ "use client"; +import { Button, Modal } from "../UI"; import type { ClientAddress } from "@/src/types/client_adresses"; // ─── Helpers ─────────────────────────────────────────────────────────────── @@ -124,13 +125,9 @@ function SectionCard({ ); } -// ─── Main component ──────────────────────────────────────────────────────── +// ─── Body content — was AddressDetails.tsx, now inlined here ─────────────── -interface AddressDetailsProps { - address: ClientAddress; -} - -export function AddressDetails({ address }: AddressDetailsProps) { +function AddressDetailsBody({ address }: { address: ClientAddress }) { const { details, contactPerson, location } = address; return ( @@ -255,4 +252,33 @@ export function AddressDetails({ address }: AddressDetailsProps) {
); +} + +// ─── Main export — the modal ──────────────────────────────────────────────── + +interface AddressDetailModalProps { + address: ClientAddress; + onClose: () => void; +} + +// Unlike UserDetailModal, no fetch-by-id is needed here — the address list +// (useClientAddresses) already returns the full ClientAddress object with +// details/contactPerson/location, so we just render what we already have. +export function AddressDetailModal({ address, onClose }: AddressDetailModalProps) { + return ( + + إغلاق + + } + > + + + ); } \ No newline at end of file