'use client' import { useMemo, useState } from 'react' import { useRouter } from 'next/navigation' import { toast } from 'sonner' import { PageHeader, PageTitle } from '@/components/ui/page-title' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from '@/components/ui/empty' import { CalendarCheck } from 'lucide-react' import { checkIn, checkOut } from '@/lib/actions' import { dayKey, visitDurationMinutes } from '@/lib/presence-logic' type Present = { id: string; name: string; isHost: boolean; isMe: boolean; checkedInAt: string; note: string | null } type Row = { id: string name: string isHost: boolean status: 'present' | 'checked_out' | 'stale' checkedInAt: string checkedOutAt: string | null checkInNote: string | null checkOutNote: string | null } const input = 'w-full bg-background border border-border text-foreground font-sans text-sm rounded px-3 py-2 focus:outline-none focus:border-primary' const STATUS: Record = { present: { label: 'Here now', cls: 'text-primary' }, checked_out: { label: 'Checked out', cls: 'text-muted-foreground' }, stale: { label: 'No checkout', cls: 'text-amber-600' }, } function fmtDur(mins: number): string { if (mins < 60) return `${mins}m` return `${Math.floor(mins / 60)}h ${mins % 60}m` } function fmtTime(iso: string) { return new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) } function fmtDayHeading(key: string) { const d = new Date(key + 'T00:00:00Z') return d.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' }) } export function AttendanceClient({ present, history }: { present: Present[]; history: Row[] }) { const router = useRouter() const [busy, setBusy] = useState(false) const [note, setNote] = useState('') const [query, setQuery] = useState('') const [day, setDay] = useState('') // '' = all days const me = present.find(p => p.isMe) const amHere = !!me const hosts = present.filter(p => p.isHost).length async function act(fn: () => Promise<{ error?: string } | { data: unknown }>, ok: string) { setBusy(true) const res = await fn() setBusy(false) if ('error' in res && res.error) return toast.error(res.error) setNote('') toast.success(ok) router.refresh() } const filtered = useMemo(() => { const q = query.trim().toLowerCase() return history.filter(r => { if (q && !r.name.toLowerCase().includes(q)) return false if (day && dayKey(r.checkedInAt) !== day) return false return true }) }, [history, query, day]) const groups = useMemo(() => { const m = new Map() for (const r of filtered) { const k = dayKey(r.checkedInAt) || 'unknown' ;(m.get(k) ?? m.set(k, []).get(k)!).push(r) } return [...m.entries()].sort((a, b) => b[0].localeCompare(a[0])) }, [filtered]) const uniqueMembers = useMemo(() => new Set(filtered.map(r => r.name)).size, [filtered]) return ( <> Attendance
{/* Here now + self check-in/out */}

Here now ({present.length}{hosts > 0 ? ` · ${hosts} hosting` : ''})

{present.length === 0 ? (

Nobody is checked in right now.

) : (
    {present.map(p => (
  • {p.name}{p.isMe ? ' (you)' : ''} {p.note && {p.note}} {p.isHost && Host} since {fmtTime(p.checkedInAt)}
  • ))}
)}
setNote(e.target.value)} />
{amHere ? ( ) : ( <> )}
{/* History with search + day filter */}

History · {filtered.length} visit{filtered.length === 1 ? '' : 's'} · {uniqueMembers} member{uniqueMembers === 1 ? '' : 's'}

setQuery(e.target.value)} /> setDay(e.target.value)} /> {(query || day) && ( )}
{filtered.length === 0 ? ( {history.length === 0 ? 'No visits recorded yet' : 'No visits match'} {history.length === 0 ? 'When members check in, their visits appear here.' : 'Try a different name or day, or clear the filters.'} ) : (
{groups.map(([key, rows]) => (

{key === 'unknown' ? 'Unknown date' : fmtDayHeading(key)} · {rows.length}

{rows.map(r => { const s = STATUS[r.status] return (
{r.name} {r.isHost && Host} {s.label}
{(r.checkInNote || r.checkOutNote) && (

{r.checkInNote ? `in: ${r.checkInNote}` : ''} {r.checkInNote && r.checkOutNote ? ' · ' : ''} {r.checkOutNote ? `out: ${r.checkOutNote}` : ''}

)}
{fmtTime(r.checkedInAt)}{r.checkedOutAt ? `–${fmtTime(r.checkedOutAt)}` : ''}
{fmtDur(visitDurationMinutes(r.checkedInAt, r.checkedOutAt))} {!r.checkedOutAt && r.status === 'present' ? ' so far' : r.checkedOutAt ? ' visit' : ''}
) })}
))}
)}
) }