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

The definitive per-screen spec. Wire contract per 00-shared/07. All field names and rules cite the DTO/schema they come from. (planned) = backend/roadmap gap. The weekly grid (S1) is the hero component — largest spec, shared by S2/S3.


S1 — Class Timetable Grid (hero)

Layout (phone / tablet / desktop)

AppBar: "Timetable · {Class name}"   [scope ▾ class|teacher] [⋮]
─────────────────────────────────────────────────────────────
[Class picker ▾]  [WeekNavigator ‹  Week of 03 Aug  ›  Today]
─────────────────────────────────────────────────────────────
TimeGutter | Mon      | Tue      | Wed      | Thu      | Fri      | Sat
08:00      | [slot]   |          | [slot]   |          |          |
09:00      | [slot]   | [slot]   |          | [slot]   | [slot]   |
10:00      |          |          | [slot]   |          |          |
   ...     (rows = distinct startTime across the week)
─────────────────────────────────────────────────────────────
FAB: "Add slot"
  • Columns: 6 day columns from DayOfWeek enum — mondaysaturday (timetable.schema.ts:7-14). No Sunday column (Sunday is not a legal value).
  • Rows: derived from the union of startTime values present in the week's data, sorted ascending (server contract dayOfWeek, startTime, timetable.service.ts:51). A slot spans row-height × duration (endTime − startTime), minimum 1 row.
  • Slot card content (TimetableSlotCard, 07 §A2): {subject shortName} (titleSmall), {teacher name} · {room code} (bodySmall), optional room icon. Conflicts (same teacher/room in another slot of the SAME grid) get an errorContainer border + warning icon — client-derived (server allows same-class overlaps, OQ-1).
  • Breakpoints: phone → single-column day-strip list (day header + slots stacked); tablet (≥ 600 dp) → 2-day panes; desktop (≥ 1024 dp) → full 6-column grid, drag & drop enabled (15 §8).

Behaviour & states

StateUISource
Loadingskeleton grid (6 columns × 6 ghost rows, shimmer)00-shared/06 §3.1
Success + datagrid, slots placed
Success + emptyAppEmptyState "No classes scheduled" + CTA "Add slot"
ErrorAppErrorState(code, message, retry)envelope error.code
Offlinecached last-good grid + AppOfflineBanner00-shared/06 §3.3
Permissiontimetable.read gate (permissions.constants.ts:44)client-side, 01 §5

Data & interactions

  • Fetch: GET /timetable?classId=<id> (timetable.controller.ts:23) — non-paginated array, server-sorted dayOfWeek, startTime (timetable.service.ts:48-53). Client never re-sorts.
  • Both classId + teacherId supplied → classId wins (timetable.controller.ts:26-27).
  • Tap empty cell → S4 (entry editor, prefilled class + day + time).
  • Tap slot → detail popover (subject, teacher, room, time, year) with actions: Duplicate (opens S4 prefilled), Edit/Delete (planned) — no endpoints exist (01 §10).
  • Desktop drag & drop: drag slot card onto a target cell → opens S4 prefilled with the new day/time and the slot's subject/teacher/room (a create, since no PATCH exists; original remains until the new entry is saved and the user deletes it (planned) — see 10 §2 and OQ-2).
  • Long-press slot (mobile) → same actions as detail popover (10 §1).
  • Pull-to-refresh bypasses cache; scope switch re-fetches; week navigator re-filters locally (no server date concept, 05 §S6).

Accessibility

  • Grid has one Semantics container per slot: "Mathematics, R. Kumar, Room 201, Monday 08:00–09:00".
  • Headers: dayOfWeek + time gutter announced as column/row headers (09 §6).
  • Conflict slots never color-only — border + warning icon + label ("double-booked", 09 §9).
  • Keyboard (desktop): arrow keys move cell focus; Enter opens editor; focus ring primary 2 dp (09 §6).

