import { useEffect, useState } from "react"; import type { Service } from "@groombook/types"; interface ServiceForm { name: string; description: string; priceStr: string; durationMinutes: number; defaultBufferMinutes: number; active: boolean; } const EMPTY_FORM: ServiceForm = { name: "", description: "", priceStr: "", durationMinutes: 60, defaultBufferMinutes: 0, active: true, }; export function ServicesPage() { const [services, setServices] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editing, setEditing] = useState(null); const [showForm, setShowForm] = useState(false); const [form, setForm] = useState(EMPTY_FORM); const [saving, setSaving] = useState(false); const [formError, setFormError] = useState(null); const [togglingId, setTogglingId] = useState(null); async function load() { const r = await fetch("/api/services?includeInactive=true"); if (!r.ok) throw new Error(`HTTP ${r.status}`); const data = (await r.json()) as Service[]; setServices(data); } useEffect(() => { load() .catch((e: unknown) => setError(e instanceof Error ? e.message : "Unknown error")) .finally(() => setLoading(false)); }, []); function openNew() { setEditing(null); setForm(EMPTY_FORM); setFormError(null); setShowForm(true); } function openEdit(s: Service) { setEditing(s); setForm({ name: s.name, description: s.description ?? "", priceStr: (s.basePriceCents / 100).toFixed(2), durationMinutes: s.durationMinutes, defaultBufferMinutes: s.defaultBufferMinutes ?? 0, active: s.active, }); setFormError(null); setShowForm(true); } async function submit(e: React.FormEvent) { e.preventDefault(); const price = parseFloat(form.priceStr); if (isNaN(price) || price <= 0) { setFormError("Price must be a positive number."); return; } setSaving(true); setFormError(null); try { const body = { name: form.name, description: form.description || undefined, basePriceCents: Math.round(price * 100), durationMinutes: form.durationMinutes, defaultBufferMinutes: form.defaultBufferMinutes, active: form.active, }; const res = editing ? await fetch(`/api/services/${editing.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }) : await fetch("/api/services", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!res.ok) { const err = (await res.json()) as { error?: string }; throw new Error(err.error ?? `HTTP ${res.status}`); } setShowForm(false); await load(); } catch (e: unknown) { setFormError(e instanceof Error ? e.message : "Failed to save"); } finally { setSaving(false); } } async function toggleActive(s: Service) { setTogglingId(s.id); try { await fetch(`/api/services/${s.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ active: !s.active }), }); await load(); } finally { setTogglingId(null); } } if (loading) return

Loading…

; if (error) return

Error: {error}

; return (

Services

{services.length === 0 ? (

No services configured yet.

) : (
{["Name", "Description", "Price", "Duration", "Default Buffer", "Status", ""].map((h) => ( ))} {services.map((s) => ( ))}
{h}
{s.name} {s.description ?? "—"} ${(s.basePriceCents / 100).toFixed(2)} {s.durationMinutes} min {(s as Service & { defaultBufferMinutes?: number }).defaultBufferMinutes ?? 0} min
)} {showForm && ( setShowForm(false)}>

{editing ? "Edit Service" : "New Service"}

setForm((f) => ({ ...f, name: e.target.value }))} required style={inputStyle} />