import { type ReactNode } from 'react'; import { FORMAT_LOCALES, type AuditEntry, type Locale } from '@wellformed/shared'; import { useAudit } from '../api/hooks'; import { useI18n } from '../i18n'; import { useWorkspace } from '../state/workspace'; export function AuditPanel(): ReactNode { const ws = useWorkspace(); const { t } = useI18n(); const query = useAudit({ table: ws.tableName, rowId: ws.auditRowId ?? undefined, }); const entries = query.data?.pages.flatMap((p) => p.entries) ?? []; return ( ); } function AuditRow({ entry }: { entry: AuditEntry }): ReactNode { const ws = useWorkspace(); const { t, locale } = useI18n(); // The trail stores identifiers, which outlive renames; the line above the // change speaks labels while the column exists and falls back to the // identifier once it does not. const table = entry.table ? ws.tables.find((tb) => tb.name === entry.table) : undefined; const column = entry.column ? (table?.columns.find((c) => c.name === entry.column)?.label ?? entry.column) : null; const location = [table?.label ?? entry.table, entry.rowLabel].filter(Boolean).join(' · '); const where = column ? `${location} · ${column}` : location; return (
{entry.who} {/* The account stays the author; this says the work came through an assistant, and which one. Reading the trail is how you find that out, so it has to be visible at a glance rather than on hover. */} {entry.via && ( {entry.via} )} {entry.kind !== 'edit' && {entry.kind}} {formatTime(entry.ts, locale)}
{where &&
{where}
}
{entry.old && ( <> {entry.old}{' → '} )} {entry.nw}
); } /** SQLite writes `YYYY-MM-DD HH:MM:SS` in UTC; show it in the reader's zone. */ function formatTime(ts: string, locale: Locale): string { const parsed = new Date(ts.includes('T') ? ts : `${ts.replace(' ', 'T')}Z`); if (Number.isNaN(parsed.getTime())) return ts; const today = new Date(); const sameDay = parsed.getFullYear() === today.getFullYear() && parsed.getMonth() === today.getMonth() && parsed.getDate() === today.getDate(); const time = new Intl.DateTimeFormat(FORMAT_LOCALES[locale], { hour: '2-digit', minute: '2-digit', }); if (sameDay) return time.format(parsed); const day = new Intl.DateTimeFormat(FORMAT_LOCALES[locale], { month: 'short', day: 'numeric' }); return `${day.format(parsed)} ${time.format(parsed)}`; }