'use client' import { useEffect, useState, useCallback } from 'react' import { toast } from 'sonner' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { useConfirm } from '@/components/ui/confirm' import { listMemberCertifications, listCertifications, grantCertification, revokeCertification, renewCertification, } from '@/lib/actions' import { certificationStatus, CERT_STATUS_LABEL, type CertStatus, } from '@/lib/certifications-logic' type Grant = { id: string certification_id: string granted_at: string expires_at: string | null revoked_at: string | null revoked_reason: string | null note: string | null certifications: { name: string; validity_months: number | null; is_active: boolean } | null } type CertType = { id: string name: string is_active: boolean } const STATUS_CLASS: Record = { active: 'text-primary bg-primary/10 border-primary/20', expiring_soon: 'text-amber-600 bg-amber-50 border-amber-200', expired: 'text-red-600 bg-red-50 border-red-200', revoked: 'text-muted-foreground bg-muted border-border', } export function MemberCertificationsDialog({ member, onClose, }: { member: { id: string; display_name: string | null } | null onClose: () => void }) { const confirm = useConfirm() const open = !!member const [loading, setLoading] = useState(false) const [grants, setGrants] = useState([]) const [types, setTypes] = useState([]) const [certId, setCertId] = useState('') const [note, setNote] = useState('') const [expires, setExpires] = useState('') const [busy, setBusy] = useState(false) const load = useCallback(async (memberId: string) => { setLoading(true) const [g, t] = await Promise.all([ listMemberCertifications({ memberId }), listCertifications(), ]) setLoading(false) if ('error' in g && g.error) { toast.error(g.error) } else { setGrants(('data' in g ? g.data : []) ?? []) } if (!('error' in t) || !t.error) { setTypes(((t as { data: CertType[] }).data ?? []).filter(c => c.is_active)) } }, []) useEffect(() => { if (member) { setCertId('') setNote('') setExpires('') load(member.id) } }, [member, load]) async function onGrant(e: React.FormEvent) { e.preventDefault() if (!member) return if (!certId) { toast.error('Pick a certification') return } setBusy(true) const res = await grantCertification({ memberId: member.id, certificationId: certId, note: note.trim() || null, expires_at: expires || null, }) setBusy(false) if ('error' in res && res.error) { toast.error(res.error) return } toast.success('Certification awarded') setCertId('') setNote('') setExpires('') load(member.id) } async function onRevoke(g: Grant) { const ok = await confirm({ title: 'Revoke certification', description: `Revoke "${g.certifications?.name ?? 'this certification'}" from ${member?.display_name ?? 'this member'}? The record is kept as history.`, confirmText: 'Revoke', destructive: true, }) if (!ok || !member) return const res = await revokeCertification({ memberCertificationId: g.id }) if ('error' in res && res.error) { toast.error(res.error) return } toast.success('Revoked') load(member.id) } async function onRenew(g: Grant) { if (!member) return const res = await renewCertification({ memberCertificationId: g.id }) if ('error' in res && res.error) { toast.error(res.error) return } toast.success('Renewed') load(member.id) } return ( { if (!o) onClose() }}> Certifications — {member?.display_name ?? 'Member'}

Award a certification

setNote(e.target.value)} placeholder="Note (optional, e.g. completed in class)" className="w-full bg-background border border-border text-foreground font-sans text-sm rounded px-2 py-2 focus:outline-none focus:border-primary" />
{loading ? (

Loading…

) : grants.length === 0 ? (

No certifications yet.

) : (
    {grants.map(g => { const status = certificationStatus(g) return (
  • {g.certifications?.name ?? 'Certification'} {CERT_STATUS_LABEL[status]}

    granted {new Date(g.granted_at).toLocaleDateString()} {g.expires_at ? ` · expires ${new Date(g.expires_at).toLocaleDateString()}` : ' · no expiry'} {g.revoked_at ? ` · revoked ${new Date(g.revoked_at).toLocaleDateString()}` : ''}

    {g.note &&

    {g.note}

    }
    {!g.revoked_at && ( <> )}
  • ) })}
)}
) }