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 (Transport Module)

Detailed specifications per screen: layout, wire contract, states, interactions, errors, permissions, a11y and analytics. Read alongside 05_Screen_Inventory.md and 00-shared/03 (components), 00-shared/06 (state), 00-shared/09 (a11y).


0. Shared states (every screen)

StateRenderingSource of truth
idle/loadingAppSkeleton per block; list rows as skeleton tiles00-shared/06 §3.1
error offlineAppOfflineBanner + native retry; data shown stale if cached00-shared/10 §2
error APIAppErrorState(code, message, onRetry); 401 -> re-auth; 403 -> permission copy00-shared/03
emptyAppEmptyState with CTA00-shared/03
409 conflictinline field/dialog message with corrective action (see each screen)transport.service.ts:38-45, 113-116, 177-190, 252-258
404detail screens -> AppErrorState + backtransport.service.ts:80, 151, 220

Error envelope: { statusCode, message, timestamp, path } per 00-shared/07 §3 (HttpExceptionFilter).


1. Transport Overview (/transport)

Layout

  • AppBar: title "Transport", global search icon disabled (no search endpoint).
  • Body: StatTile grid (Vehicles / Routes / Drivers) - count from meta.totalItems of each list call with limit=1 (transport.service.ts:75, 146, 215); each tile navigates to its list.
  • Quick actions card: "Assign student to route", "Student assignments" (student search first), "Route editor" shortcut to route list.
  • FAB: "Assign student" (requires transport.assign, permissions.constants.ts:74).

Wire contract

  • 3 parallel GETs: GET /transport/vehicles?page=1&limit=1, GET /transport/routes?page=1&limit=1, GET /transport/drivers?page=1&limit=1 (transport.controller.ts:36-40, 66-70, 96-100).
  • Envelope per 00-shared/07 §2: { data: [...], meta: { page, limit, totalItems, totalPages, hasNext, hasPrevious } }.

States

  • Per-tile loading/error - one failed tile shows AppErrorState with retry for that tile only; the rest render.
  • All three fail -> full-page error.

Interactions

  • Tile tap -> list screen. FAB -> assign sheet (screen §7).
  • Pull-to-refresh re-fires all three GETs.

a11y / motion

  • StatTile semantics label "Vehicles: 12". Fade-in stagger m-base (00-shared/08).

Analytics (proposed)

transport.overview.open, transport.overview.tile.{vehicles,routes,drivers}.


2. Vehicle List (/transport/vehicles)

Layout

  • AppBar: "Vehicles" + FAB "New vehicle" (.create).
  • List of AppListTile: leading vehicle type icon (bus/van/car), title plateNumber (monospace), subtitle model + capacity seats, trailing AppBadge (status: active=success, maintenance=warning, inactive=neutral) + AppMenu (Edit / Delete).
  • AppPagination footer (page, limit from state; meta from envelope).

Wire contract

  • GET /transport/vehicles?page=1&limit=20 (transport.controller.ts:36-40); server sort: plateNumber asc (transport.service.ts:71).
  • Item shape: { _id, plateNumber, model, capacity, type, status, year?, color?, insuranceExpiry?, notes?, tenantId, isDeleted, version, createdAt, updatedAt } (vehicle.schema.ts:19-50).

States

  • loading: 6 skeleton tiles; empty: "No vehicles yet" + CTA; error per §0.

Interactions

  • Tap row -> detail (§3). Delete via menu -> AppDialog confirm -> optimistic remove; on 409 ("Vehicle is assigned to a route.", transport.service.ts:98) show dialog with "View blocking routes" button -> route list.
  • Infinite approach: pagination controls (page jumps) - not infinite scroll (00-shared/01 §"pagination, not infinite scroll alone").

a11y / motion

  • Row delete confirmation focus trap; status badge semantics label.

3. Vehicle Detail (/transport/vehicles/:id)

Layout

  • AppBar: plateNumber + back; actions: Edit (.update), Delete (.delete).
  • AppCard "Fleet info": model, type, capacity, year, color, status badge, insuranceExpiry (localized date), notes.
  • Audit footer: createdAt / updatedAt / version (read-only, base.repository.ts:57-66).

Wire contract

  • GET /transport/vehicles/:id (transport.controller.ts:42-46); 404 "Vehicle not found." -> AppErrorState + back (transport.service.ts:80).

Interactions

  • Delete -> confirm dialog -> DELETE /transport/vehicles/:id (transport.controller.ts:54-58); 409 route reference -> explain dialog (transport.service.ts:94-99); success -> pop to list + snackbar; emits VehicleDeleted (transport.service.ts:102-109).
  • Edit -> create/update form (§4) prefilled; PATCH returns updated doc.

4. Vehicle Create / Update Form (/transport/vehicles/new or sheet)

