From 76ad235c9ff27d9e9600c5f77a6c2316f539220c Mon Sep 17 00:00:00 2001 From: Bas van Rossem Date: Wed, 17 Jun 2026 19:17:20 +0200 Subject: [PATCH] docs(admin): phase 3a session log + roadmap status --- apps/admin/README.md | 98 +++++++++++++++++++ docs/roadmap.md | 14 ++- .../2026-06-17-phase-3a-admin-panel.md | 72 ++++++++++++++ 3 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 apps/admin/README.md create mode 100644 docs/sessions/2026-06-17-phase-3a-admin-panel.md diff --git a/apps/admin/README.md b/apps/admin/README.md new file mode 100644 index 0000000..8c28afb --- /dev/null +++ b/apps/admin/README.md @@ -0,0 +1,98 @@ +# @solelog/admin + +The SoleLog **Admin panel**: a **Vite + React + TypeScript** single-page app for the +shop-floor administrator. An admin logs in (email + password → bearer token in +`localStorage`), watches who is working **live**, and manages the **handelingen** +(activities). It is a **client only** — it talks to the `@solelog/api` backend over HTTP +with `Authorization: Bearer `; it never touches the database. + +> **Admin-only.** Sign-in confirms `role === 'admin'` via `GET /api/me`. A worker who signs +> in with valid credentials is rejected with **"Geen toegang — alleen beheerders."** and the +> token is cleared. The UI is in **Dutch**. + +It mirrors `apps/worker`'s toolchain and conventions (same Vite/React/react-query/vitest +versions, the same `lib/api.ts` + `lib/auth-storage.ts`), so it shares proven patterns. + +## What Phase 3a covers + +- **Login** — Dutch email + password form (no self-signup), with the admin-only gate. +- **Sidebar shell** — left sidebar with the signed-in email + an Uitloggen button, nav to + **Live** and **Handelingen**, and a muted "Binnenkort" group (Rapporten / Gebruikers / + Handmatig) hinting at Phase 3b. +- **Live** (`/`) — who is working right now, from `GET /api/admin/sessions/active`, + auto-refreshing every **5 s** (read-only in 3a). One card per session: worker name, + activity, insole-type pill, pair count, and a client-side ticking elapsed timer. Empty + state: "Niemand is nu aan het werk.". +- **Handelingen** (`/handelingen`) — add / inline-edit / delete activities and their insole + types (`Kurk` / `Berk` / `3D`), against `/api/activities` (writes are admin-gated + server-side; the admin bearer satisfies them). + +**Deferred to Phase 3b:** reports + all-users CSV export, user management +(`/api/auth/admin/*`), and manual session entry/edit + admin stop/fix of a running session +(needs new backend endpoints). + +## Prerequisites + +- Node + Corepack (Yarn 4 is pinned in the repo). +- From the **repo root**, install everything once: + + ```bash + yarn install + ``` + +## Run it + +Two processes: the API on `:3000` and the admin SPA on `:5174`. + +### 1. Backend API (`:3000`) + +From the repo root: + +```bash +yarn workspace @solelog/api db:migrate # apply migrations (creates ./data/app.db on first run) +yarn workspace @solelog/api db:seed # idempotent: seeds reference activities + dev logins +yarn workspace @solelog/api start # Hono server on http://localhost:3000 +``` + +`db:seed` creates two **dev logins** (dev-only — skipped when `NODE_ENV=production`): +**admin** `admin@solelog.local` / `werkplaats-admin` and **worker** +`worker@solelog.local` / `werkplaats123`. Only the admin can use this app. + +The API's default `WEB_ORIGINS` already allows `http://localhost:5174` (this app's dev +origin), so cross-origin sign-in and requests work out of the box. Override with +`CORS_ORIGINS` for other origins (e.g. a LAN IP). + +### 2. Admin SPA (`:5174`) + +From the repo root: + +```bash +yarn workspace @solelog/admin dev # Vite dev server on http://localhost:5174 +``` + +Open **http://localhost:5174** and sign in as the admin. The API base URL comes from +`VITE_API_URL` (default `http://localhost:3000`). + +## Scripts + +```bash +yarn workspace @solelog/admin dev # dev server (:5174, LAN-exposed) +yarn workspace @solelog/admin build # tsc -b && vite build → dist/ +yarn workspace @solelog/admin preview # preview the production build +yarn workspace @solelog/admin typecheck # tsc -b +yarn workspace @solelog/admin test # vitest run +``` + +## Architecture + +- **Auth.** `lib/api.ts` + `lib/auth-storage.ts` are copied from the worker: sign-in reads + the bearer token from the `set-auth-token` response header into `localStorage`, and + `apiFetch` attaches `Authorization: Bearer ` to every request. + `auth/AuthContext.tsx` adds the admin gate (re-fetch `/api/me`, reject non-admins). +- **Live data.** `api/admin-sessions.ts` `useActiveSessions()` uses react-query with + `refetchInterval: 5000`; each card computes elapsed time from the server `start_time` + with a 1 s client tick (`lib/elapsed.ts` `formatTime`). +- **Activities.** `api/activities.ts` hooks (`useActivities` + create/update/delete) + invalidate the `['activities']` query on every mutation. +- **Shared contracts.** Request/response shapes are zod schemas in `@solelog/shared`, + imported here for a typed client. diff --git a/docs/roadmap.md b/docs/roadmap.md index f368369..77994ba 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,7 +1,7 @@ # Insole Production Time Tracker — Rebuild Roadmap & Project Overview - **Created:** 2026-06-17 -- **Status:** Approved — living project doc; Phases 0–2 implemented (`docs/plans/phase-2-accounts-roles.md`) +- **Status:** Approved — living project doc; Phases 0–2 implemented + Phase **3a** implemented (`docs/superpowers/plans/2026-06-17-phase-3a-admin-panel.md`) - **Type:** Greenfield rebuild of an inherited app - **Tracked in git** under `docs/` (the project's documentation source of truth). @@ -174,8 +174,16 @@ Each phase keeps the system working and is its own spec → plan → build cycle an admin account exists; admin manages users via `/api/auth/admin/*` and sees all sessions via `/api/admin/sessions`. Public sign-up is closed; activity writes are admin-only. _Done when:_ workers see only their own sessions; an admin account exists. -- **Phase 3 — Admin panel.** The React admin app: live active-work view, reports/export, - user management, **activity management**, **manual entry/edit (the fallback)**. +- **Phase 3 — Admin panel.** The React admin app (`apps/admin`, Vite + React, dev port + 5174): live active-work view, reports/export, user management, **activity management**, + **manual entry/edit (the fallback)**. + **3a implemented** (`apps/admin`; plan `docs/superpowers/plans/2026-06-17-phase-3a-admin-panel.md`): + admin-only login (rejects non-admins via `role` on `/api/me`), the sidebar shell, the live + active-work view (`/api/admin/sessions/active`, 5 s auto-refresh, read-only), and activity + management (handelingen CRUD on `/api/activities`). **3b remaining:** reports/export + (all-users filtered CSV — current `/api/export` is self-scoped), user management + (better-auth `/api/auth/admin/*`), and manual entry/edit + admin stop/fix of a running + session (needs new backend endpoints). 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 backend already exists (`/api/activities` writes are admin-gated; `useActivities`/the legacy diff --git a/docs/sessions/2026-06-17-phase-3a-admin-panel.md b/docs/sessions/2026-06-17-phase-3a-admin-panel.md new file mode 100644 index 0000000..2d6da8a --- /dev/null +++ b/docs/sessions/2026-06-17-phase-3a-admin-panel.md @@ -0,0 +1,72 @@ +# Session: 2026-06-17 — Phase 3a (Admin panel MVP) + +## Goal + +Build `apps/admin`: a Vite + React desktop SPA where an admin logs in, watches who is +working **live** (auto-refreshing, read-only), and manages **handelingen** (activities). +Everything rides on existing backend endpoints plus one tiny backend touch (`role` on +`/api/me`). Reports/export, user management, and manual session entry/edit are deferred to +Phase 3b. Spec: `docs/superpowers/specs/2026-06-17-phase-3a-admin-panel-design.md`; +plan: `docs/superpowers/plans/2026-06-17-phase-3a-admin-panel.md`. + +## Work done + +Implemented Phase 3a task-by-task per the plan (TDD throughout): + +- **Task 1 — `role` on `/api/me` + admin-origin CORS** (`02b7522`). Added `role: Role` to + `PublicUser` in `@solelog/shared`; `apps/api/src/routes/me.ts` now returns the session + user's role (default `'worker'`). Added `http://localhost:5174` (admin dev origin) to the + default `WEB_ORIGINS` in `env.ts` + `.env.example` (it drives both `hono/cors` and + better-auth `trustedOrigins`). Extended `me.test.ts` and `cors.test.ts`. Worker client + ignores the extra field — no worker change needed. +- **Task 2 — Scaffold `apps/admin`** (`682a9dc`). New `@solelog/admin` workspace mirroring + `apps/worker`'s toolchain (Vite 7, React 18.3, react-router 6, react-query 5, vitest 3, + TS 5.7). Copied `lib/api.ts` + `lib/auth-storage.ts` verbatim (shared `solelog.token` + key; separate localStorage because the admin runs on a different port). Dev port **5174**. +- **Task 3 — Auth context + admin gate + Login** (`77659ed`). `auth/AuthContext.tsx`: + `signIn` calls the worker `signIn`, fetches `/api/me`, and rejects non-admins + (`clearToken()` + throw) so `Login` shows "Geen toegang — alleen beheerders."; other + failures show "Inloggen mislukt". +- **Task 4 — Sidebar shell + routing** (`286e2d2`). Left-sidebar shell: brand "SoleLog + Admin", `NavLink`s **Live** / **Handelingen**, a muted "Binnenkort" group (Rapporten / + Gebruikers / Handmatig) hinting 3b, and a header strip with the signed-in email + an + Uitloggen button (`aria-label="Uitloggen"`). +- **Task 5 — Live active-work view** (`67dd0d3`). `useActiveSessions()` polls + `/api/admin/sessions/active` with `refetchInterval: 5000`; `lib/elapsed.ts` `formatTime` + ported verbatim from the worker stopwatch. One card per session (worker name, activity, + insole-type pill, pair count, ticking elapsed timer). Header "Actief nu (N)"; empty state + "Niemand is nu aan het werk.". +- **Task 6 — Activity management** (`c0d9d21`). Ported the legacy worker `Settings.tsx` + (git `decb158`) near-verbatim: `api/activities.ts` hooks (`useActivities` + + create/update/delete, all invalidating `['activities']`) and `screens/Activities.tsx` + (add form with insole-type toggles, list with inline edit, delete-with-confirm). Title + "Handelingen". +- **Task 7 — Docs, lint, verification** (this task). Lint/format clean, full green matrix, + live smoke, docs. + +## Verification (Task 7) + +- `npx oxlint` — clean (exit 0). +- `npx oxfmt --list-different` over the phase-3a files — nothing to change (all formatted). +- `yarn workspace @solelog/api typecheck` — pass; `test` — **46 passed** (11 files). +- `yarn workspace @solelog/admin typecheck` — pass; `test` — **14 passed** (5 files); + `build` — pass (`vite build`, 89 modules). +- `yarn workspace @solelog/worker test` (regression) — **22 passed** (7 files). +- **Live smoke** (API started, seeded, then server tree killed + port 3000 freed): + - admin sign-in → `GET /api/me` → `{ ..., "role": "admin" }`. + - `GET /api/admin/sessions/active` with the admin bearer → `[]`, HTTP 200. + - worker sign-in → `GET /api/me` → `{ ..., "role": "worker" }`. + +## Outcome + +Phase 3a is implemented and green. An admin can sign in to the admin app, see who is +working right now (5s auto-refresh, read-only), and add/edit/delete handelingen; a worker +who signs in with valid credentials is rejected with "Geen toegang". Roadmap Phase 3 status +updated to "3a implemented; 3b remaining"; `apps/admin/README.md` filled. + +## Next (Phase 3b) + +- Reports + all-users filtered CSV export (current `/api/export` is self-scoped). +- User management via better-auth `/api/auth/admin/*`. +- Manual session entry/edit + admin stop/fix of another worker's session (needs new + backend endpoints).