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

05 — Screen Inventory (Leave Module)

Every screen of the Leave module, its intent, route, composition, states, permissions, and data source. Authoritative shared components in 00-shared/03; this file enumerates which ones each screen uses with module specifics. Legend matches design-docs/auth/05.


Legend

States = idle / loading / success / empty / error(offline, server, conflict) / permission. Analytics follow {module}.{screen}.{action} (proposed).


1. Leave Balance Screen (/leave/balance)

FieldDetail
PurposeShow daysRemaining per leave type for the current user
Entryhome grid "Leave → Balance"; after a rejected request (contextual)
SourceGET /leave/balance/:userId (leave.controller.ts:55-59) → LeaveBalanceEntry[]: leaveTypeId, code, name, daysPerYear, carriedForward, daysUsed, daysRemaining (leave.service.ts:66-74)
CompositionAppCard per type: code badge, name, daysRemaining headline, daysPerYear, carriedForward, daysUsed sub-line; linear progress used/entitlement
Statesloading (skeleton per card); empty (no types — server seeds defaults on first call, leave.service.ts:299-306, so practically unreachable); error (offline → cached copy + banner; 404 user → inline)
Refreshpull-to-refresh (RefreshIndicator)
Analyticsleave.balance.open, leave.balance.refresh
Permissionany authenticated user; client hides for viewing others' balance (API does not restrict — gap, leave.controller.ts:55-59)
NotesBalance is live-computed (leave.service.ts:87-88); never cache > 1 session. Weekend/holiday days count as used days (:310-312) — surface a caption on the card

2. Request Leave Screen (/leave/requests/new)

FieldDetail
PurposeCreate a leave request
EntryMy Requests FAB; Balance card action
SourceGET /leave/types (leave.controller.ts:67-71) for the picker; submit POST /leave/requests (:32-36)
Formleave type (required, dropdown from types), startDate (required), endDate (required), reason (optional, multiline) — contract per create-leave-request.dto.ts:4-21; daysRequested is server-computed (not in DTO)
Primary CTA"Submit request"
Statesloading (types + submit); validation (client: endDate ≥ startDate; mirror of leave.service.ts:133-134); error: 404 type (reload types), 409/offline
Successrequest created pending → toast → detail/list
Analyticsleave.request.open, leave.request.submit, leave.request.success, leave.request.failure(code)
Permissionany authenticated user (id from token, leave.service.ts:138)
NotesAfter submit show computed days + "pending" hint; warn on dates that include weekends (server counts them, :310-312)

3. My Requests List (/leave/requests)

FieldDetail
PurposeList the user's own requests, newest first
SourceGET /leave/requests — non-admins are forced to own userId (leave.service.ts:166-167); sorted createdAt desc (:168)
CompositionAppListTile per request: type code badge, date range, daysRequested, status chip (pending/approved/rejected), decision note line when present
Filtersstatus client-side or server ?status= (leave.controller.ts:40)
Statesloading / empty ("No leave requests yet" + CTA) / error / offline (cached)
Refreshpull-to-refresh
Analyticsleave.requests.list, leave.requests.filter(status)
Permissionany authenticated user

4. Request Detail Sheet (/leave/requests/:id)

FieldDetail
PurposeRead-only detail of one request (no GET /:id — data passed from list; gap noted in 04 §5)
Contenttype, dates, days, reason, status, decidedBy, decidedAt, decisionNote (leave-request.schema.ts:16-48)
Actionsnone for requester (no cancel/edit endpoint); admin (if approved) → "Assign substitution" shortcut
Statesas list row, expanded

5. Approvals Queue (/leave/approvals) — org admin

FieldDetail
PurposeReview and decide all tenant requests
SourceGET /leave/requests?status=pending (admin sees all, leave.service.ts:163-167); optional ?userId= (leave.controller.ts:41-44)
Compositionfilter chips (pending default, approved, rejected); list of request tiles with requester name (needs users lookup — no populate in service, gap), dates, days, reason
Row actionAppMenu → "Approve" / "Reject (add note)" → PATCH /leave/requests/:id/approve with {action, note?} (leave-decision.dto.ts:9-17)
Statesper row: deciding (button spinner); error: 409 already decided (leave.service.ts:175-178) → refresh list; 409 insufficient balance (:188-189) → inline "insufficient balance" chip; 409 self (:179-180) → hide own requests from queue in client
Empty"No pending requests"
Analyticsleave.approvals.open, leave.approvals.decide(action)
Permissionorg admin only (client-side gate; server allows any non-self decision — gap, no RBAC guard, leave.controller.ts:27)

6. Substitutions List (/leave/substitutions) — substitute teacher

FieldDetail
PurposeShow assignments for the current teacher
SourceGET /leave/substitutions/teacher/:id (leave.controller.ts:79-83); sorted date asc (leave.service.ts:275-280)
Compositiondate-grouped AppListTiles: class, subject, start–end time, notes; status chip assigned (only reachable state from service)
Statesloading / empty ("No substitutions assigned") / error
Refreshpull-to-refresh
Analyticsleave.substitutions.list

7. Assign Substitution Sheet (/leave/approvals/assign) — org admin

FieldDetail
PurposeAssign a substitute teacher for an approved request
SourcePOST /leave/substitutions (leave.controller.ts:73-77); needs teacher list (teachers module), class/subject pickers
FormleaveRequestId (from context), substituteTeacherId, classId, subjectId, date, startTime, endTime, notes? (assign-substitution.dto.ts:4-37)
Stateserror: 409 not approved (leave.service.ts:227-230), 404 no teacher record (:232-238), 409 time clash (:241-250) → inline "already assigned in this slot"
Permissionorg admin

8. Leave Types List + Create (/leave/types) — org admin

FieldDetail
PurposeView seeded/custom types; create new
SourceGET /leave/types (:67-71); POST /leave/types (:61-65)
Formcode, name, daysPerYear, carryForward?, maxCarryForward? (create-leave-type.dto.ts:4-28)
Statesduplicate code → MongoDB unique (tenantId, code) (leave-type.schema.ts:29) → client maps 11000 to "code already exists" (no server mapper — gap)
Permissionorg admin

9. Leave Calendar (/leave/calendar)

FieldDetail
PurposeMonth grid of approved leave (tenant-wide)
SourceGET /leave/calendar?from&to (leave.controller.ts:85-91) — approved only, overlap query (leave.service.ts:282-296); default current month (:283-287)
Compositionmonth pager; day cells with count/avatars of who is out; tap → detail sheet
Statesloading / empty / error
NotesClient should restrict from/to to a month window to match server default semantics

10. Notifications (planned)

Decision pushed to requester via LeaveApproved/LeaveRejected events (events/leave-events.ts:10-18) → Notifications module; substitution notification to substitute + students (planned, IMPLEMENTATION_PLAN.md:163).


Shared components used

AppTextField, AppButton, AppSnackbar, AppCard, AppListTile, AppBottomSheet, AppDialog, AppMenu, AppSkeleton, AppEmptyState, AppOfflineBanner, AppFAB, AppChips, AppBadge, AppAvatar, DatePicker, SegmentedButton. Module-specific: StatusChip, BalanceCard, LeaveTypeCard, SubstitutionTile, ApprovalActionSheet — see 07_Component_Library.md.

Analytics events (proposed)

leave.balance.open|refresh, leave.request.open|submit|success|failure, leave.requests.list|filter, leave.approvals.open|decide, leave.substitutions.list, leave.calendar.open|month, leave.types.list|create.