06 - Screen Specifications (Transport Module)
- 0. Shared states (every screen)
- 1. Transport Overview (
/transport) - 2. Vehicle List (
/transport/vehicles) - 3. Vehicle Detail (
/transport/vehicles/:id) - 4. Vehicle Create / Update Form (
/transport/vehicles/newor sheet) - 5. Driver List (
/transport/drivers) - 6. Driver Detail (
/transport/drivers/:id) - 7. Driver Create / Update Form
- 8. Route List (
/transport/routes) - 9. Route Detail (
/transport/routes/:id) - 10. Route Editor w/ stops (create & update)
- 11. Assign Student to Route (bottom sheet / dialog)
- 12. Student Route Assignments (
/transport/students/:studentId/transport) - 13. Live Tracking (
/transport/live)(planned) - 14. Bus Attendance (
/transport/attendance)(planned) - 15. Transport Fees (
/transport/fees)(planned) - Cross-screen rules
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)
| State | Rendering | Source of truth |
|---|---|---|
| idle/loading | AppSkeleton per block; list rows as skeleton tiles | 00-shared/06 §3.1 |
| error offline | AppOfflineBanner + native retry; data shown stale if cached | 00-shared/10 §2 |
| error API | AppErrorState(code, message, onRetry); 401 -> re-auth; 403 -> permission copy | 00-shared/03 |
| empty | AppEmptyState with CTA | 00-shared/03 |
| 409 conflict | inline field/dialog message with corrective action (see each screen) | transport.service.ts:38-45, 113-116, 177-190, 252-258 |
| 404 | detail screens -> AppErrorState + back | transport.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:
StatTilegrid (Vehicles / Routes / Drivers) - count frommeta.totalItemsof each list call withlimit=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
AppErrorStatewith 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), titleplateNumber(monospace), subtitlemodel + capacity seats, trailingAppBadge(status: active=success, maintenance=warning, inactive=neutral) +AppMenu(Edit / Delete). AppPaginationfooter (page, limit from state;metafrom envelope).
Wire contract
GET /transport/vehicles?page=1&limit=20(transport.controller.ts:36-40); server sort:plateNumberasc (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 ->
AppDialogconfirm -> 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; emitsVehicleDeleted(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
IsStringfor plateNumber/model/type; capacityIsNumber @Min(1); yearIsNumberoptional (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/:idwithUpdateVehicleDto(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:AppAvatarinitials, title "firstName lastName", subtitlelicenseNumber + phone, trailing status badge (active/on_leave/inactive) +AppMenu(Edit / Delete).AppPaginationfooter.
Wire contract
GET /transport/drivers?page=1&limit=20(transport.controller.ts:96-100); sortfirstNameasc (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).AppPaginationfooter.
Wire contract
GET /transport/routes?page=1&limit=20(transport.controller.ts:66-70); sortnameasc (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 fromstopsordered byorder(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/driverIdare 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 carryorder1..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:
stopsarray of{name: string, order: number}viaValidateNested(create-route.dto.ts:11-19, 34-39);vehicleId/driverIdplain 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
transportRequiredtoggle 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/assignbody{ routeId, studentId, shift, stopName?, notes? }(assign-route.dto.ts:4-26;transport.controller.ts:120-124).- Server sets
assignedAt: new Date()and default statusactive(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 ->
StudentRouteAssignedevent (transport.service.ts:267-278), snackbar, sheet closes.
Note (client-side only)
- Capacity overflow guard is NOT server-enforced (
transport.service.ts:251-266does 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:
AppListTileper 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 withrouteIdpopulated (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:
AppDialogconfirm, mention consequence (409 guards intransport.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;
metafrom envelope (00-shared/06 §3.2;buildPaginationMeta-transport.service.ts:75).