docs(admin): phase 3a spec + implementation plan
This commit is contained in:
116
docs/superpowers/specs/2026-06-17-phase-3a-admin-panel-design.md
Normal file
116
docs/superpowers/specs/2026-06-17-phase-3a-admin-panel-design.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Phase 3a — Admin Panel (MVP) Design
|
||||
|
||||
- **Created:** 2026-06-17
|
||||
- **Status:** Approved (brainstorming) — ready for implementation plan
|
||||
- **Phase:** 3a (first of two slices of roadmap Phase 3; see `docs/roadmap.md` §9)
|
||||
- **Tracker:** Plane (workspace `solelog`, project SoleLog)
|
||||
|
||||
## Goal
|
||||
|
||||
A working `apps/admin` desktop web app where an admin logs in, watches who is working
|
||||
**live**, and manages **handelingen** (activities). Everything consumes existing backend
|
||||
endpoints plus one tiny backend touch (`role` on `/api/me`). Reports/export, user
|
||||
management, and manual session entry/edit are explicitly deferred to **Phase 3b**.
|
||||
|
||||
_Done when:_ an admin can sign in to the admin app, see who is working right now (auto-
|
||||
refreshing), and add/edit/delete activities. A worker who signs in with valid credentials
|
||||
is rejected with "Geen toegang".
|
||||
|
||||
## Scope decisions (confirmed during brainstorming, 2026-06-17)
|
||||
|
||||
1. **Slice** → MVP-first, two cycles. 3a = shell + admin login + live view + activity
|
||||
management (existing endpoints). 3b = reports/export + user management + manual
|
||||
entry/edit.
|
||||
2. **Auth** → reuse the worker's bearer-token flow (`POST /api/auth/sign-in/email` →
|
||||
capture `set-auth-token` → localStorage → `Authorization: Bearer`). Gate admin access
|
||||
by reading `role` from `/api/me`; non-admins are signed out with "Geen toegang".
|
||||
3. **Layout** → fixed left sidebar + content area (scales as 3b adds sections).
|
||||
4. **Live refresh** → react-query `refetchInterval: 5000` (5s); elapsed time ticks
|
||||
client-side every second.
|
||||
5. **Live view is read-only in 3a** — no stop/fix button (admin-stopping another worker's
|
||||
session needs a new backend endpoint → 3b).
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
apps/admin/ (new Vite + React 18 + TS SPA, dev port 5174)
|
||||
src/
|
||||
lib/ api.ts (apiFetch + signIn), auth-storage.ts ← copied from worker
|
||||
auth/ AuthContext.tsx (signIn + admin-role gate via /api/me)
|
||||
api/ admin-sessions.ts (useActiveSessions), activities.ts (CRUD hooks)
|
||||
screens/ Login.tsx, Live.tsx, Activities.tsx
|
||||
components/ Sidebar.tsx
|
||||
lib/ elapsed.ts (HH:MM:SS formatter, ported from worker stopwatch)
|
||||
App.tsx, main.tsx, styles.css
|
||||
```
|
||||
|
||||
The admin app is a **client only** — it talks to the existing backend over HTTP with a
|
||||
bearer token. No DB access. It mirrors `apps/worker`'s toolchain and conventions exactly
|
||||
so the build can copy proven patterns.
|
||||
|
||||
### Backend change (the only one in 3a)
|
||||
|
||||
- `packages/shared/src/index.ts`: add `role: Role` to `PublicUser` (so `MeResponse.user`
|
||||
carries it).
|
||||
- `apps/api/src/routes/me.ts`: include `role` in the response (read from the session user,
|
||||
default `'worker'`). The worker app ignores the extra field — no worker change needed.
|
||||
|
||||
## Components
|
||||
|
||||
- **`lib/api.ts` / `lib/auth-storage.ts`** — copied verbatim from worker; bearer token in
|
||||
localStorage, `apiFetch<T>` adds the `Authorization` header.
|
||||
- **`auth/AuthContext.tsx`** — `signIn(email,password)` calls the worker `signIn`, then
|
||||
fetches `/api/me`; if `role !== 'admin'` it clears the token and throws so Login shows
|
||||
"Geen toegang — alleen beheerders." `isAuthed` reflects a present token; an admin check
|
||||
on mount (re-fetch `/api/me`) guards against a stale worker token.
|
||||
- **`screens/Login.tsx`** — Dutch email+password form (no self-signup), error line.
|
||||
- **`components/Sidebar.tsx`** — app name + signed-in email + logout (⏻); nav items **Live**
|
||||
and **Handelingen**; a muted/disabled "binnenkort" group hints 3b (Rapporten /
|
||||
Gebruikers / Handmatig).
|
||||
- **`screens/Live.tsx`** — consumes `GET /api/admin/sessions/active` (`useActiveSessions`,
|
||||
`refetchInterval: 5000`). One card per session: worker name, activity name, insole-type
|
||||
pill, pair count, ticking elapsed timer (from `start_time`). Empty state "Niemand is nu
|
||||
aan het werk."; header "Actief nu (N)".
|
||||
- **`screens/Activities.tsx`** — port of the legacy worker `Settings.tsx` (git `decb158`):
|
||||
add form (name + insole-type toggles), list with inline edit, delete-with-confirm —
|
||||
desktop-styled. Uses `api/activities.ts` hooks against `/api/activities`.
|
||||
|
||||
## Data flow
|
||||
|
||||
1. Admin enters credentials → `signIn` → token stored → `/api/me` confirms `role==='admin'`.
|
||||
2. Live screen polls `/api/admin/sessions/active` every 5s; each card computes elapsed from
|
||||
`start_time` with a 1s client tick.
|
||||
3. Activity CRUD hits `/api/activities` (GET open to any authed user; POST/PUT/DELETE are
|
||||
admin-gated server-side — the admin bearer satisfies them).
|
||||
|
||||
## Error handling
|
||||
|
||||
- Login: 401/no-token → "Inloggen mislukt"; authenticated non-admin → "Geen toegang —
|
||||
alleen beheerders" (token cleared).
|
||||
- Live/Activities: react-query error → inline "Kon gegevens niet laden." with the data
|
||||
hidden; loading → "Laden…".
|
||||
- Delete activity: `window.confirm` warns that the activity's sessions are also deleted
|
||||
(matches backend cascade in `activities.ts`).
|
||||
|
||||
## Testing
|
||||
|
||||
Mirror the worker's vitest + testing-library setup, mocking `apiFetch`:
|
||||
|
||||
- **Backend:** `/api/me` includes `role` for a worker and an admin (extend `me.test.ts`).
|
||||
- **Auth/login:** admin signs in; worker credentials are rejected with "Geen toegang".
|
||||
- **Shell:** sidebar renders nav; logout clears the token.
|
||||
- **Live:** renders a card per active session (name/activity/type); empty state when none.
|
||||
- **Activities:** add/edit/delete invoke the correct endpoints (mocked).
|
||||
|
||||
## Out of scope (→ Phase 3b)
|
||||
|
||||
- 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).
|
||||
|
||||
## Build approach
|
||||
|
||||
spec → `writing-plans` → **one Workflow** that executes the plan task-by-task (TDD, commit
|
||||
per task, final lint/typecheck/test/build + live verify), matching the Phase 2 pattern and
|
||||
keeping this session's context clean.
|
||||
Reference in New Issue
Block a user