import { useRef, useEffect } from 'react' import { Badge } from '../ui/Badge.tsx' import { Button } from '../ui/Button.tsx' import { parseDescription } from '../../lib/ticket.ts' import type { Ticket } from '../../lib/types.ts' function formatDate(iso: string): string { return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) } function Checkbox({ checked, indeterminate = false, disabled = false, onChange, ariaLabel, }: { checked: boolean indeterminate?: boolean disabled?: boolean onChange: (checked: boolean) => void ariaLabel: string }) { const ref = useRef(null) useEffect(() => { if (ref.current) ref.current.indeterminate = indeterminate }, [indeterminate]) return ( onChange(e.target.checked)} className={` h-3.5 w-3.5 rounded border border-border-200 bg-bg-300 checked:bg-fg-100 checked:border-fg-100 focus-visible:ring-2 focus-visible:ring-ring-100 focus-visible:outline-none ${disabled ? 'opacity-30 cursor-not-allowed' : 'cursor-pointer'} `} /> ) } interface PaginationProps { page: number totalPages: number total: number pageSize: number onPrev: () => void onNext: () => void } interface AdminTableProps { tickets: Ticket[] onOpen: (ticket: Ticket) => void currentUserId: string | null selection: Set onSelectionChange: (selection: Set) => void pagination: PaginationProps } export function AdminTable({ tickets, onOpen, currentUserId, selection, onSelectionChange, pagination, }: AdminTableProps) { const { page, totalPages, total, pageSize, onPrev, onNext } = pagination const start = (page - 1) * pageSize + 1 const end = Math.min(page * pageSize, total) // When unauthenticated, currentUserId is null and all local tickets have userId: null — // the user owns all of them. When authenticated, only match on userId. const isOwned = (ticket: Ticket) => currentUserId === null ? true : ticket.userId === currentUserId const selectableIds = tickets.filter(isOwned).map(t => t.id) const selectedOnPage = selectableIds.filter(id => selection.has(id)) const allSelected = selectableIds.length > 0 && selectedOnPage.length === selectableIds.length const someSelected = selectedOnPage.length > 0 && !allSelected const handleHeaderChange = (checked: boolean) => { const next = new Set(selection) if (checked) { selectableIds.forEach(id => next.add(id)) } else { selectableIds.forEach(id => next.delete(id)) } onSelectionChange(next) } const handleRowChange = (id: string, checked: boolean) => { const next = new Set(selection) checked ? next.add(id) : next.delete(id) onSelectionChange(next) } if (tickets.length === 0 && total === 0) { return (

No tickets in the system.

) } const hasBilling = tickets.some(t => t.type === 'billing') return (
{/* Select-all checkbox */} {(['Subject', 'User', 'Type', 'Status', ...(hasBilling ? ['Transaction'] : []), 'Description', 'Created'] as const).map(col => ( ))} {tickets.map(ticket => { const { txnId, txnLine, body: displayDescription } = parseDescription(ticket.description) const hasTxn = ticket.type === 'billing' && txnId !== null const owned = isOwned(ticket) const isSelected = selection.has(ticket.id) return ( onOpen(ticket)} > {/* Row checkbox — stop propagation so clicking it doesn't open the modal */} {hasBilling && ( )} ) })}
{col}
e.stopPropagation()} > handleRowChange(ticket.id, checked)} ariaLabel={`Select ticket: ${ticket.subject}`} />
{ticket.subject} {owned && ( mine )}
{ticket.username ?? guest} {ticket.type.replace('-', ' ')} {hasTxn ? ( {txnLine!.split(' — ')[0]} ) : ( )} {displayDescription || No description} {formatDate(ticket.createdAt)}
{/* Pagination footer */}

{total === 0 ? 'No tickets' : `${start}–${end} of ${total}`}

{page} / {totalPages}
) }