import { useEffect, useState, useCallback } from "react"; import { useParams, Link } from "react-router-dom"; import type { Client, GroomingVisitLog, Pet } from "@groombook/types"; import { PetPhotoDisplay } from "../components/PetPhotoDisplay.js"; import { PetPhotoUpload } from "../components/PetPhotoUpload.js"; export function ClientDetailPage() { const { clientId } = useParams<{ clientId: string }>(); const [client, setClient] = useState(null); const [pets, setPets] = useState([]); const [visitLogs, setVisitLogs] = useState>({}); const [logsLoading, setLogsLoading] = useState>({}); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [photoRevisions, setPhotoRevisions] = useState>({}); const handlePhotoUploaded = useCallback((petId: string) => { setPhotoRevisions((prev) => ({ ...prev, [petId]: (prev[petId] ?? 0) + 1 })); }, []); useEffect(() => { if (!clientId) { setError("No client ID provided"); setLoading(false); return; } async function load() { const id = clientId!; setLoading(true); setError(null); try { const [clientRes, petsRes] = await Promise.all([ fetch(`/api/clients/${encodeURIComponent(id)}`), fetch(`/api/pets?clientId=${encodeURIComponent(id)}`), ]); if (!clientRes.ok) { const err = await clientRes.json().catch(() => ({})) as { error?: string }; throw new Error(err.error ?? `Client fetch failed: ${clientRes.status}`); } if (!petsRes.ok) { throw new Error(`Pets fetch failed: ${petsRes.status}`); } setClient(await clientRes.json() as Client); setPets(await petsRes.json() as Pet[]); } catch (e) { setError(e instanceof Error ? e.message : "Failed to load client"); } finally { setLoading(false); } } void load(); }, [clientId]); async function loadVisitLogs(petId: string) { setLogsLoading((prev) => ({ ...prev, [petId]: true })); const r = await fetch(`/api/grooming-logs?petId=${encodeURIComponent(petId)}`); if (r.ok) { const logs = await r.json() as GroomingVisitLog[]; setVisitLogs((prev) => ({ ...prev, [petId]: logs })); } setLogsLoading((prev) => ({ ...prev, [petId]: false })); } if (loading) { return (
Loading client…
); } if (error || !client) { return (
← Back to clients
{error ?? "Client not found"}
); } return (
{/* Header */}

{client.name}

{client.status === "disabled" && ( Disabled )}
{client.email &&
{client.email}
} {client.phone &&
{client.phone}
} {client.address &&
{client.address}
} {client.notes && (
{client.notes}
)}
← Back to list
{/* Pets */}

Pets

{pets.length === 0 ? (

No pets on file for this client.

) : (
{pets.map((p) => (
{/* Photo + header */}
{p.name}
{p.species}{p.breed ? ` · ${p.breed}` : ""}
{p.weightKg != null &&
{p.weightKg} kg
} {p.dateOfBirth &&
Born {new Date(p.dateOfBirth).toLocaleDateString()}
}
handlePhotoUploaded(p.id)} />
{p.healthAlerts && (
⚠ Health alerts: {p.healthAlerts}
)} {/* Grooming preferences */} {(p.cutStyle || p.shampooPreference || p.specialCareNotes || p.groomingNotes) && (
{p.cutStyle && (
Cut: {p.cutStyle}
)} {p.shampooPreference && (
Shampoo: {p.shampooPreference}
)} {p.specialCareNotes && (
Special care: {p.specialCareNotes}
)} {p.groomingNotes && (
Notes: {p.groomingNotes}
)}
)} {/* Visit history */} {(() => { const logs = visitLogs[p.id]; const loadingLogs = logsLoading[p.id]; return (
VISIT HISTORY
{!logs && !loadingLogs && ( )}
{loadingLogs &&
Loading…
} {logs && logs.length === 0 &&
No visits yet
} {logs && logs.length > 0 && ( <> {logs.slice(0, 3).map((log) => (
{new Date(log.groomedAt).toLocaleDateString()} {log.cutStyle && · {log.cutStyle}} {log.notes && · {log.notes}}
))} {logs.length > 3 && (
+{logs.length - 3} more visits
)} )}
); })()}
))}
)}
); }