'use client' import { useState } from 'react' import { toast } from 'sonner' import { Award, Plus } from 'lucide-react' import { PageHeader, PageTitle } from '@/components/ui/page-title' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from '@/components/ui/empty' import { useConfirm } from '@/components/ui/confirm' import { createCertification, updateCertification, deleteCertification, } from '@/lib/actions' type Cert = { id: string name: string description: string | null validity_months: number | null is_active: boolean created_at: string updated_at: string } type Draft = { name: string; description: string; validity_months: string } const emptyDraft: Draft = { name: '', description: '', validity_months: '' } function validityLabel(months: number | null): string { if (months == null) return 'Never expires' if (months === 1) return 'Expires 1 month after award' if (months % 12 === 0) { const y = months / 12 return `Expires ${y} year${y === 1 ? '' : 's'} after award` } return `Expires ${months} months after award` } function parseMonths(raw: string): number | null { const t = raw.trim() if (t === '') return null const n = Number(t) return Number.isInteger(n) && n > 0 ? n : null } export function CertificationsClient({ initial }: { initial: Cert[] }) { const confirm = useConfirm() const [certs, setCerts] = useState(initial) const [showNew, setShowNew] = useState(false) const [draft, setDraft] = useState(emptyDraft) const [editingId, setEditingId] = useState(null) const [editDraft, setEditDraft] = useState(emptyDraft) const [busy, setBusy] = useState(false) async function onCreate(e: React.FormEvent) { e.preventDefault() if (!draft.name.trim()) { toast.error('Name is required') return } setBusy(true) const res = await createCertification({ name: draft.name.trim(), description: draft.description.trim() || null, validity_months: parseMonths(draft.validity_months), }) setBusy(false) if ('error' in res && res.error) { toast.error(res.error) return } const id = (res as { data: { id: string } }).data.id setCerts(prev => [ { id, name: draft.name.trim(), description: draft.description.trim() || null, validity_months: parseMonths(draft.validity_months), is_active: true, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }, ...prev, ]) setDraft(emptyDraft) setShowNew(false) toast.success('Certification created') } function startEdit(c: Cert) { setEditingId(c.id) setEditDraft({ name: c.name, description: c.description ?? '', validity_months: c.validity_months == null ? '' : String(c.validity_months), }) } async function onSaveEdit(id: string) { if (!editDraft.name.trim()) { toast.error('Name is required') return } setBusy(true) const res = await updateCertification({ certificationId: id, name: editDraft.name.trim(), description: editDraft.description.trim() || null, validity_months: parseMonths(editDraft.validity_months), }) setBusy(false) if ('error' in res && res.error) { toast.error(res.error) return } setCerts(prev => prev.map(c => c.id === id ? { ...c, name: editDraft.name.trim(), description: editDraft.description.trim() || null, validity_months: parseMonths(editDraft.validity_months), } : c, ), ) setEditingId(null) toast.success('Saved') } async function onToggleActive(c: Cert) { const res = await updateCertification({ certificationId: c.id, is_active: !c.is_active }) if ('error' in res && res.error) { toast.error(res.error) return } setCerts(prev => prev.map(x => (x.id === c.id ? { ...x, is_active: !x.is_active } : x))) toast.success(c.is_active ? 'Archived' : 'Restored') } async function onDelete(c: Cert) { const ok = await confirm({ title: 'Delete certification', description: `"${c.name}" will be permanently removed. This is only allowed if it has never been granted.`, confirmText: 'Delete', destructive: true, }) if (!ok) return const res = await deleteCertification({ certificationId: c.id }) if ('error' in res && res.error) { toast.error(res.error) return } setCerts(prev => prev.filter(x => x.id !== c.id)) toast.success('Deleted') } const inputCls = 'w-full bg-background border border-border text-foreground font-sans text-sm rounded px-3 py-2 focus:outline-none focus:border-primary' return ( <> Certifications

Define the certifications your space tracks (e.g. a tool sign-off). Awarding and revoking a certification to a member is a separate permission (“Award and revoke certifications” / Instructor) and is done from a member’s row on the Members page.

{showNew && (
setDraft({ ...draft, name: e.target.value })} />