import { useEffect, useState } from "react"; import type { Staff } from "@groombook/types"; interface StaffForm { name: string; email: string; role: "groomer" | "receptionist" | "manager"; } const EMPTY_FORM: StaffForm = { name: "", email: "", role: "groomer" }; export function StaffPage() { const [staff, setStaff] = useState([]); const [currentUser, setCurrentUser] = useState(null); 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 [formError, setFormError] = useState(null); const [saving, setSaving] = useState(false); const [togglingId, setTogglingId] = useState(null); const [toggleError, setToggleError] = useState(null); async function load() { const [staffRes, meRes] = await Promise.all([ fetch("/api/staff?includeInactive=true"), fetch("/api/staff/me"), ]); if (!staffRes.ok) throw new Error(`HTTP ${staffRes.status}`); if (!meRes.ok) throw new Error(`HTTP ${meRes.status}`); setStaff((await staffRes.json()) as Staff[]); setCurrentUser((await meRes.json()) as Staff); } 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: Staff) { setEditing(s); setForm({ name: s.name, email: s.email, role: s.role }); setFormError(null); setShowForm(true); } async function submit(e: React.FormEvent) { e.preventDefault(); setSaving(true); setFormError(null); try { const res = editing ? await fetch(`/api/staff/${editing.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: form.name, role: form.role }) }) : await fetch("/api/staff", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(form) }); 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: Staff) { setTogglingId(s.id); setToggleError(null); try { const res = await fetch(`/api/staff/${s.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ active: !s.active }) }); if (!res.ok) { const err = (await res.json()) as { error?: string }; setToggleError(err.error ?? `HTTP ${res.status}`); return; } await load(); } finally { setTogglingId(null); } } async function toggleSuperUser(s: Staff) { setTogglingId(s.id); setToggleError(null); try { const res = await fetch(`/api/staff/${s.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ isSuperUser: !s.isSuperUser }), }); if (!res.ok) { const err = (await res.json()) as { error?: string }; setToggleError(err.error ?? `HTTP ${res.status}`); return; } await load(); } finally { setTogglingId(null); } } const isLastSuperUser = (s: Staff) => s.isSuperUser && staff.filter((st) => st.isSuperUser).length === 1; if (loading) return

Loading…

; if (error) return

Error: {error}

; return (

Staff

{toggleError && (

{toggleError}

)} {staff.length === 0 ? (

No staff members yet.

) : (
{["Name", "Email", "Role", "Super User", "Status", ""].map((h) => ( ))} {staff.map((s) => ( ))}
{h}
{s.name} {s.email} {s.role} {currentUser?.isSuperUser ? ( ) : ( s.isSuperUser ? ( Super User ) : ( ) )}
)} {showForm && (
{ if (e.target === e.currentTarget) setShowForm(false); }} >

{editing ? "Edit Staff" : "New Staff Member"}

setForm((f) => ({ ...f, name: e.target.value }))} required style={inputStyle} />
{!editing && (
setForm((f) => ({ ...f, email: e.target.value }))} required style={inputStyle} />
)}
{formError &&

{formError}

}
)}
); } const btnStyle: React.CSSProperties = { padding: "0.4rem 0.85rem", border: "1px solid #d1d5db", borderRadius: 6, background: "#fff", cursor: "pointer", fontSize: 13, fontWeight: 500 }; const inputStyle: React.CSSProperties = { width: "100%", padding: "0.45rem 0.6rem", border: "1px solid #d1d5db", borderRadius: 6, fontSize: 14, boxSizing: "border-box" }; const labelStyle: React.CSSProperties = { display: "block", fontWeight: 600, marginBottom: "0.25rem", fontSize: 13, color: "#374151" }; const tdStyle: React.CSSProperties = { padding: "0.55rem 0.75rem", borderBottom: "1px solid #f3f4f6" };