05 — Screen Inventory (Users Module)
- S1 — Users list (master)
- S2 — Create user (wizard-lite form)
- S3 — User detail
- S4 — Bulk import wizard (upload → preview → result)
- S5 — Edit user
- S6 — Preferences (self + manager)
- S7 — Self profile
- S8 — Confirm dialogs (delete / erase / deactivate)
- S9 — Import result / error report
- Screen → endpoint matrix
Every screen the module owns, with purpose, entry, data sources, and states. Detailed specs in 06_Screen_Specifications.md. Screens are
(planned)(no client repo); data source citations are backend-exact.
S1 — Users list (master)
| Route | /users |
| Entry | Settings group → "Users & Roles" |
| Purpose | Find, filter, and act on every person in the tenant |
| Data source | GET /api/v1/users?page&limit&sort&q (users.controller.ts:42-46; users.service.ts:90-115) + GET /api/v1/rbac/members for role chips (rbac.controller.ts:57-61) |
| Query contract | q → $or regex on firstName/lastName/email/displayName (users.service.ts:92-99); default sort -createdAt (:103); paginated envelope meta {page,limit,totalItems,totalPages,hasNext,hasPrevious} (pagination-query.dto.ts:32-55) |
| States | Initial → skeleton rows; success+empty → AppEmptyState ("No users — add your first user"); error → AppErrorState (404 never for lists; 429 → backoff copy) |
| Key elements | AppSearchBar (debounce 300 ms) · filter chips row (status, role — (planned) server params, client-side filter meanwhile, 04_IA §10) · user rows: AppAvatar + name/displayName + email + status badge + role chips + last-login · pagination (infinite scroll on phone, page controls on desktop) |
| Actions | Row tap → S3 detail · row menu: Edit / Deactivate |
S2 — Create user (wizard-lite form)
| Route | /users/new |
| Entry | List FAB |
| Data source | POST /api/v1/users with CreateUserDto (create-user.dto.ts:5-62) + POST /api/v1/rbac/members after success |
| Elements | Identity section (firstName, middleName?, lastName, displayName?, email, phone?) · demographics (gender?, dateOfBirth?) · locale (language, timezone) · status (default active) · membership section (roles from GET /rbac/roles) |
| Submission | create user → on success immediately create membership; if membership fails show explicit "created without roles" banner + retry (OQ-5) |
| States | Form validation per field; 409 email/phone → inline conflict; success → snackbar + navigate to detail |
S3 — User detail
| Route | /users/:id |
| Data sources | GET /users/:id (users.controller.ts:48-53) · GET /rbac/members (join by userId) · GET /users/:id/preferences (:78-83) |
| Elements | Header: avatar + name + status badge + email + roles · tabs: Profile (all fields incl. lastLoginAt, emailVerified, phoneVerified, metadata) · Membership (roles + member status) · Preferences (manager view) · Audit (planned) (audit-write events exist — event-queue-map.ts:11-12; no read endpoint in scope) |
| Actions | Edit · status menu · Delete / GDPR erase (top-bar overflow) |
| States | 404 → "User not found" empty state (cross-tenant ids resolve 404 — base.repository.ts:20-30) |
S4 — Bulk import wizard (upload → preview → result)
| Route | /users/import |
| Entry | List toolbar "Import CSV" |
| Data source | POST /api/v1/users/import (multipart file, users.controller.ts:104-110); future bulk-module adapter path POST /api/v1/bulk/import/:entity (planned) for a users entity (bulk-import.service.ts:17-20) |
| Steps | 1 Upload — file picker (.csv only), drag-drop on desktop, size guard, template download · 2 Preview — client-side parse in isolate: header map, row count, first rows table, column mapping (firstname/first_name), warnings (missing optional cols, quotes detected) · 3 Result — imported/errors counts, error table Row N: message (users.service.ts:281), download-error CSV, "Import more" |
| Progress | stage-based client progress; server phase indeterminate (sync endpoint, 03_User_Journey.md §2) |
| States | Upload invalid file → inline error; malformed (no header/data) → server returns errors ["CSV must have a header row and at least one data row"] (users.service.ts:238-243); partial success → result screen |
S5 — Edit user
| Route | /users/:id/edit |
| Data source | PATCH /users/:id with UpdateUserDto (update-user.dto.ts:5-70) |
| Elements | same fields as S2 minus roles (roles edited in S3 Membership via PATCH /rbac/members/:id — rbac.controller.ts:69-73); email/phone conflicts → 409 inline (users.service.ts:120-134) |
| Notes | displayName recomputed server-side when names change (users.service.ts:136-139) — show resulting displayName |
S6 — Preferences (self + manager)
| Route | /settings/profile/preferences (self) · /users/:id/preferences (manager) |
| Data source | GET/PATCH /api/v1/users/:id/preferences (users.controller.ts:78-93) |
| Elements | Notifications: email/push/sms switches · Theme: mode radio `light |
| States | Load → skeleton; save → button spinner; 404 → not found |
S7 — Self profile
| Route | /settings/profile |
| Data source | GET /users/:id with :id = jwt sub (client-resolved; server self-guard (planned) — OQ-3) |
| Elements | avatar (POST /users/:id/avatar, users.controller.ts:95-102) · name/email/phone/gender/DOB/language/timezone · status read-only (self) · link to S6 preferences · link to password/2FA/sessions (auth module surface, auth.controller.ts:109-165) |
| Notes | emailVerified/phoneVerified read-only; see OQ-4 (user-level emailVerified flag not maintained by verifyEmail — auth.service.ts:217-223) |
S8 — Confirm dialogs (delete / erase / deactivate)
| Scope | Modal overlays on S1/S3 |
| Variants | Deactivate (plain confirm + consequence copy: still log-in capable if credentials exist) · Delete (typed confirm "delete"; 30-day purge note — tenant-purge.worker.ts:32-43) · GDPR erase (typed confirm "ERASE"; irreversible — anonymization users.service.ts:188-197 + purge job :200-209) |
S9 — Import result / error report
| Scope | Step 3 of S4 (also standalone after server-side error-only response) |
| Elements | headline counts (imported / failed), error table (row number, backend message), download-error-CSV, "Import more" |
| Copy rule | error strings rendered as-is from server (Row N: … — users.service.ts:256-278); prefixed with a static i18n title, never interpreted client-side |
Screen → endpoint matrix
| Screen | Endpoint(s) |
|---|---|
| S1 | GET /users · GET /rbac/members |
| S2 | POST /users · POST /rbac/members · GET /rbac/roles |
| S3 | GET /users/:id · GET /rbac/members · GET /users/:id/preferences |
| S4 | POST /users/import (future: POST /bulk/import/users (planned)) |
| S5 | PATCH /users/:id |
| S6 | GET/PATCH /users/:id/preferences |
| S7 | GET /users/:id · POST /users/:id/avatar |
| S8 | (client-only dialogs) |
| S9 | (client-only; feeds retry of POST /users/import) |