The ~124 px-frozen text-[Npx] labels didn't track the Settings font-scale slider while their rem-sized neighbours did. Add text-2xs (0.6875rem) / text-3xs (0.625rem) font-size-ONLY tokens (no paired line-height -> matches the arbitrary they replace, zero vertical-rhythm shift) and codemod every text-[Npx] to a rem class (rare 8/9/12/13px -> rem arbitraries). Also: --hdr-h 68px -> 4.25rem so header content clears the rem-sized pills at scale 1.3 instead of sliding under; NavSidebar/SidePanel slot widths + their icon/badge px sizes go rem so the rails keep pace with their (now-scaling) labels; a w-24/h-[54px] thumb -> h-13.5 to preserve 16:9 at scale. Media thumbnails, page-width caps and fixed container widths deliberately stay px. Verified via getComputedStyle at scale 1.06/1.3: text-2xs 11.66->14.3px, header clearance 72->88px, nav slot 246->302px — all scale proportionally.
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
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<AuditRow>[] {
|
|
return [
|
|
{
|
|
key: "when",
|
|
header: t("audit.cols.when"),
|
|
nowrap: true,
|
|
sortable: true,
|
|
sortValue: (r) => r.created_at ?? "",
|
|
cardPrimary: true,
|
|
render: (r) => (
|
|
<span
|
|
className="text-muted tabular-nums"
|
|
title={r.created_at ? formatDate(r.created_at, i18n.language) : ""}
|
|
>
|
|
{/* `|| "—"` not a null check: relativeTime is empty for a missing OR unreadable date. */}
|
|
{relativeTime(r.created_at) || "—"}
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
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 ? (
|
|
<span className="truncate">{r.actor}</span>
|
|
) : (
|
|
<span className="text-muted italic">{t("audit.system")}</span>
|
|
),
|
|
},
|
|
{
|
|
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) => <span className="font-medium">{actionLabel(t, r.action)}</span>,
|
|
},
|
|
{
|
|
key: "details",
|
|
header: t("audit.cols.details"),
|
|
render: (r) => {
|
|
const change = changeText(r);
|
|
return (
|
|
// wrap-break-word (wrap), NOT truncate: in the auto-layout table a `truncate` (nowrap) cell
|
|
// sizes to its longest row, so one long summary/change stretched the whole table past the
|
|
// card (the row rules bled into the page). Wrapping keeps every row inside the container.
|
|
<div className="min-w-0">
|
|
{r.summary && <div className="wrap-break-word">{r.summary}</div>}
|
|
{change && <div className="text-2xs text-muted wrap-break-word">{change}</div>}
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
}
|