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

Detailed screen-by-screen specifications. Data sources are the exact endpoints from src/modules/rooms/rooms.controller.ts; fields from room.schema.ts and create-room.dto.ts. Markings: (planned) backend gap, (proposed) client contract, (forward-looking) future surface. Motion per 00-shared/08, a11y per 00-shared/09.


S1 — Room List (/rooms)

1.1 Purpose & entry

Browse the tenant's rooms; filter; create; open detail. Entry: admin workspace nav (gated rooms.read), deep link /rooms. Exit: row → S2, FAB → S3-create, back → workspace.

1.2 Layout (phone)

┌─────────────────────────────────┐
│ AppBar "Rooms"        [count]   │
├─────────────────────────────────┤
│ [Search]  (proposed, client)    │
│ [Type chips] [Building chips]   │
├─────────────────────────────────┤
│ ┌ RoomCard 1 ────────────────┐  │
│ │ [icon] Lab 2        LAB-02  │  │
│ │        Building B · Cap 40  │  │
│ │        projector, AC        │  │
│ └─────────────────────────────┘  │
│ ┌ RoomCard 2 …                ┐  │
│ └─────────────────────────────┘  │
│ (skeleton rows while loading)    │
├─────────────────────────────────┤
│                    [FAB +]      │
└─────────────────────────────────┘

1.3 Data contract

ItemDetailSource
EndpointGET /api/v1/rooms?page=1&limit=20rooms.controller.ts:30-34
Responseenvelope data: Room[], meta: PaginationMeta {page,limit,totalItems,totalPages,hasNext,hasPrevious}rooms.service.ts:25-34; pagination-query.dto.ts:32-39
Page/limitpage ≥ 1 default 1; limit default 20 (controller default; shared DTO caps 1–100 — pagination-query.dto.ts:13-19 but not applied on this route)rooms.controller.ts:32
Sort/filter/searchnone — list is Mongo natural order (OQ-3); client-side filter/search (proposed); server params (planned)rooms.controller.ts:32; rooms.service.ts:29-32

1.4 States

StateUITrigger
Loading6 skeleton RoomCardsfirst load / refresh
Successcards; count badge in AppBar200
EmptyAppEmptyState "No rooms yet" + "Add room" (gated)totalItems = 0
Load-morebottom spinner rowscroll hits 80% while hasNext
ErrorAppErrorState + Retry (re-emits LoadFirst)non-401 failure
OfflineAppOfflineBanner + cached listconnectivity lost
Permissionno nav entry; route guard → 403 screenrooms.read absent

1.5 Interactions

  • Row tap → S2 (push; on tablet master-detail select without push).
  • Search field (debounce 300 ms (proposed)): client-side case-insensitive match on name/code/building; resets to page 1.
  • Type chips (multi-select (proposed)): client filter on type; "All" chip resets.
  • Building chips derived from loaded + cached items (proposed).
  • Pull-to-refresh: bypasses cache, reloads page 1.
  • Infinite scroll: while hasNext fetch page+1, append, dedupe by _id.
  • Delete row (menu) → S4 dialog; on success remove row + decrement count locally (remove() is void — rooms.service.ts:48).

1.6 Analytics (proposed)

rooms.list.open, rooms.list.filter.{type,building}, rooms.list.search, rooms.list.load_more, rooms.list.refresh, rooms.list.row_tap.

1.7 A11y & motion

  • Cards: single semantics label "Lab 2, code LAB-02, capacity 40, building B".
  • Loading announced via live region; m-fast fade-in of cards (00-shared/08).
  • Focus order: search → chips → cards → FAB.

S2 — Room Detail (/rooms/:id)

2.1 Purpose & entry

Full room profile. Entry: S1 row, deep link studylyon://rooms/:id (forward-looking). Exit: back; Edit → S3; Delete → S4 → S1.

2.2 Layout

┌───────────────────────────────────┐
│ AppBar ← "Room"        [⋮ menu]   │
├───────────────────────────────────┤
│ ┌ header card ─────────────────┐  │
│ │ [icon] Lab 2        LAB-02   │  │
│ │       Building B · Cap 40    │  │
│ └──────────────────────────────┘  │
│ Meta grid:                        │
│  Type: lab   Capacity: 40         │
│  Building: B  Code: LAB-02        │
│ Facilities: [projector][AC][+2]   │
│ (planned: Availability section)   │
│ (forward-looking: QR card)        │
├───────────────────────────────────┤
│ [Edit]  [Delete]   (gated)        │
└───────────────────────────────────┘

2.3 Data contract

ItemDetailSource
EndpointGET /api/v1/rooms/:idrooms.controller.ts:36-40
Responseenvelope data: Roomrooms.service.ts:36-40
404RESOURCE_NOT_FOUND "Room not found." — incl. cross-tenant id (no leak)rooms.service.ts:38; base.repository.ts:24-29
Bad id400 VALIDATION_ERROR (CastError mapping)shared filter

2.4 States

StateUITrigger
Loadingheader skeletonfetch
Successfull profile200
NotFoundAppEmptyState "Room not found" + back404
ErrorAppErrorState retrynetwork/5xx
Offlinebanner + cached docconnectivity

2.5 Interactions

  • Edit → S3 prefilled (only when rooms.update).
  • Delete → S4 dialog (only when rooms.delete).
  • Menu (proposed): "Copy code" (clipboard — powers future QR/scanner flows).
  • Facilities: first 2 chips inline, tap "+n" → expand all (AnimatedSize).
  • Availability section (planned): shows upcoming bookings when bookings module exists.
  • QR card (forward-looking): renders code as QR for door signage; hidden until signage feature ships.

2.6 Analytics (proposed)

