"use client"; import { useEffect, useState } from "react"; import { useParams, useRouter } from "next/navigation"; // ── UI components (canonical barrel — no old Client/Toast) ───────────────── import { Alert, Toast } from "@/src/Components/UI"; // ── Hooks & services ─────────────────────────────────────────────────────── import { useClientAddresses } from "@/src/hooks/useClientAddresses"; import { clientService } from "@/src/services/client.service"; import { getStoredToken } from "@/src/lib/auth"; // ── Types ────────────────────────────────────────────────────────────────── import type { Client, ClientAddress, ClientAddressFormData, ClientFormData, } from "@/src/types/client"; // ── Client-specific modals ───────────────────────────────────────────────── import { AddressFormModal,DeleteConfirmModal ,ClientFormModal } from "@/src/Components/Client"; import { CreateAddressFormValues, UpdateAddressFormValues } from "@/src/validations/client_address.validator"; // ───────────────────────────────────────────────────────────────────────────── // Helpers // ───────────────────────────────────────────────────────────────────────────── /** Emoji icon for known address label types */ function labelIcon(label: string): string { const map: Record = { "فوترة": "💳", "شحن": "📦", "المقر الرئيسي": "🏢", "فرع": "🏬", "مستودع": "🏭", billing: "💳", shipping: "📦", "head office": "🏢", branch: "🏬", warehouse: "🏭", }; return map[label.toLowerCase()] ?? "📍"; } // ───────────────────────────────────────────────────────────────────────────── // AddressCard sub-component // ───────────────────────────────────────────────────────────────────────────── interface AddressCardProps { address: ClientAddress; onEdit: () => void; onDelete: () => void; onMakePrimary: () => void; /** Whether a "set primary" action is pending for this specific card */ settingPrimary?: boolean; } /** * AddressCard — Renders a single address in the grid. */ function AddressCard({ address, onEdit, onDelete, onMakePrimary, settingPrimary = false, }: AddressCardProps) { return (
{/* Label row */}
{address.label}
{/* Primary badge */} {address.isPrimary && ( ★ رئيسي )}
{/* Address lines */}

{address.street}

{address.city}،{" "}{address.state} {address.postalCode}

{address.country}

{/* Actions */}
تعديل {/* * Set-primary button — only for non-primary addresses. * Shows a loading indicator while the API call is in-flight. */} {!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 ( ); } // ───────────────────────────────────────────────────────────────────────────── // AddressMiniList — used inside the client edit modal // ───────────────────────────────────────────────────────────────────────────── /** * AddressMiniList — A compact read-only list of addresses shown inside * the ClientFormModal when editing an existing client. * * Purpose: * Allows the user to see all addresses and designate one as primary * without navigating away from the edit modal. * * Props: * addresses — current list from useClientAddresses * onMakePrimary — handler that calls clientAddressService.setPrimary * settingId — id of the address currently being promoted (for loading state) */ function AddressMiniList({ addresses, onMakePrimary, settingId, }: { addresses: ClientAddress[]; onMakePrimary: (id: string) => void; settingId: string | null; }) { if (addresses.length === 0) { return (
لا توجد عناوين مضافة بعد.
); } return ( ); } // ───────────────────────────────────────────────────────────────────────────── // ClientEditModalWithAddresses — wraps ClientFormModal + AddressMiniList // ───────────────────────────────────────────────────────────────────────────── /** * ClientEditModalWithAddresses */ interface ClientEditModalWithAddressesProps { client: Client; addresses: ClientAddress[]; onClose: () => void; onSubmit: (data: ClientFormData, isNew: boolean) => Promise; onMakePrimary: (id: string) => Promise; } function ClientEditModalWithAddresses({ client, addresses, onClose, onSubmit, onMakePrimary, }: ClientEditModalWithAddressesProps) { const [settingId, setSettingId] = useState(null); const handleSetPrimary = async (id: string) => { setSettingId(id); await onMakePrimary(id); setSettingId(null); }; return ( // We reuse the backdrop and center the compound modal manually
{ 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", gap: "1rem", }} > {/* Left panel: client form */}
e.stopPropagation()} style={{ width: "100%", maxWidth: 520, display: "flex", flexDirection: "column", gap: "1rem", }} > {/* Render ClientFormModal in a "portal-less" way by embedding its JSX inline */}
{/* We mount ClientFormModal as a standalone dialog — it handles its own form logic. We intercept onClose to close the whole compound modal. */}
{/* * Address section — appended below the form card. * Satisfies: "create a section that displays all available addresses." */}
{/* Section header */}

عناوين العميل

{addresses.length} عنوان — اضغط "تعيين كرئيسي" لتغيير العنوان الرئيسي