S2 — Teacher Timetable Grid

  • Same grid component, scope = teacher: GET /timetable?teacherId=<id> (timetable.controller.ts:26), sorted dayOfWeek, startTime (timetable.service.ts:55-60).
  • Slot content: {subject shortName} · {class name} (teacher's own slots).
  • Teacher role → read-only: no FAB, no drag & drop, no long-press editor actions (02 §3); slot tap shows detail popover only.
  • Empty → "No classes scheduled" (no CTA for teacher role).
  • Coordinator/admin view additionally shows per-slot room code and opens S4 on empty cells.

S3 — Room Timetable Grid — (planned)

  • Client composition: fetch GET /timetable?classId= for every class whose default room or entries reference the room; merge slots; render read-only.
  • No roomId filter on the API (timetable.controller.ts:22-25); native ?roomId= is (planned) (IMPLEMENTATION_PLAN.md:226).
  • Slot content: {subject shortName} · {class name} · {teacher}.
  • Empty → "No bookings for this room".

S4 — Entry Editor (bottom sheet on phone / dialog on desktop)

Layout

Sheet "New timetable slot"                        [Save]
──────────────────────────────────────────────────
  Class ▾          (required, prefilled from grid)
  Subject ▾        (required, searchable, GET /subjects)
  Teacher ▾        (required, searchable, GET /teachers)
  Room ▾           (optional, GET /rooms)
  Day ▾            (Mon..Sat — from DayOfWeek enum)
  Start time       08:00   (24 h, HH:MM — MUST be zero-padded)
  End time         09:00   (> start; client-enforced)
  Academic year ▾  (default isCurrent)
──────────────────────────────────────────────────
[ConflictBanner — only when 409 / pre-flight warning]

Field-by-field validation (server DTO vs client)

FieldServer (create-timetable-entry.dto.ts)Client rules
classIdrequired @IsMongoId (6-8)required; prefilled
subjectIdrequired @IsMongoId (10-12)required picker
teacherIdrequired @IsMongoId (14-16)required picker
roomIdoptional @IsOptional @IsMongoId (18-21)optional picker
dayOfWeekrequired @IsEnum(DayOfWeek) (23-25)6 values from timetable.schema.ts:7-14
startTimerequired @IsString (27-29) — format unvalidated24 h HH:MM (e.g. 08:00); regex `^([01]\d
endTimerequired @IsString (31-33) — no >start check server-sideend > start client-enforced (proposed); 1-row minimum
academicYearIdrequired @IsMongoId (35-37)required; default isCurrent from GET /academic-years

Pre-flight conflict check (client, best-effort)

  • Before submit, consult cached per-day grids: same teacherId overlapping → warn "Teacher already booked {day} {start}–{end}"; same roomId overlapping → warn "Room already in use". Server remains authoritative (409, races possible).
  • Same-class overlap is NOT warned — server permits it (OQ-1); editor copy must not claim a class-level conflict check.

Submit & errors

  • POST /timetable (timetable.controller.ts:14-18) → 200 data = entry doc.
  • 409 ConflictException "Schedule conflict detected" (timetable.service.ts:28) → ConflictBanner in-sheet, form values kept, focus conflicting picker.
  • 400 → map error.details[].field to form fields (00-shared/07 §3).
  • Double-submit guard; offline → blocked with banner (no offline write queue).
  • Success → slot inserted into grid (AnimatedList, 10 §4), snackbar "Slot added".

S5 — Conflict Banner (spec)

AspectDetail
Trigger409 on submit; pre-flight warning on picker change
PlacementS4, between fields and Save
Copyteacher: "Teacher already booked {day} {start}–{end}" / room: "Room already in use {day} {start}–{end}" — mapped from the generic server message + local clash data
Actions[Dismiss] [Change time] (focuses time fields)
TokenserrorContainer/onErrorContainer, warning icon event_busy (11 §2)
A11ySemanticsService.announce on appear (live region)

S6 — Week Navigation

  • WeekNavigator (07 §A7): / shift displayed week, Today resets.
  • Pure client state (no server date dimension — timetable.schema.ts:30-37 stores weekday + time only); week offset stored in shell state (04 §4).
  • Future date-aware model (specific dates, push reminders) (forward-looking).