Files
solelog/docs/sessions/2026-06-24-phase-3b2-reports-export.md
Bas van Rossem bbf29120ac
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m17s
docs: phase 3b.2 reports/export session log + roadmap note
2026-06-24 16:44:17 +02:00

6.3 KiB

Session: 2026-06-24 — Phase 3b·2 (Reports + all-users export)

Goal

Give the admin a Rapporten screen: pick a period (Deze week / Deze maand / Alles + manual from/to) and optionally narrow by worker / insole type / activity, see headline production totals plus three breakdowns (per medewerker / per handeling / per type), and export the underlying detail rows (all workers) to CSV. Today's /api/export is self-scoped to the logged-in user; this cycle adds the cross-user, filterable reporting + export. Second of three Phase 3b cycles (after manual-sessions 3b·1; user management remains). Spec: docs/superpowers/specs/2026-06-24-phase-3b2-reports-export-design.md; plan: docs/superpowers/plans/2026-06-24-phase-3b2-reports-export.md. Tracked as Plane epic SL-48 with tasks SL-49…SL-53.

Scope decisions (brainstorming)

  • Both lenses on one screen (totals + breakdowns per worker AND activity AND type).
  • Filters: date range (spine) + optional worker + type + activity; default this week.
  • Completed-only counts toward totals/export (active/discarded excluded).
  • All four metrics: gewerkte tijd, aantal zolen, aantal sessies, pauzetijd.
  • CSV = detail rows, all workers (a leading Worker column), not the aggregated summary.
  • Tables only — no chart library, no CSS bars (dependency-light).

Work done

Built via a 5-task TDD Workflow (w7ut902zo), one commit per task, run sequentially because the tasks share one working tree:

  • Task 1 — DRY CSV builder (01fa18f). Extracted the inline header/row builder from the self-scoped /api/export into buildSessionsCsv(rows, { includeWorker }) + a SessionCsvRow interface in apps/api/src/lib/csv.ts. sessions.ts now maps its query rows into SessionCsvRow and calls the shared builder. includeWorker:false keeps the worker export byte-identical (the existing export.test.ts is the regression guard); includeWorker:true prepends a Worker column. New csv.test.ts covers both header variants.
  • Task 2 — Report contracts + GET /api/admin/report (4b213e2). Added ReportTotals, ReportWorkerRow, ReportActivityRow, ReportTypeRow, ReportResponse to @solelog/shared. In admin.ts: parseReportQuery (validates from/to range, insole_type, activity_id), buildSessionFilters (completed-only + date range + optional worker/type/activity), and the report route — fetches the filtered joined rows and aggregates totals + by_worker/ by_activity/by_type in JS (sorted by worked_seconds desc; empty range → zeros + empty arrays; null type bucketed under "Onbekend"). report.test.ts (5 cases).
  • Task 3 — GET /api/admin/export (d33fcb7). All-users filtered CSV reusing parseReportQuery + buildSessionFilters (Task 2) and buildSessionsCsv(includeWorker:true) (Task 1); ordered by start_time asc; filename solelog-report_<from>_<to>.csv. 401/403 inherited from the /api/admin/* guard. Export tests appended to report.test.ts.
  • Task 4 — Admin API client (8ad2e69). apps/admin/src/api/reports.ts: ReportFilters, filtersToQuery (omits empty optionals), useReport (React Query), and downloadExport — a raw fetch with the bearer token → Blob → object-URL download (a plain <a href> can't carry the token). reports.test.ts covers serialization + the bearer-fetch download + non-ok throw.
  • Task 5 — Rapporten screen + nav (8d75be0). Pure lib/date-range.ts helpers (thisWeek, thisMonth, allTime, dayStartISO/dayEndISO — local-day → ISO instants so there's no server tz ambiguity) with unit tests. screens/Reports.tsx: filter bar (presets + date inputs + worker/ type/handeling dropdowns), a headline totals line, and three breakdown tables, plus the Exporteer CSV button wired to downloadExport. Sidebar moves Rapporten into navItems (soon = just Gebruikers); App.tsx gets the /rapporten route; report styles appended to styles.css.

Deviations (fix-forward, test-only)

  • Task 3: the API test DB is reset once per file (test/setup.ts), so the WIDE-window export tests initially picked up sessions seeded by the earlier report tests. Fixed by scoping the two non-gating export tests to their own freshly-seeded activity via &activity_id=… (a real filter the route already supports). The route code is exactly as planned.
  • Task 5: the plan's Reports.test.tsx had a few assertions that collided with the screen's own markup (headline 36 vs a table cell; Jan/Kurk matching dropdown <option>s). Tightened three assertions (scope to .reports-totals; target getByRole('cell', …)) and loosened the apiFetch mock signature for a teardown refetch. Screen behavior/markup unchanged.

Verification (independent of the workflow's self-reports)

  • git log — five task commits 01fa18f → 8d75be0; clean tree.
  • yarn workspace @solelog/api test85 passed (14 files), incl. csv, report, and the unchanged regression export.test.ts; typecheck clean.
  • yarn workspace @solelog/admin test46 passed (10 files), incl. reports, date-range, Reports; typecheck clean; build succeeds (vite, 94 modules).
  • npx oxlint — clean (exit 0).

Outcome

Phase 3b·2 is implemented and green across both touched workspaces. An admin opens Rapporten, picks a period + optional filters, and sees correct headline totals (worked / zolen / sessies / pauze) with three breakdown tables, then exports every matching completed session (all workers, with a Worker column) to a range-named CSV. One CSV format now serves both the worker self-export and the admin export. No DB migration (reuses existing work_sessions columns). Plane SL-48 + SL-49…SL-53 all marked Done. Roadmap Phase 3 note updated: 3b·2 done; user management is the last 3b cycle.

The maintainer's deploy WIP (.env.prod.example / docker-compose.prod.yml) was left untouched. origin/main is behind (the pre-compaction push never landed); after this cycle main is ahead ~16 commits — a single git push origin main from the maintainer's terminal carries the batch (and triggers Gitea CI).

Next (Phase 3b remainder)

  • User management — create / set role / deactivate via better-auth /api/auth/admin/* (the Gebruikers screen). Final 3b cycle.