Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

06 — Screen Specifications (Staff Module)

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 (AppAvatar initials from joined user name — staff profile has no name field, staff.schema.ts:22-52); title employeeNumber in mono; subtitle department • designation (or "—"); trailing status badge + 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 computes meta (staff.service.ts:64-76, pagination-query.dto.ts:41-55).
  • limit clamp 1–100, page ≥ 1 (pagination-query.dto.ts:6-19); invalid → 400 VALIDATION_ERROR (http-exception.filter.ts:28,103-107).
  • q and sort accepted but ignored by StaffService.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

StateRender
Initial/LoadingAppSkeleton list (8 rows)
Success + datarows; "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"
ErrorAppErrorState(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 → 404 RESOURCE_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 fetching GET /departments/:id, GET /designations/:id (proposed). A ref that 404s (soft-deleted catalog entry) renders "—".
  • Joined user name: GET /users/:id or a client-side join from the users list (proposed) — no staff endpoint returns the person's name.

Tabs

TabContent source
OverviewemployeeNumber, departmentId, designationId, status (staff.schema.ts:22-49)
EmploymentemploymentType, joiningDate, salaryGrade (staff.schema.ts:35-47)
Metadatametadata key/values (staff.schema.ts:51-52); read-only; empty → "No metadata"
Auditversion, 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 (required userId, 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.
  • employeeNumber required, 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). Submit PATCH /staff/:id with 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 status here is the deactivation alternative for on_leave/inactive without deleting (update-staff.dto.ts:35-38).
  • 409 duplicate employee number → inline; 404 → not-found state (concurrent deletion); success → snackbar "Saved" + StaffUpdated audit 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, haptic heavyImpact on 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); meta server-computed (department.service.ts:38-50). Server returns insertion order — client sorts by name (proposed).
  • Row: name (title), code chip (optional, department.schema.ts:12-13), head name (resolved via headId (proposed); "—" when unset or deleted department.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, headId field).
  • "Members" section (proposed): fetch staff list, filter departmentId === :id client-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): name required text; code optional text (trimmed server-side, department.schema.ts:12-13); headId optional staff picker (department.dto.ts:4-18). Edit adds status (department.dto.ts:36-39).
  • Submit → POST /departments (201 → list) or PATCH /departments/:id (200 → detail refresh). 409 duplicate name → inline error on name (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 from departmentId, (proposed), "—" if unset), status badge.
  • Display order: level ascending, 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): name required text; departmentId optional department picker; level optional integer (IsIntdesignation.dto.ts:14-17), default 0 shown in helper (designation.schema.ts:15-16); edit adds status (designation.dto.ts:36-39).
  • Submit → POST /designations / PATCH /designations/:id. 409 duplicate name → inline (designation.service.ts:25-28). Non-integer level → 400 VALIDATION_ERROR with 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).