diff --git a/apps/api/src/routes/pets.ts b/apps/api/src/routes/pets.ts index 5dc5869..ad6eb29 100644 --- a/apps/api/src/routes/pets.ts +++ b/apps/api/src/routes/pets.ts @@ -12,6 +12,7 @@ const createPetSchema = z.object({ breed: z.string().max(200).optional(), weightKg: z.number().positive().optional(), dateOfBirth: z.string().datetime().optional(), + healthAlerts: z.string().max(2000).optional(), groomingNotes: z.string().max(2000).optional(), }); diff --git a/apps/web/src/pages/Clients.tsx b/apps/web/src/pages/Clients.tsx index e1a5776..b768226 100644 --- a/apps/web/src/pages/Clients.tsx +++ b/apps/web/src/pages/Clients.tsx @@ -17,11 +17,12 @@ interface PetForm { breed: string; weightStr: string; dob: string; + healthAlerts: string; groomingNotes: string; } const EMPTY_CLIENT: ClientForm = { name: "", email: "", phone: "", address: "", notes: "" }; -const EMPTY_PET: PetForm = { name: "", species: "Dog", breed: "", weightStr: "", dob: "", groomingNotes: "" }; +const EMPTY_PET: PetForm = { name: "", species: "Dog", breed: "", weightStr: "", dob: "", healthAlerts: "", groomingNotes: "" }; // ─── Component ─────────────────────────────────────────────────────────────── @@ -47,6 +48,8 @@ export function ClientsPage() { const [petForm, setPetForm] = useState(EMPTY_PET); const [petFormError, setPetFormError] = useState(null); const [savingPet, setSavingPet] = useState(false); + const [deletingPetId, setDeletingPetId] = useState(null); + const [deletingClient, setDeletingClient] = useState(false); async function loadClients() { const r = await fetch("/api/clients"); @@ -133,12 +136,50 @@ export function ClientsPage() { name: p.name, species: p.species, breed: p.breed ?? "", weightStr: p.weightKg != null ? String(p.weightKg) : "", dob: p.dateOfBirth ? p.dateOfBirth.slice(0, 10) : "", + healthAlerts: p.healthAlerts ?? "", groomingNotes: p.groomingNotes ?? "", }); setPetFormError(null); setShowPetForm(true); } + async function deletePet(petId: string) { + if (!selectedClient) return; + if (!window.confirm("Delete this pet? This cannot be undone.")) return; + setDeletingPetId(petId); + try { + const res = await fetch(`/api/pets/${petId}`, { method: "DELETE" }); + if (!res.ok) { + const err = (await res.json()) as { error?: string }; + throw new Error(err.error ?? `HTTP ${res.status}`); + } + await loadPets(selectedClient.id); + } catch (e: unknown) { + alert(e instanceof Error ? e.message : "Failed to delete pet"); + } finally { + setDeletingPetId(null); + } + } + + async function deleteClient(clientId: string) { + if (!window.confirm("Delete this client and all their pets? This cannot be undone.")) return; + setDeletingClient(true); + try { + const res = await fetch(`/api/clients/${clientId}`, { method: "DELETE" }); + if (!res.ok) { + const err = (await res.json()) as { error?: string }; + throw new Error(err.error ?? `HTTP ${res.status}`); + } + setSelectedClient(null); + setPets([]); + await loadClients(); + } catch (e: unknown) { + alert(e instanceof Error ? e.message : "Failed to delete client"); + } finally { + setDeletingClient(false); + } + } + async function submitPet(e: React.FormEvent) { e.preventDefault(); if (!selectedClient) return; @@ -152,6 +193,7 @@ export function ClientsPage() { breed: petForm.breed || undefined, weightKg: petForm.weightStr ? parseFloat(petForm.weightStr) : undefined, dateOfBirth: petForm.dob ? new Date(petForm.dob).toISOString() : undefined, + healthAlerts: petForm.healthAlerts || undefined, groomingNotes: petForm.groomingNotes || undefined, }; const res = editingPet @@ -233,9 +275,18 @@ export function ClientsPage() { )} - +
+ + +
@@ -255,13 +306,27 @@ export function ClientsPage() {
{p.name} - +
+ + +
{p.species}{p.breed ? ` · ${p.breed}` : ""}
{p.weightKg != null &&
{p.weightKg} kg
} {p.dateOfBirth &&
Born {new Date(p.dateOfBirth).toLocaleDateString()}
} + {p.healthAlerts && ( +
+ ⚠ Health alerts: {p.healthAlerts} +
+ )} {p.groomingNotes && (
Notes: {p.groomingNotes} @@ -331,6 +396,9 @@ export function ClientsPage() { setPetForm((f) => ({ ...f, dob: e.target.value }))} style={inputStyle} /> + +