rooms.detail.open, rooms.detail.edit, rooms.detail.menu.copy_code, rooms.detail.delete.start.

2.7 A11y & motion

  • Header is one semantics group; facilities chips individually tappable with labels.
  • Delete/mutation feedback via m-fast; snackbar auto-dismiss with undo (proposed) (no undo API — restore = recreate).

S3 — Room Editor — Create (/rooms/new) / Edit (/rooms/:id/edit)

3.1 Purpose & entry

Create (from S1 FAB, gated rooms.create) or edit (from S2, gated rooms.update) a room. Exit: save success → pop + snackbar; cancel → discard confirm if dirty; back arrow same.

3.2 Layout (scroll form)

┌───────────────────────────────────┐
│ AppBar "New room" / "Edit room"   │
│ [Save] (AppBar action)            │
├───────────────────────────────────┤
│ Name *        [_____________]     │
│ Code *        [LAB-02        ] ✓  │   ← uniqueness hint (proposed)
│ Type          [classroom ▾]       │
│ Capacity      [ 40 ]              │   ← numeric, min 1 (proposed)
│ Building      [ Building B ]      │
│ Facilities    [projector][AC][+]  │   ← chip input
├───────────────────────────────────┤
│ (inline field errors / 409 box)   │
└───────────────────────────────────┘

3.3 Data contract

ItemDetailSource
CreatePOST /api/v1/rooms body CreateRoomDtorooms.controller.ts:24-28; create-room.dto.ts:11-38
UpdatePATCH /api/v1/rooms/:id body CreateRoomDtofull DTO required (name+code mandatory on PATCH; no update-room.dto.ts — OQ-6)rooms.controller.ts:42-46
409create: "Room code … already exists."; update: no check (OQ-1)rooms.service.ts:19-21
400VALIDATION_ERROR details[] (e.g. type not in enum)shared filter
404update of unknown id: "Room not found."rooms.service.ts:44

3.4 Field specs

FieldRequiredTypeServer validation (source)Client rules (proposed)
nameyesstring@IsString, trim (create-room.dto.ts:12-14; room.schema.ts:18-19)non-empty; ≤ 120 chars; whitespace-trimmed
codeyesstring@IsString, trim (create-room.dto.ts:16-18; room.schema.ts:21-22)non-empty; unique hint; auto-uppercase suggestion; pattern [A-Z0-9-]{2,24} (proposed)
capacitynonumber@IsNumber only — no min/zero guard (create-room.dto.ts:20-23; OQ-7)integer ≥ 1; < 1 blocked; > 10 000 warning (proposed)
typeno (default classroom)enum@IsEnum(RoomType) (create-room.dto.ts:25-28; room.schema.ts:7-14,27-28)dropdown from enum; default classroom
buildingnostring@IsString (create-room.dto.ts:30-33)free text + suggestions from existing (proposed)
facilitiesnostring[]@IsArray of strings (element type unchecked) (create-room.dto.ts:35-38)chip input, dedupe, max 12 chips (proposed)

3.5 States & interactions

StateUI
idleform editable; Save enabled when required valid
validatingasync code-uniqueness check (proposed)GET list client-side match; spinner under field
submittingSave spinner; fields disabled
success (201/200)pop; snackbar "Room created"/"Room updated"; emit RoomChanged to list/detail (cache invalidation)
409inline error under Code field; form kept; scroll-to-field (create). Update: blocked client-side until server check (planned)
400field errors mapped from details[]; focus first invalid
404 (edit)pop + snackbar "Room not found"
offlinewrite blocked + banner
  • Dirty tracking: compare against initial model; back-arrow with dirty → discard dialog.
  • Facilities chip input: type + Enter → chip; tap × removes; duplicates ignored.
  • Capacity field: numeric keyboard, comma/point filtered (proposed).

3.6 Analytics (proposed)

rooms.editor.create.open, rooms.editor.create.submit, rooms.editor.create.success, rooms.editor.create.duplicate, rooms.editor.edit.open, rooms.editor.edit.submit, rooms.editor.edit.success, rooms.editor.discard.

3.7 A11y & motion

  • Labels linked to fields; errors in live regions; m-fast focus transitions.
  • Keyboard avoidance; next-field on Enter; Save via keyboard action (proposed).

S4 — Delete Room — confirm dialog

4.1 Spec

ItemDetailSource
TriggerDetail menu / list row menu (gated rooms.delete)permissions.constants.ts:53
Contenttitle "Delete room"; copy "Type {name} to confirm. This can't be undone." (soft-delete: actually recoverable via DB, not via UI); AppTextField confirm; buttons Cancel / Delete (disabled until text matches room.name)
EndpointDELETE /api/v1/rooms/:idrooms.controller.ts:48-52
Response200, no body payload (void — rooms.service.ts:48); client removes row locallyrooms.service.ts:48-51
Errors404 → snackbar "Room not found" (already deleted); network → snackbar Retryrooms.service.ts:50
Gapno in-use guard (OQ-4): informational copy "This room may appear in timetables" (proposed); server block (planned)rooms.service.ts:48-51
Analyticsrooms.delete.confirm, rooms.delete.cancel (proposed)

5. Cross-screen rules

RuleValue
Version/optimistic lockPATCH increments version (base.repository.ts:57-66); client does not send version today; concurrent-edit detection (proposed) via If-Match (planned)
401/refreshsingle-flight refresh + replay, then session expiry (00-shared/06 §3.6)
Offlinereads cached; writes blocked (no offline queue for this module)
Empty list after filtersAppEmptyState "No rooms match your filters" + clear-filters action
Deep linksstudylyon://rooms , studylyon://rooms/:id (forward-looking)
Loading budgetslist ≤ 300 ms p95 target; detail ≤ 250 ms (00-shared/10 §1)