import Link from "next/link"; import { ActionForm, SubmitButton } from "@/components/forms"; import { DateTime } from "@/components/time"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import type { AuditEntry } from "@/lib/api"; import { appeal } from "./cases/actions"; const tagLinks: Record string> = { catalog: (id) => `/console/catalogs/${id}`, product: (id) => `/products/${id}`, room: (id) => `/rooms/${id}/edit`, }; /** Audit entries: when, who, what, which resources (linked), and the details. */ export function AuditTable({ entries, empty = "Nothing yet." }: { entries: AuditEntry[]; empty?: string }) { if (entries.length === 0) return

{empty}

; return ( When Who What About Detail {entries.map((e) => ( {e.actor ? ( {e.actor} ) : ( hub )} {e.action} {Object.entries(e.tags as Record).map(([kind, id]) => { if (!id) return null; const name = (e.tagNames as Record)[id] ?? id; const href = tagLinks[kind]?.(id); return ( {kind} {href ? ( {name} ) : ( name )} ); })} {Object.keys(e.tags).length === 0 && (e.action.startsWith("file.") && /^[0-9a-f]{64}$/.test(e.target) ? ( file {e.target.slice(0, 12)}… ) : ( e.target ))} {detailText(e.detail)} {e.appealable && (
Appeal
Send
)}
))}
); } function detailText(detail: unknown): string { return Object.entries((detail ?? {}) as Record) .map(([k, v]) => { if (k === "changed" && v && typeof v === "object") return Object.entries(v as Record) .map(([f, c]) => `${f}: ${String(c?.from)} → ${String(c?.to)}`) .join(", "); return `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`; }) .join(", "); }