Layout

  • Fields: plateNumber*, model*, capacity* (number, min 1), type* (segmented: bus/van/car), year (number), color, insuranceExpiry (date), notes (multiline) - create-vehicle.dto.ts:5-42.
  • Validation per DTO: required IsString for plateNumber/model/type; capacity IsNumber @Min(1); year IsNumber optional (create-vehicle.dto.ts:14-26).
  • Submit button disabled until valid; loading spinner on submit.

Wire contract

  • Create: POST /transport/vehicles (transport.controller.ts:30-34).
  • Update: PATCH /transport/vehicles/:id with UpdateVehicleDto (PartialType, update-vehicle.dto.ts:4; any subset allowed).
  • 409: "Vehicle "" already exists." inline under plateNumber (transport.service.ts:42-44).
  • On success: emit VehicleCreated (transport.service.ts:47-57) -> navigate to detail + snackbar.

Notes

  • plateNumber trimmed server-side (vehicle.schema.ts:21-22); normalize case client-side before submit to reduce false conflicts.

5. Driver List (/transport/drivers)

Layout

  • AppBar: "Drivers" + FAB "New driver".
  • AppListTile: AppAvatar initials, title "firstName lastName", subtitle licenseNumber + phone, trailing status badge (active/on_leave/inactive) + AppMenu (Edit / Delete).
  • AppPagination footer.

Wire contract

  • GET /transport/drivers?page=1&limit=20 (transport.controller.ts:96-100); sort firstName asc (transport.service.ts:211).
  • Item: { _id, firstName, lastName, licenseNumber, phone, email?, status, licenseExpiry?, address?, emergencyContact?, joinedAt?, notes?, ...base } (driver.schema.ts:15-46).

States / interactions

  • Same as §2. Delete 409: "Driver is assigned to a route." (transport.service.ts:245).

6. Driver Detail (/transport/drivers/:id)

Layout

  • AppBar: full name; actions Edit / Delete.
  • AppCard "License & contact": licenseNumber, phone, email, licenseExpiry (warn style when < 30 days - client heuristic; no server check), address, emergencyContact, joinedAt.
  • AppCard "Status": status badge + notes.

Wire contract

  • GET /transport/drivers/:id (transport.controller.ts:102-106); 404 "Driver not found." (transport.service.ts:220).

Interactions

  • Edit -> form (§7); Delete -> confirm -> DELETE /transport/drivers/:id (transport.controller.ts:114-118) with 409 guard (transport.service.ts:241-246).

7. Driver Create / Update Form

Layout

  • Fields: firstName*, lastName*, licenseNumber*, phone*, email (email keyboard, IsEmail - create-driver.dto.ts:23-24), licenseExpiry (date, IsDateString - :28-29), address, emergencyContact, joinedAt (date, :43-44), notes.
  • Update: UpdateDriverDto = PartialType (update-driver.dto.ts:4).

Wire contract

  • Create: POST /transport/drivers (transport.controller.ts:90-94).
  • Update: PATCH /transport/drivers/:id (transport.controller.ts:108-112).
  • Dates converted server-side (transport.service.ts:191-197, 228-234).
  • 409 inline: "Driver with license "" already exists." / "Driver with phone "" already exists." (transport.service.ts:181-183, 187-189).

8. Route List (/transport/routes)

Layout

  • AppBar: "Routes" + FAB "New route".
  • AppListTile: title name, subtitle "startPoint -> endPoint", trailing "N stops" chip + status badge (active/inactive) + AppMenu (Edit / Delete).
  • AppPagination footer.

Wire contract

  • GET /transport/routes?page=1&limit=20 (transport.controller.ts:66-70); sort name asc (transport.service.ts:142).
  • Item: { _id, name, startPoint, endPoint, stops: [{name, order}], vehicleId?, driverId?, status, estimatedDuration?, notes?, ...base } (route.schema.ts:14-39).

Interactions

  • Delete 409: "Route has active student assignments." (transport.service.ts:170) -> dialog offering "View students" (assignments search by route is not exposed; offer student-side lookup instead) or dismiss.

9. Route Detail (/transport/routes/:id)

Layout

  • AppBar: route name; actions: Edit, Delete, "Assign students".
  • AppCard "Route": startPoint -> endPoint, status badge, estimatedDuration, notes.
  • AppCard "Stops": numbered list 1..N from stops ordered by order (route.schema.ts:23-24); start/end pinned as stop 0 / last for clarity (client presentation).
  • AppCard "Resources": assigned vehicle (plateNumber via ref lookup) and driver (name); ids arrive raw (route.schema.ts:26-30) - resolve client-side by fetching detail or batch list; unresolved -> "Not assigned".

