import { useState, useEffect } from "react"; import { PawPrint, Heart, Scissors, Clock, Edit3, Loader2, Star } from "lucide-react"; import { PetForm } from "./PetForm.js"; import type { Pet } from "@groombook/types"; interface Appointment { id: string; startTime: string; endTime: string; status: string; confirmationStatus: string | null; customerNotes: string | null; groomerNotes: string | null; reportCardId: string | null; pet: { id: string; name: string; photo: string | null } | null; service: { id: string } | null; staff: { id: string; name: string } | null; } interface AppointmentsResponse { appointments: Appointment[]; } interface Props { sessionId: string | null; readOnly: boolean; } function buildHeaders(sessionId: string | null): Record { const headers: Record = {}; if (sessionId) { headers["X-Impersonation-Session-Id"] = sessionId; } return headers; } export function PetProfiles({ sessionId, readOnly }: Props) { const [pets, setPets] = useState([]); const [appointments, setAppointments] = useState({ appointments: [] }); const [selectedPetId, setSelectedPetId] = useState(""); const [activeTab, setActiveTab] = useState<"info" | "medical" | "grooming" | "history">("info"); const [editingPetId, setEditingPetId] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { async function fetchData() { setLoading(true); setError(null); try { const [petsRes, apptsRes] = await Promise.all([ fetch("/api/portal/pets", { headers: buildHeaders(sessionId) }), fetch("/api/portal/appointments", { headers: buildHeaders(sessionId) }), ]); if (!petsRes.ok) { throw new Error("Failed to load pets"); } if (!apptsRes.ok) { throw new Error("Failed to load appointments"); } const petsData: Pet[] = await petsRes.json(); const apptsData: AppointmentsResponse = await apptsRes.json(); setPets(petsData); setAppointments(apptsData); if (petsData.length > 0 && !selectedPetId) { setSelectedPetId(petsData[0]?.id ?? ""); } } catch (e) { setError(e instanceof Error ? e.message : "Failed to load data"); } finally { setLoading(false); } } fetchData(); }, [sessionId]); const selectedPet = pets.find(p => p.id === selectedPetId) ?? null; const petHistory = appointments.appointments.filter(a => a.pet?.id === selectedPetId && new Date(a.startTime) <= new Date()); const editingPet = editingPetId ? pets.find(p => p.id === editingPetId) ?? null : null; const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(null); async function handlePetSave(updatedPet: Pet) { setSaving(true); setSaveError(null); try { const res = await fetch(`/api/portal/pets/${updatedPet.id}`, { method: "PATCH", headers: { "Content-Type": "application/json", ...buildHeaders(sessionId) }, body: JSON.stringify(updatedPet), }); if (!res.ok) throw new Error("Failed to save pet"); const saved: Pet = await res.json(); setPets(prev => prev.map(p => p.id === saved.id ? saved : p)); setEditingPetId(null); } catch (e) { setSaveError(e instanceof Error ? e.message : "Failed to save pet"); } finally { setSaving(false); } } if (editingPet) { return ( setEditingPetId(null)} saving={saving} saveError={saveError} /> ); } if (loading) { return (
); } if (error) { return (

{error}

); } if (pets.length === 0) { return (

No pets found

); } return (
{/* Pet Selector */}
{pets.map(p => ( ))}
{/* Profile Header */} {selectedPet && (
{selectedPet.photoKey ? ( 🐾 ) : ( 🐾 )}

{selectedPet.name}

{selectedPet.breed ?? "Unknown breed"} · {(() => { const w = selectedPet.weight ?? selectedPet.weightKg; return w != null && w !== "" ? `${w} kg` : "Unknown weight"; })()}

Born {(() => { const d = selectedPet.birthDate ?? selectedPet.dateOfBirth; return d ? new Date(d).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" }) : "Unknown"; })()}

{!readOnly && ( )}
)} {/* Tabs */}
{([ { id: "info", label: "Basic Info", icon: PawPrint }, { id: "medical", label: "Medical", icon: Heart }, { id: "grooming", label: "Grooming", icon: Scissors }, { id: "history", label: "History", icon: Clock }, ] as const).map(({ id, label, icon: Icon }) => ( ))}
{/* Tab Content */}
{activeTab === "info" && selectedPet && } {activeTab === "medical" && selectedPet && } {activeTab === "grooming" && selectedPet && } {activeTab === "history" && }
); } export function formatSizeCategory(size?: string | null): string { if (!size) return "Unknown"; return size .split("_") .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); } function InfoRow({ label, value }: { label: string; value: React.ReactNode }) { return (
{label} {value}
); } function SeverityBadge({ severity }: { severity: "low" | "medium" | "high" }) { const classes = { low: "bg-green-100 text-green-700", medium: "bg-amber-100 text-amber-700", high: "bg-red-100 text-red-700", }; return ( {severity.charAt(0).toUpperCase() + severity.slice(1)} ); } export function BasicInfoTab({ pet, readOnly }: { pet: Pet; readOnly: boolean }) { const score = pet.temperamentScore; const flags = pet.temperamentFlags ?? []; return (
{ const w = pet.weight ?? pet.weightKg; return w != null && w !== "" ? `${w} kg` : "Unknown"; })()} /> { const d = pet.birthDate ?? pet.dateOfBirth; return d ? new Date(d).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" }) : "Unknown"; })()} /> {/* Temperament (staff-set, read-only) */} {(score != null || flags.length > 0) && (
Temperament
{score != null && (
{[1, 2, 3, 4, 5].map(s => ( ))} ({score}/5 · staff-set)
)} {flags.length > 0 && (
{flags.map(flag => ( {flag} ))}
)}
)} {!readOnly && ( )}
); } function MedicalTab({ pet, readOnly }: { pet: Pet; readOnly: boolean }) { const alerts = pet.medicalAlerts ?? []; return (
{alerts.length === 0 ? (

No medical alerts on file.

) : ( alerts.map(alert => (
{alert.type}
{alert.description && (

{alert.description}

)}
)) )} {!readOnly && (

Changes to medical alerts will be flagged for staff review.

)}
); } function GroomingTab({ pet, readOnly }: { pet: Pet; readOnly: boolean }) { const coatType = pet.coatType; const cuts = pet.preferredCuts ?? []; return (
{coatType && ( {coatType}} /> )}
Preferred Cuts
{cuts.map(cut => ( {cut} ))} {cuts.length === 0 && None on file.}
{!readOnly && ( )}
); } function HistoryTab({ petHistory }: { petHistory: Appointment[] }) { return (
{petHistory.length === 0 ? (

No history yet

) : ( petHistory.map(appt => (

{appt.service ? "Grooming Service" : "Appointment"}

with {appt.staff?.name || "Unknown Groomer"}

{new Date(appt.startTime).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })} {appt.reportCardId && ( Report )}
)) )}
); }