import type { TFunction } from "i18next"; import { relativeTime, formatDate } from "../lib/format"; import i18n from "../i18n"; import type { AuditRow } from "../lib/api"; import type { Column } from "./DataTable"; function actionLabel(t: TFunction, action: string): string { // Canonical key → localized label; falls back to the raw key for any unmapped action. return t(`auditActions.${action}`, action); } function fmt(v: unknown): string { return v == null || v === "" ? "—" : String(v); } /** A compact before→after (or after-only) rendering of the JSON snapshots, e.g. "role: user → admin". */ function changeText(row: AuditRow): string | null { const { before, after } = row; if (before && after) { return Object.keys(after) .map((k) => `${k}: ${fmt(before[k])} → ${fmt(after[k])}`) .join(", "); } if (after) { return Object.entries(after) .map(([k, v]) => `${k}: ${fmt(v)}`) .join(", "); } return null; } /** Columns for the admin audit-log DataTable. `actionOptions` are the distinct actions present in * the data (built by the page) so the Action column's select filter only offers real values. */ export function auditColumns( t: TFunction, actionOptions: { value: string; label: string }[], ): Column[] { return [ { key: "when", header: t("audit.cols.when"), nowrap: true, sortable: true, sortValue: (r) => r.created_at ?? "", cardPrimary: true, render: (r) => ( {r.created_at ? relativeTime(r.created_at) : "—"} ), }, { key: "actor", header: t("audit.cols.actor"), sortable: true, sortValue: (r) => r.actor ?? "", filter: { kind: "text", get: (r) => r.actor ?? t("audit.system") }, render: (r) => r.actor ? ( {r.actor} ) : ( {t("audit.system")} ), }, { key: "action", header: t("audit.cols.action"), nowrap: true, sortable: true, sortValue: (r) => actionLabel(t, r.action), filter: { kind: "select", options: actionOptions, test: (r, v) => r.action === v }, render: (r) => {actionLabel(t, r.action)}, }, { key: "details", header: t("audit.cols.details"), render: (r) => { const change = changeText(r); return (
{r.summary &&
{r.summary}
} {change &&
{change}
}
); }, }, ]; }