Wire contract

  • GET /transport/routes/:id (transport.controller.ts:72-76); 404 "Route not found." (transport.service.ts:151).
  • Note: no populated payload - vehicleId/driverId are ObjectIds; resolution is a client concern (2 extra GETs or list cache).

Interactions

  • Edit -> editor (§10). Delete -> confirm -> DELETE /transport/routes/:id (transport.controller.ts:84-88).

10. Route Editor w/ stops (create & update)

Layout

  • Section A "Basics": name*, startPoint*, endPoint*, estimatedDuration (minutes, number), notes.
  • Section B "Stops": RouteStopEditor - rows "Stop name" + up/down arrows and drag handles; add-stop field; stops carry order 1..N client-maintained (route.schema.ts:23-24); max ~20 stops client guard (no server limit).
  • Section C "Resources": vehicle picker (searchable dropdown from vehicle list cache), driver picker (same); "Clear" sets field absent on submit.
  • Save button: Create -> POST /transport/routes (transport.controller.ts:60-64); Edit -> PATCH /transport/routes/:id (transport.controller.ts:78-82).

Wire contract / validation

  • DTO: stops array of {name: string, order: number} via ValidateNested (create-route.dto.ts:11-19, 34-39); vehicleId/driverId plain strings, converted to ObjectId server-side (transport.service.ts:117-119, 159-161).
  • 409 duplicate name on create: "Route "" already exists." (transport.service.ts:115). On update: name conflict surfaces as 409 from unique index {tenantId, name} (route.schema.ts:44).

States

  • Draft persisted locally (prefs) while editor open; reorder animation m-base (00-shared/08).

a11y

  • Reorder actions exposed as explicit up/down buttons (drag optional); every stop row semantics label "Stop 2: Market Road".

11. Assign Student to Route (bottom sheet / dialog)

Layout

  • Header "Assign student to route".
  • Step 1: route picker (dropdown; label shows name + stop count).
  • Step 2: student picker (search-as-you-type; shows admissionNumber; filter by transportRequired toggle default on - student.schema.ts:53-54).
  • Step 3: shift segmented control morning / evening / both* (assign-route.dto.ts:13-15; IsEnum(['morning','evening','both'])).
  • Optional: stopName (text, or pick from route stops when route selected), notes.
  • Submit "Assign".

Wire contract

  • POST /transport/assign body { routeId, studentId, shift, stopName?, notes? } (assign-route.dto.ts:4-26; transport.controller.ts:120-124).
  • Server sets assignedAt: new Date() and default status active (transport.service.ts:259-266, route-assignment.schema.ts:23-28).

States & errors

  • 409 "Student already assigned to this route." (transport.service.ts:257) -> inline warning + "View existing" opens the student's assignment screen.
  • Success -> StudentRouteAssigned event (transport.service.ts:267-278), snackbar, sheet closes.

Note (client-side only)

  • Capacity overflow guard is NOT server-enforced (transport.service.ts:251-266 does no capacity check) - warn client-side when route's assigned-count (derived from student-side lookups) reaches vehicle capacity (vehicle.schema.ts:27-28); documented as QA item (14 §3).

12. Student Route Assignments (/transport/students/:studentId/transport)

Layout

  • AppBar: student name; action "Assign" (opens §11 prefilled with student).
  • List: AppListTile per assignment - title route name (populated), subtitle "shift + stopName", status badge, menu Remove.
  • AppEmptyState: "No route assigned" + CTA.

Wire contract

  • GET /transport/assignments/:studentId -> array of assignments with routeId populated (transport.controller.ts:126-130, route-assignment.repository.ts:20-24).
  • Remove: DELETE /transport/assignments/:id (transport.controller.ts:132-136); 404 "Assignment not found." -> row already gone, refetch (transport.service.ts:289-290).

Interactions

  • Remove -> confirm dialog (shows route + shift) -> optimistic removal with rollback on error.

13. Live Tracking (/transport/live) (planned)

  • Map with vehicle markers per active route; data source to be defined (IMPLEMENTATION_PLAN.md:229). No API today - screen renders offline banner + "coming soon" when endpoint absent.

14. Bus Attendance (/transport/attendance) (planned)

  • Route/shift picker -> boarding list; QR boarding (forward-looking).

15. Transport Fees (/transport/fees) (planned)

  • Route fee definition + per-student calc (IMPLEMENTATION_PLAN.md:229); fee line item exists in fee structures (see design-docs/fees).

Cross-screen rules

  • All destructive actions: AppDialog confirm, mention consequence (409 guards in transport.service.ts:94-99, 168-171, 241-246).
  • All create actions emit domain events (transport.service.ts:47-57, 121-128, 267-278) - client treats event as completion signal, response is the doc.
  • Pagination: page/limit state per list cubit; meta from envelope (00-shared/06 §3.2; buildPaginationMeta - transport.service.ts:75).