feat(admin): activity panel on admin dashboard — system-wide weekly contribution counts #335
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
#324 ships the admin dashboard at
/adminwith three panels: Invites, OCR, and a placeholder where Activity was originally planned. The Activity panel was descoped from #324 to keep that issue focused on the core dashboard structure and the OCR + Invites data pipelines.The Activity panel shows system-wide contribution health at a glance — how much transcription, annotation, upload, and conversation activity happened across all users in the last 7 days. This is the answer to the admin question: "Is the archive being actively worked on, or has it gone quiet?"
No new data pipelines are needed. The audit log already records all four event types.
AuditLogQueryRepository.getPulseStats()already counts three of the four; the fourth (COMMENT_ADDED) is one line of SQL.Non-goals
Proposed panel
Four stat cards in a 2×2 grid on mobile, 1×4 row on desktop. Numbers prominent (
font-serif text-3xl font-bold text-ink), labels small (font-sans text-xs text-ink-3 uppercase tracking-widest). Reuse theOcrStatCards.sveltevisual pattern — it already exists inadmin/ocr/for exactly this purpose.Implementation plan
Backend
Step 1 — Add
getSystemActivityCounts()toAuditLogQueryRepository.Adapt the existing
getPulseStats()native query (lines 114–129). Changes:COMMENT_ADDEDto the counted kindsyourPagesper-user column (nouserIdparameter)annotatedsplit; countANNOTATION_CREATEDas a flat totalNew projection interface
SystemActivityRow(same package asPulseStatsRow):Step 2 — Extend
AdminDashboardDTOwith anactivityfield.Step 3 — Wire into
AdminDashboardService.Frontend
Step 4 — Create
ActivityPanel.svelteinfrontend/src/lib/components/admin/.Props:
activity: AdminDashboardDTO['activity']Renders four stat cards using the
OcrStatCards.sveltevisual pattern. Labels come from Paraglide keys (see i18n section).Step 5 — Add
ActivityPanelto+page.svelte.Replace the placeholder / empty section with
<ActivityPanel activity={data.dashboard.activity} />. No layout changes — the card grid already exists from #324.Step 6 — Regenerate API types (
npm run generate:api).i18n
4–5 new Paraglide keys:
admin_dashboard_activity_titleadmin_dashboard_activity_periodadmin_dashboard_activity_uploadsadmin_dashboard_activity_commentsadmin_dashboard_activity_transcribedadmin_dashboard_activity_annotatedTests
AuditLogQueryRepositoryTest, Testcontainers): seed known rows (e.g. 2FILE_UPLOADED, 3COMMENT_ADDED, 5TEXT_SAVED, 1ANNOTATION_CREATED) in the last 7 days + 1 row older than 7 days — assert each count matches exactly and the old row is excluded.AdminDashboardServiceTest): mockgetSystemActivityCounts()→ assertActivityDTOfields are mapped correctly.AdminDashboardControllerTest):GET /api/admin/dashboard— assertactivity.uploadsThisWeekis present in the response body (add to the existing shape assertion).ActivityPanel.spec.ts, Vitest Browser): render with{ uploadsThisWeek: 12, commentsThisWeek: 47, textSavedThisWeek: 8, annotationsThisWeek: 3 }— assert all four numbers are visible; render with all zeros — assert zero-state renders without errors.Acceptance criteria
GET /api/admin/dashboardresponse includesactivity.uploadsThisWeek,commentsThisWeek,textSavedThisWeek,annotationsThisWeekActivityPanelrenders correctly for non-zero counts and for all-zero countsCritical files
Related