feat(admin): replace overview section-grid with audit-log preview + system snapshot

The `/admin` overview rendered a six-card status grid plus a quick-jump
list of every admin section. Both duplicated navigation that already
lives in the sidebar (and the global nav after Phase 4b), so the
landing page told the admin nothing they didn't already see two clicks
away.

Replace the duplicated nav with two at-a-glance panes that *only* the
overview can show:

- Welcome card greets the signed-in admin by username and confirms the
  admin context (so the role indicator the shell shows is reinforced
  in the page body, not just the chrome).
- `<SystemStatusSummary>` reuses the existing `useSystemStatus()` hook
  + `/api/version` to print app version, DB up/down, worker state,
  process start time, build SHA, and built-at. Same data source as
  `<SystemStatusSection>` so we don't double-fetch.
- `<RecentAuditPreview>` lists the latest 10 audit entries from
  `/api/admin/audit-log?limit=10&filter=auth` with a "View all" link to
  `/admin/login-overview`. Reuses the same auth-action label map the
  full section uses.

The previous `<StatusCardGrid>` component is now unused — removed
entirely. The test file kept its name (`status-card-grid.test.tsx`) so
git history follows the rename, but it now asserts the new overview
composition: welcome card paints with the admin's username, snapshot
heading present, audit preview links to `/admin/login-overview`, and
the legacy "Maintenance"/"Monitoring" card titles + "Manage users"
CTAs are *not* rendered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-09 20:18:07 +02:00
1 parent 3e45a7ba53
commit a967895808
8 files changed
+665 -489

No files matched your search

+8 -28
View File
@@ -4,6 +4,7 @@ import { annotate } from "@/lib/logging/context";
import { apiSuccess } from "@/lib/api-response";
import { summarize, type DataPoint } from "@/lib/analytics/trends";
import { getBpTargets } from "@/lib/analytics/bp-targets";
import { computeBpInTargetPct } from "@/lib/analytics/bp-in-target";
import type { MeasurementType } from "@/generated/prisma/client";
import { measurementTypeEnum } from "@/lib/validations/measurement";
@@ -75,34 +76,13 @@ export const GET = apiHandler(async () => {
}),
]);
if (sysData.length > 0 && diaData.length > 0) {
let inTarget = 0;
for (const sys of sysData) {
const closestDia = diaData.reduce((closest, dia) =>
Math.abs(dia.measuredAt.getTime() - sys.measuredAt.getTime()) <
Math.abs(closest.measuredAt.getTime() - sys.measuredAt.getTime())
? dia
: closest,
);
const timeDiff = Math.abs(
closestDia.measuredAt.getTime() - sys.measuredAt.getTime(),
);
if (timeDiff < 5 * 60 * 1000) {
if (
sys.value >= bpTargets.sysLow &&
sys.value <= bpTargets.sysHigh &&
closestDia.value >= bpTargets.diaLow &&
closestDia.value <= bpTargets.diaHigh
) {
inTarget++;
}
}
}
bpInTargetPct =
sysData.length > 0
? Math.round((inTarget / sysData.length) * 100)
: null;
}
// Pure pair-and-count helper (see lib/analytics/bp-in-target.ts).
// Replaces an earlier inline implementation that divided by
// `sysData.length` and ignored a same-Berlin-day fallback for
// imports rounded past the 5-minute window — which made the tile
// read 0 % even when readings clearly sat inside target.
const result = computeBpInTargetPct(sysData, diaData, bpTargets);
bpInTargetPct = result?.pct ?? null;
}
// Per-context glucose summaries (canonical mg/dL).