13 — State Management (Staff Module)
- 1. Cubits
- 2. Async state machine (all cubits)
- 3. Pagination (mixin
PaginatedListMixin<T>) - 4. Caching & staleness
- 5. Optimistic updates
- 6. Cross-screen sync
- 7. Error handling (module specifics)
- 8. Testing hooks
Per-screen state on top of the shared architecture (00-shared/06_State_Management.md). Stack:
flutter_blocCubits,get_itDI, repository layer withdio.
1. Cubits
| Cubit | Screen | Events | State |
|---|---|---|---|
StaffListCubit | S1 | Load, Refresh, LoadMore, Retry, ChangeStatusFilter, ChangeTypeFilter, Search(q) | {loadState, items, page, hasNext, isLoadingMore, statusFilter, typeFilter, query, meta} |
StaffDetailCubit | S2 | Load(id), Retry | {loadState, staff?, department?, designation?, user?} |
StaffFormCubit | S3/S4 | LoadRefs, Load(initial), Submit(delta), Clear | {loadState, submitState, fields, users, departments, designations, fieldErrors} |
DepartmentListCubit | S6 | Load, Refresh, LoadMore, Retry | paginated + membersCountByDept (proposed) |
DepartmentFormCubit | S8 | Load(initial), Submit(delta) | form state |
DesignationListCubit | S9 | Load, Refresh, LoadMore, Retry | paginated |
DesignationFormCubit | S11 | Load(initial), Submit(delta) | form state |
DeactivateCubit | S5 | Confirm(id), Dismiss | {idle, submitting, success, error} |
Deactivate lives in its own cubit (not the list cubit) so the dialog state is independent and testable.
2. Async state machine (all cubits)
Initial → Loading → Success | Error(ApiException) (00-shared/06 §3.1); UI maps:
Loading → AppSkeleton, Error → AppErrorState(code, retry), Success+empty →
AppEmptyState, Success → content. Form cubits add submitState with
fieldErrors mapped from ApiException.details (400 → error.details[],
http-exception.filter.ts:103-107).
3. Pagination (mixin PaginatedListMixin<T>)
- Contract:
page/limit(20)/sort/q(00-shared/06 §3.2). loadMorefires whenmeta.hasNext(pagination-query.dto.ts:52) andisLoadingMore == false; appends, dedupes by_id.Refreshbypasses cache and resets to page 1.- Cache key:
sl:{tenant}:staff:list:{page}per module cache rules (00-shared/06 §3.3).
4. Caching & staleness
| Data | TTL (proposed) | Notes |
|---|---|---|
| Staff list pages | 5 min | volatile (status changes) |
| Department list | 24 h | reference catalog (00-shared/06 §3.3) |
| Designation list | 24 h | reference catalog |
| Staff detail | no client cache | refetch per visit; server caches |
| Users (picker) | 24 h | cross-module reference |
Stale-while-revalidate: render cache instantly, refresh in background (00-shared/06 §3.3).
5. Optimistic updates
None. All mutations (create/update/deactivate) wait for the server envelope —
status changes and deletes have downstream audit events
(event-queue-map.ts:34-36) and no rollback path (00-shared/06 §3.5).
6. Cross-screen sync
StaffDetailCubitrefreshes after a successful edit;StaffListCubitrefreshes after create/delete (or removes the row locally onDELETEsuccess —(proposed)).- Catalog changes (department/designation) invalidate the 24 h catalog cache so pickers show fresh options (00-shared/06 §3.3).
- No WebSocket topics exist for staff (00-shared/07 §8 lists none) — no realtime updates; lists refresh on pull or refetch.
7. Error handling (module specifics)
| Code | Behavior |
|---|---|
| 400 | field errors onto fieldErrors; focus first invalid field |
| 401 | interceptor refresh once; fail → re-login (00-shared/06 §3.6) |
| 403 | hide actions (no staff.create → no FAB) + 403 screen if routed |
| 404 | detail → not-found empty-state; deactivate → snackbar "Already deactivated" |
| 409 | inline duplicate message on the field (00-shared/06 §5) |
| 429 | backoff copy; no auto-retry |
| 5xx | generic + requestId (00-shared/06 §5) |
8. Testing hooks
- Cubits are pure Dart with mocked repositories (00-shared/06 §6); each list cubit has widget-test pairs for skeleton/error/empty/success permutations; deactivate cubit tested for 404 and success paths.
- Repository mappers (envelope → model) unit-tested for all status/type enum values and missing refs.