08 — Form Specifications (Dashboard Module)
- 1. Widget Customization form
(planned) - 2. Period selector
(planned)— segmented, not a form - 3. Existing "inputs" (non-form)
- 4. Global form rules (applied when forms land)
The dashboard is a read-first surface: no forms exist today (the single endpoint is
GET /dashboard/overview—dashboard.controller.ts:13-17; no DTO directory exists undersrc/modules/dashboard/). This file specifies the two form-like surfaces that arrive with(planned)server work, plus the non-form "inputs" the screens already take. Validation rules follow the shared form baseline (00-shared/08).
1. Widget Customization form (planned)
Reached from /dashboard/customize (perm-gated by dashboard.widget.manage
— permissions.constants.ts:38). Server API (planned)
(Dashboard.md:26-27); persistence dashboard_widgets (Dashboard.md:7).
| Field | Control | Rules |
|---|---|---|
| Widget list | ReorderableListView (drag handles) | 1:1 with server widget ids; reorder is local until save |
| Visibility | Switch per widget | optimistic toggle + rollback on failure |
| Role chips | AppChips multi-select (planned) | "who may see this widget" — free-text chip input; server role-list param (planned) |
| Submit | AppButton "Save" | one PATCH /dashboard/widgets/:id per changed widget (planned); per-item saving state; conflict (409, concurrent edit) → AppSnackbar + reload |
States: idle (pristine, Save disabled) / dirty (Save enabled) / saving (per-item spinner) / saved (success snackbar < 3 s) / error (banner ≥ 4 s, keep local edits) / conflict (reload).
A11y: drag handles Move semantics; every switch has widget-name label;
focus moves to first invalid — none expected (no free-text validation).
2. Period selector (planned) — segmented, not a form
| Field | Control | Rules |
|---|---|---|
| Period | SegmentedButton Today/7d/30d | single-select, non-empty default "Today"; disabled + tooltip until server accepts ?period= on /dashboard/attendance / /dashboard/finance (planned) (Dashboard.md:24-25) |
State lives in the cubit (13_State_Management.md §3); changing period refetches; no optimistic rendering of unloaded periods (charts show skeleton).
3. Existing "inputs" (non-form)
| Input | Kind | Behaviour |
|---|---|---|
| Pull-to-refresh | gesture | bypasses client cache, force network (06_Screen_Specifications.md §1.4) |
| KPI tap → drill | tap target ≥ 48 | navigation only, no payload mutation |
| WS invalidation | passive listener | never user-visible as input |
4. Global form rules (applied when forms land)
- No form autofill needed (no personal data).
- Submit-once: double-tap guard (00-shared/08).
- Offline: forms disabled with
AppOfflineBanner(no offline queue for dashboard writes; 00-shared/06 §3.6). - Error copy maps
400 VALIDATION_ERRORfield-level,409conflict inline (00-shared/07 §3).