{/* Address mini-list */}
); } // ───────────────────────────────────────────────────────────────────────────── // Page component // ───────────────────────────────────────────────────────────────────────────── export default function ClientAddressesPage() { const params = useParams(); const router = useRouter(); // FIX: "id is undefined" 404 bug. // params.clientId only works if your route folder is named [clientId]. // If your folder is actually [id], params.clientId is always undefined, // which becomes the string "undefined" in API URLs like /client/undefined/addresses. // CHECK YOUR FOLDER NAME and keep only the line that matches it: const clientId = (params?.clientId ?? params?.id) as string | undefined; // FIX: warn loudly in dev instead of silently calling the API with no id useEffect(() => { if (!clientId) { console.warn("ClientAddressesPage: clientId is missing from route params. Check your folder name (must match the key used in useParams())."); } }, [clientId]); // ── Parent client meta ─────────────────────────────────────────────────── const [client, setClient] = useState(null); const [clientLoading, setClientLoading] = useState(true); // Fetch the parent client so we can show their name / details in the header useEffect(() => { if (!clientId) return; setClientLoading(true); clientService .getById(clientId, getStoredToken()) .then((res) => setClient(res.data)) .catch(() => router.replace("/dashboard/clients")) .finally(() => setClientLoading(false)); }, [clientId, router]); // ── Address CRUD hook ──────────────────────────────────────────────────── const { addresses, loading: addrLoading, error, notification, // { type, message } — matches ToastNotification clearError, createAddress, updateAddress, deleteAddress, makePrimary, reload, } = useClientAddresses(clientId ?? ""); // NOTE: hook itself guards "if (!clientId) return" before calling the API, // so passing "" here is safe — it just won't fetch until clientId is real. // ── Modal state ────────────────────────────────────────────────────────── // Address form: false = closed | null = create | ClientAddress = edit const [addrFormTarget, setAddrFormTarget] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [deleting, setDeleting] = useState(false); // Client edit modal (with address section) const [editingClient, setEditingClient] = useState(false); // Per-card primary-setting loading state (card id) const [settingPrimaryId, setSettingPrimaryId] = useState(null); // ── Address form handler ───────────────────────────────────────────────── // FIX: "addrFormTarget!.id" used "!" which only blocks null/undefined, // NOT "false" — but addrFormTarget's type is "ClientAddress | null | false". // If addrFormTarget was ever false here, ".id" would be undefined at runtime, // causing the "id is undefined" 404 bug from the API call. // We now check addrFormTarget directly instead of trusting the isNew param, // so create vs update can never disagree with each other. const handleAddressSubmit = async ( data: CreateAddressFormValues | UpdateAddressFormValues ): Promise => { if (addrFormTarget === null) { // create mode return createAddress(data as CreateAddressFormValues); } if (addrFormTarget === false) { // modal isn't even open — this should never happen, but guard anyway console.error("handleAddressSubmit called while addrFormTarget is false (modal closed)"); return false; } // update mode — addrFormTarget is a real ClientAddress here, .id is safe return updateAddress(addrFormTarget.id, data as UpdateAddressFormValues); }; // ── Delete handler ─────────────────────────────────────────────────────── const handleDeleteConfirm = async () => { if (!deleteTarget || deleting) return; setDeleting(true); const ok = await deleteAddress(deleteTarget.id); setDeleting(false); if (ok) setDeleteTarget(null); }; // ── Set-primary handler ────────────────────────────────────────────────── /** * makePrimaryForCard — wraps makePrimary from the hook with per-card * loading state so each card can show its own spinner independently. */ const makePrimaryForCard = async (id: string): Promise => { setSettingPrimaryId(id); const ok = await makePrimary(id); setSettingPrimaryId(null); return ok; }; // ── Client edit submit ─────────────────────────────────────────────────── const handleClientEditSubmit = async ( data: ClientFormData, _isNew: boolean, ): Promise => { if (!client) return false; try { const token = getStoredToken(); const res = await clientService.update(client.id, data, token); setClient(res.data); // optimistically update local state return true; } catch { return false; } }; // ── Guard: wait for client ─────────────────────────────────────────────── if (!clientId || clientLoading) { return (
); } // ── Render ──────────────────────────────────────────────────────────────── return ( <> {/* * Global toast — sourced from UI barrel (not the old Client/Toast). * The notification object from useClientAddresses has the same shape * as ToastNotification: { type: "success" | "error"; message: string }. */} {/* ── Address create / edit modal ── */} {addrFormTarget !== false && ( setAddrFormTarget(false)} onSubmit={handleAddressSubmit} /> )} {/* * ── Address delete confirmation ── * We cast the address to the Client shape that DeleteConfirmModal expects * (it only uses id + name fields from Client). */} {deleteTarget && ( { if (!deleting) setDeleteTarget(null); }} onConfirm={handleDeleteConfirm} /> )} {/* * ── Client edit modal with embedded address section ── * TEST: Click "تعديل بيانات العميل" in the header. * → Modal shows client form + address list with "تعيين كرئيسي" buttons. */} {editingClient && client && ( setEditingClient(false)} onSubmit={handleClientEditSubmit} onMakePrimary={makePrimaryForCard} /> )}
{/* ── Page header ── */}
{/* Back link */}

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

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

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

{/* Header action buttons */}
{/* Edit client (opens compound modal with address section) */} {client && ( )} {/* Add address button */}
{/* Client info strip */} {client && (
{client.name} {client.email} {client.phone} {client.taxId && ( {client.taxId} )}
)}
{/* * Error alert — shown when the address load fails. * Uses the UI/Alert component with an onClose dismiss handler. * TEST: Simulate a network error → alert banner appears above the grid. */} {error && ( )} {/* ── Address grid ── */} {addrLoading ? ( /* Loading skeleton-ish state */
جارٍ تحميل العناوين…
) : addresses.length === 0 ? ( /* Empty state */

📍

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

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

) : ( /* * Address cards grid. * TEST: * - Click "تعديل" → AddressFormModal opens pre-filled. * - Click "تعيين كرئيسي" → button shows "…" loading, then card re-renders * with "★ رئيسي" badge and blue ring; previously primary card loses the badge. * - Click "حذف" → DeleteConfirmModal opens → confirm → card removed, toast shown. */
{addresses.map((addr) => ( setAddrFormTarget(addr)} onDelete={() => setDeleteTarget(addr)} onMakePrimary={() => makePrimaryForCard(addr.id)} settingPrimary={settingPrimaryId === addr.id} /> ))}
)}
); }