From bbf29120acfbf9afc505dccb704613c9e2be000a Mon Sep 17 00:00:00 2001 From: Bas van Rossem Date: Wed, 24 Jun 2026 16:44:17 +0200 Subject: [PATCH] docs: phase 3b.2 reports/export session log + roadmap note --- docs/roadmap.md | 9 +- .../2026-06-24-phase-3b2-reports-export.md | 95 +++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 docs/sessions/2026-06-24-phase-3b2-reports-export.md diff --git a/docs/roadmap.md b/docs/roadmap.md index 813dc8f..d0c41fe 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -187,8 +187,13 @@ Each phase keeps the system working and is its own spec → plan → build cycle `GET /api/admin/users` roster), an admin **Sessies** screen (list + status filter + create/edit form + row actions), Stop/Annuleer on the Live cards, and a worker stopwatch that polls (15 s) and reconciles to server truth (converges within ~15 s when an admin - stops/cancels/edits a session — no stuck state). **3b remaining:** reports/export - (all-users filtered CSV — current `/api/export` is self-scoped) and user management + stops/cancels/edits a session — no stuck state). + **3b·2 implemented** (plan `docs/superpowers/plans/2026-06-24-phase-3b2-reports-export.md`): + the admin **Rapporten** screen (period presets Deze week / Deze maand / Alles + + worker/type/activity filters, headline totals and breakdowns per worker/activity/type) backed by + `GET /api/admin/report` (completed-only, JS aggregation), plus an all-users filtered CSV via + `GET /api/admin/export` — a shared `buildSessionsCsv(rows, {includeWorker})` now serves both the + worker self-export and the admin export. **3b remaining:** user management (better-auth `/api/auth/admin/*`). Activity management (add/edit/delete handelingen + their `insole_types`) was removed from the worker client in the Phase 2 follow-up because it is admin-only; it must be **ported here**. The diff --git a/docs/sessions/2026-06-24-phase-3b2-reports-export.md b/docs/sessions/2026-06-24-phase-3b2-reports-export.md new file mode 100644 index 0000000..caadbe3 --- /dev/null +++ b/docs/sessions/2026-06-24-phase-3b2-reports-export.md @@ -0,0 +1,95 @@ +# 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__.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 `` 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 `