import { useEffect, useState } from "react"; import type { Client, Pet, Service, Staff } from "@groombook/types"; // ─── Types ──────────────────────────────────────────────────────────────────── interface PetSlot { petId: string; serviceId: string; staffId: string; endTime: string; // HH:MM } interface GroupAppointment { id: string; petId: string; petName?: string; serviceId: string; serviceName?: string; staffId: string | null; staffName?: string | null; status: string; startTime: string; endTime: string; } interface AppointmentGroup { id: string; clientId: string; notes: string | null; createdAt: string; appointments: GroupAppointment[]; } // ─── Helpers ────────────────────────────────────────────────────────────────── function fmtTime(iso: string) { return new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } function fmtDate(iso: string) { return new Date(iso).toLocaleDateString(); } const STATUS_COLORS: Record = { scheduled: "#3b82f6", confirmed: "#10b981", in_progress: "#f59e0b", completed: "#6b7280", cancelled: "#ef4444", no_show: "#9ca3af", }; // ─── New Group Booking Form ─────────────────────────────────────────────────── function NewGroupBookingForm({ clients, pets, services, staff, onCreated, onClose, }: { clients: Client[]; pets: Pet[]; services: Service[]; staff: Staff[]; onCreated: () => void; onClose: () => void; }) { const [clientId, setClientId] = useState(""); const [date, setDate] = useState(() => new Date().toISOString().slice(0, 10)); const [startTime, setStartTime] = useState("09:00"); const [notes, setNotes] = useState(""); const [petSlots, setPetSlots] = useState([ { petId: "", serviceId: "", staffId: "", endTime: "10:00" }, { petId: "", serviceId: "", staffId: "", endTime: "10:00" }, ]); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const clientPets = pets.filter((p) => p.clientId === clientId); const activeServices = services.filter((s) => s.active); const activeStaff = staff.filter((s) => s.active); function addPetSlot() { setPetSlots((prev) => [ ...prev, { petId: "", serviceId: "", staffId: "", endTime: "10:00" }, ]); } function removePetSlot(i: number) { setPetSlots((prev) => prev.filter((_, idx) => idx !== i)); } function updateSlot(i: number, field: keyof PetSlot, value: string) { setPetSlots((prev) => prev.map((slot, idx) => idx === i ? { ...slot, [field]: value } : slot ) ); } // Auto-set end time based on service duration when service changes function handleServiceChange(i: number, serviceId: string) { const svc = services.find((s) => s.id === serviceId); if (svc && startTime) { const [h, m] = startTime.split(":").map(Number); const totalMins = (h ?? 0) * 60 + (m ?? 0) + svc.durationMinutes; const endH = String(Math.floor(totalMins / 60) % 24).padStart(2, "0"); const endM = String(totalMins % 60).padStart(2, "0"); updateSlot(i, "serviceId", serviceId); updateSlot(i, "endTime", `${endH}:${endM}`); } else { updateSlot(i, "serviceId", serviceId); } } async function submit(e: React.FormEvent) { e.preventDefault(); if (!clientId) { setError("Please select a client"); return; } if (petSlots.length < 2) { setError("Add at least 2 pets"); return; } if (petSlots.some((s) => !s.petId || !s.serviceId)) { setError("Each pet slot needs a pet and service selected"); return; } setSaving(true); setError(null); const payload = { clientId, startTime: `${date}T${startTime}:00.000Z`, notes: notes || undefined, pets: petSlots.map((slot) => ({ petId: slot.petId, serviceId: slot.serviceId, staffId: slot.staffId || undefined, endTime: `${date}T${slot.endTime}:00.000Z`, })), }; try { const res = await fetch("/api/appointment-groups", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!res.ok) { const err = (await res.json()) as { error?: string }; throw new Error(err.error ?? `HTTP ${res.status}`); } onCreated(); } catch (e: unknown) { setError(e instanceof Error ? e.message : "Failed to create group booking"); } finally { setSaving(false); } } return (

New Group Booking

Book multiple pets from the same client in a single visit. Each pet can have a different groomer.

setDate(e.target.value)} required style={inputStyle} /> setStartTime(e.target.value)} required style={inputStyle} />
Pets ({petSlots.length})
{petSlots.map((slot, i) => (
Pet {i + 1} {petSlots.length > 2 && ( )}
updateSlot(i, "endTime", e.target.value)} required style={inputStyle} />
))}