Files
apf_portal/apps/portal-admin
Julien Gautier 299e66fde5
CI / commits (pull_request) Successful in 3m41s
CI / scan (pull_request) Successful in 3m43s
CI / check (pull_request) Successful in 4m23s
CI / a11y (pull_request) Successful in 1m57s
CI / perf (pull_request) Successful in 3m38s
feat(portal-admin): audit log tabs (Table / Charts) + server-side stats consumption
PR 2 of the tabs + full-result-charts chantier — closes the loop on
the BFF endpoint shipped in #173.

Two changes:

1. Wraps the audit page content in two tabs — `Table` (default) and
   `Charts`. WAI-ARIA pattern: `role="tablist"` on the container,
   `role="tab"` on each button, roving tabindex (the active tab
   reachable via Tab, inactive ones via arrow keys). The Charts
   panel hides until the user switches to it.

2. Charts now consume the BFF stats endpoint (full filtered set,
   Redis-cached 5 min) instead of the per-page aggregations the
   previous PR shipped. The three client-side computeds
   (`dailyVolume`, `outcomeBreakdown`, `dailyByEventType`,
   `totalOnPage`) are replaced by:

     * `stats: signal<AdminAuditStats | null>` — filled by the
       `GET /api/admin/audit/stats` call.
     * `statsLoading` + `statsError` — own loading / error state,
       distinct from the table's so a failed stats call doesn't
       mask the table view.
     * `fetchStats()` — strips pagination from the filter shape,
       calls the new `AuditEventsService.stats()` method.

Lazy fetch policy:
  * Default tab Table → no stats call on first render. Saves a DB
    round-trip when the admin only wanted to scan rows.
  * Switch to Charts → fetch stats if not already loaded.
  * Filter change (Apply / Reset / actor pivot) → invalidate stats
    (set to null). If Charts tab is currently active, re-fetch
    immediately. Otherwise wait for the user to open it.
  * Pagination (next / previous) → does NOT invalidate stats. The
    underlying filtered set is unchanged, so re-opening Charts
    after paginating is free.

The Charts panel's note now reads "Aggregations are computed across
the full filtered set (server-side), not just the events on the
current page. Results are cached for 5 minutes per filter
combination." — honest disclosure of the new scope + cache.

Audit chunk: 84.5 KB gzip (essentially unchanged from #172's 84
KB; the tab markup + stats branch add ~50 LOC of plumbing offset
by removal of the per-page aggregation computeds). audit.scss
ticks up to 7.5 KB which exceeds the 6 KB warning threshold but
stays under the 8 KB error threshold — acceptable for a single-
PR add of the tab styling.

Verification:
  * 62 portal-admin specs pass (was 57; +5 net: 7 new tabs / stats
    tests, 3 old per-page chart tests dropped, 1 prior test reframed).
  * `pnpm nx run-many -t lint test build --projects=portal-shell,
    portal-admin,shared-charts,portal-bff` — green.
2026-05-16 23:47:16 +02:00
..