06 — Screen Specifications (Staff Module)
- SS1 — Staff List (
/staff) - SS2 — Staff Detail (
/staff/:id) - SS3 — Create Staff (
/staff/new) - SS4 — Edit Staff (
/staff/:id/edit) - SS5 — Deactivate Confirmation (modal)
- SS6 — Department List (
/departments) - SS7 — Department Detail (
/departments/:id) - SS8 — Department Form (create + edit)
- SS9 — Designation List (
/designations) - SS10 — Designation Detail (
/designations/:id) - SS11 — Designation Form (create + edit)
- Global behaviors (all screens)
Production-grade specs for the screens in 05_Screen_Inventory.md. Every field and behavior derives from backend DTOs/schemas (
src/modules/staff/**); layout/copy follow shared tokens (00-shared/02, 03, 04, 09). Screens are(planned); backend behavior is cited exactly.
SS1 — Staff List (/staff)
Layout (responsive)
Phone (<600dp) Tablet/Desktop (≥840dp)
┌────────────────────────────┐ ┌─────────────────┬──────────────────────┐
│ AppBar: "Staff" [search] │ │ AppBar "Staff" │ (master-detail) │
│ SearchBar (debounce 300ms) │ ├─────────────────┤ Detail pane (SS2) │
│ [Status ▾][Type ▾] chips │ │ SearchBar │ │
│ ┌────────────────────────┐ │ │ [Status ▾][Type ▾] │
│ │avatar EMP-0042 [▾] │ │ │ Row1 EMP-0042 ▸ (selected) │
│ │ Accounts • Acct │ │ │ Row2..N │
│ │ [active] │ │ │ [page controls] │
│ ├────────────────────────┤ │ └────────────────────────────────────────┘
│ │avatar EMP-0043 [▾] │ │
│ └────────────────────────┘ │
│ [infinite scroll spinner] │
│ FAB: "Add staff" │
└────────────────────────────┘
- List rows: height ≥ 56 (
00-shared/03 AppListTile); leading avatar 40 (AppAvatarinitials from joined user name — staff profile has no name field,staff.schema.ts:22-52); titleemployeeNumberinmono; subtitledepartment • designation(or "—"); trailingstatusbadge + overflow menu. - Master-detail at ≥ 840 dp (
00-shared/05 §3): selected row highlights, detail in right pane; phone pushes detail.
Data & pagination
GET /staff?page=1&limit=20(staff.controller.ts:27-28); server computesmeta(staff.service.ts:64-76,pagination-query.dto.ts:41-55).limitclamp 1–100,page≥ 1 (pagination-query.dto.ts:6-19); invalid → 400VALIDATION_ERROR(http-exception.filter.ts:28,103-107).qandsortaccepted but ignored byStaffService.find(staff.service.ts:64-76) — the search box and sort menu are client-side(proposed), labeled "filtering loaded results" (OQ-2).- Infinite scroll on phone (append page while
meta.hasNext); page controls on desktop (pagination-query.dto.ts:51-53); pull-to-refresh resets to page 1.
States
| State | Render |
|---|---|
| Initial/Loading | AppSkeleton list (8 rows) |
| Success + data | rows; "end of list" footer at last page |
| Success + empty (no filters) | AppEmptyState "No staff yet" + CTA "Add staff" |
| Success + empty (filters) | AppEmptyState "No staff match the current filters" |
| Error | AppErrorState(code, retry): 429 backoff copy; 5xx generic + requestId (00-shared/06 §5) |
Filter chips
- Status chip options map exactly to
StaffStatus:active, inactive, on_leave, terminated(staff.schema.ts:7-12). - Type chip options map exactly to
EmploymentType:full_time, part_time, contract, intern(staff.schema.ts:14-19). - Both are client-side filters over loaded pages (server has no such query params —
staff.service.ts:64-76).
Row menu
View (→ SS2), Edit (→ SS4, staff.update), Deactivate (→ SS5, staff.delete).
Destructive item styled error (00-shared/03 AppMenu).
A11y
AppSearchBar results live region; status announced as "badge {label}"; avatar
semantics = person name; Semantics on list position "item 3 of 12"
(00-shared/09 §5,7).
SS2 — Staff Detail (/staff/:id)
Layout
┌────────────────────────────────────────────┐
│ AppBar: back "Staff" [edit] [▾ more] │
│ ┌────────────────────────────────────────┐ │
│ │ (avatar 64) Name (from Users) │ │
│ │ EMP-0042 [active badge] │ │
│ │ Accounts • Accountant │ │
│ └────────────────────────────────────────┘ │
│ [Overview] [Employment] [Metadata] [Audit] │
│ ┌────────────────────────────────────────┐ │
│ │ Overview tab: │ │
│ │ Employee number EMP-0042 (mono) │ │
│ │ Department Accounts → link │ │
│ │ Designation Accountant → link │ │
│ │ Status active │ │
│ │ Employment tab: │ │
│ │ Employment type full_time │ │
│ │ Joining date 12 Mar 2025 │ │
│ │ Salary grade Grade B │ │
│ │ Metadata tab: free-form key/value │ │
│ │ Audit tab: version, createdAt, updatedAt│ │
│ └────────────────────────────────────────┘ │
└────────────────────────────────────────────┘
Data
GET /staff/:id(staff.controller.ts:30-31); missing → 404RESOURCE_NOT_FOUND(staff.service.ts:58-62) → not-found empty-state.- Ref fields are bare ObjectIds (
staff.schema.ts:29-33) — resolve department and designation by fetchingGET /departments/:id,GET /designations/:id(proposed). A ref that 404s (soft-deleted catalog entry) renders "—". - Joined user name:
GET /users/:idor a client-side join from the users list(proposed)— no staff endpoint returns the person's name.
Tabs
| Tab | Content source |
|---|---|
| Overview | employeeNumber, departmentId, designationId, status (staff.schema.ts:22-49) |
| Employment | employmentType, joiningDate, salaryGrade (staff.schema.ts:35-47) |
| Metadata | metadata key/values (staff.schema.ts:51-52); read-only; empty → "No metadata" |
| Audit | version, createdAt, updatedAt (base.schema.ts:29-34) — plain text; audit events live in the audit-write queue (event-queue-map.ts:35-36), not exposed by this module |
Tab selection is client-side (proposed); keep-alive via AppTabs
(00-shared/03 AppTabs).
Actions
- Edit → SS4 (
staff.update). - Deactivate → SS5 confirm (
staff.delete). - Copy employee number (context: shared elsewhere)
(proposed).
SS3 — Create Staff (/staff/new)
Full form spec in 08_Form_Specifications.md §F1. Summary:
- Two-step feel
(proposed): step 1 pick the User (requireduserId,create-staff.dto.ts:5-7) from the users directory; step 2 employment details. Or single screen with a user picker sheet — product choice; fields are the same. employeeNumberrequired, unique per tenant (create-staff.dto.ts:9-11,staff.schema.ts:57); 409 → inline error on the field (staff.service.ts:32-36).- On submit:
POST /staff→ 201; navigate to detail (SS2); snackbar "Staff created" (staff.service.ts:37-55;00-shared/03 AppSnackbar). - Helper copy: "Profile will be created as active" (
staff.service.ts:39) and "Employment type defaults to full-time" (staff.service.ts:40-41) — neither is editable at creation via the DTO (OQ-9 for a future create-status field).
SS4 — Edit Staff (/staff/:id/edit)
- Prefill from
GET /staff/:id(SS2 data). SubmitPATCH /staff/:idwith only changed fields (update-staff.dto.ts:5-42;staff.service.ts:78-91). - Fields:
employeeNumber,departmentId,designationId,employmentType,joiningDate,salaryGrade,status(dropdown with the 4 enum values,staff.schema.ts:7-12),metadata(key/value editor(proposed)). - Changing
statushere is the deactivation alternative foron_leave/inactivewithout deleting (update-staff.dto.ts:35-38). - 409 duplicate employee number → inline; 404 → not-found state (concurrent
deletion); success → snackbar "Saved" +
StaffUpdatedaudit event (staff.service.ts:82-90).
SS5 — Deactivate Confirmation (modal)
- Trigger: row menu or detail action ("Deactivate"). Requires
staff.delete. AppDialog: title "Deactivate {employee number}?", body: "This removes the staff member from all lists. There is no undo." — accurate: soft-delete excludes them from every query (base.repository.ts:20-30) and no restore endpoint exists (base.repository.ts:68-74; OQ-8).- Confirm →
DELETE /staff/:id→ success: pop back to list + snackbar "Staff deactivated"; 404 → snackbar "Already deactivated" + refresh list (staff.service.ts:93-104). - Destructive action styled
error, hapticheavyImpacton confirm (00-shared/08 §3); focus trap + ESC (00-shared/09 §6).
SS6 — Department List (/departments)
┌──────────────────────────────────────┐
│ AppBar: "Departments" [search] │
│ SearchBar (client-side) │
│ ┌──────────────────────────────────┐ │
│ │ Administration [ADMIN] [▾] │ │
│ │ Head: A. Sharma [active] │ │
│ ├──────────────────────────────────┤ │
│ │ Accounts [ACCT] [▾] │ │
│ │ Head: — [active] │ │
│ └──────────────────────────────────┘ │
│ FAB: "Add department" │
└──────────────────────────────────────┘
GET /departments?page=&limit=(department.controller.ts:29-30);metaserver-computed (department.service.ts:38-50). Server returns insertion order — client sorts by name(proposed).- Row: name (title),
codechip (optional,department.schema.ts:12-13), head name (resolved viaheadId(proposed); "—" when unset or deleteddepartment.schema.ts:15-16), status badge. - Row menu: Edit, Deactivate (both
department.manage). FAB "Add department" → SS8 create. - Empty: "No departments yet" + CTA. Member counts are client-computed
(proposed)(no server endpoint; OQ-4).
SS7 — Department Detail (/departments/:id)
GET /departments/:id(department.controller.ts:32-33); 404 → not-found.- Header card: name, code, status; head row (avatar + name) with "Change" action
(→ SS8 edit,
headIdfield). - "Members" section
(proposed): fetch staff list, filterdepartmentId === :idclient-side (the API has no members endpoint); rows link to SS2; empty → "No staff in this department". - Actions: Edit, Deactivate.
SS8 — Department Form (create + edit)
- Fields (exact):
namerequired text;codeoptional text (trimmed server-side,department.schema.ts:12-13);headIdoptional staff picker (department.dto.ts:4-18). Edit addsstatus(department.dto.ts:36-39). - Submit →
POST /departments(201 → list) orPATCH /departments/:id(200 → detail refresh). 409 duplicate name → inline error onname(department.service.ts:23-25). 404 on edit (deleted concurrently) → not-found. - Success snackbar: "Department created"/"Department saved". No events fire
(department service emits none —
department.service.ts:19-20, OQ-10).
SS9 — Designation List (/designations)
GET /designations?page=&limit=(designation.controller.ts:29-30).- Row: name (title),
level(mono,designation.schema.ts:15-16), department name (joined fromdepartmentId,(proposed), "—" if unset), status badge. - Display order:
levelascending, then name(proposed)— server returns insertion order (designation.service.ts:41-50). - Row menu: Edit, Deactivate. FAB "Add designation" → SS11 create.
- Empty: "No designations yet" + CTA.
SS10 — Designation Detail (/designations/:id)
GET /designations/:id(designation.controller.ts:32-33); 404 → not-found.- Header card: name, level, linked department (link → SS7), status.
- "Staff with this designation"
(proposed): client-filtered staff list; empty → "No staff with this designation". - Actions: Edit, Deactivate.
SS11 — Designation Form (create + edit)
- Fields (exact):
namerequired text;departmentIdoptional department picker;leveloptional integer (IsInt—designation.dto.ts:14-17), default0shown in helper (designation.schema.ts:15-16); edit addsstatus(designation.dto.ts:36-39). - Submit →
POST /designations/PATCH /designations/:id. 409 duplicate name → inline (designation.service.ts:25-28). Non-integerlevel→ 400VALIDATION_ERRORwith details (http-exception.filter.ts:103-107). - No events emitted on create/update/delete (OQ-10).
Global behaviors (all screens)
- Permissions: render/route only when the client role has the required perm
(map:
permissions.constants.ts:19-24); server RBAC not yet enforced on these endpoints (staff.controller.ts:21) — double-gate client-side, flag OQ-1. - Offline: reads from last-good cache + offline banner (
00-shared/06 §3.7); writes blocked with guidance (no module offline queue). - Loading:
AppSkeleton≤ 200 ms; content ≤ 2 s (00-shared/10 §1). - Session: 401 → single-flight refresh; failure → re-login preserving nav
(
00-shared/06 §3.6).