07 — Component Library (Timetable Module)
- A. Module components
- TimetableGrid (hero component, extends AppScrollContainer)
- TimetableSlotCard (extends AppCard)
- GridCell
- DayHeader / TimeGutter
- ConflictBanner (extends AppBanner)
- WeekNavigator
- EntryEditorSheet (extends AppBottomSheet / AppDialog)
- SlotDetailPopover (extends AppMenu surface)
- TeacherScheduleTab (composition)
- B. Reused shared components
- C. Composition rules
Module-specific components; all reference the shared library (00-shared/03). Every component listed here is a client composition over the implemented API — none invent backend data.
A. Module components
TimetableGrid (hero component, extends AppScrollContainer)
- Props:
entries: List<TimetableEntry>,scope(class | teacher | room),catalogs(subject/teacher/room/class name resolvers),readOnly,onCellTap,onSlotTap,onSlotDrag(desktop),onLongPress. - Structure: pinned
TimeGutter(left) + 6DayHeaders (Mon–Sat, fromDayOfWeek,timetable.schema.ts:7-14) + row ofGridCells; rows = union ofstartTimevalues, ascending (server sort contract,timetable.service.ts:51,58). - Slot placement: absolute-positioned
TimetableSlotCards within each day column, height ∝endTime − startTime(min 1 row). - Perf: row/cell widgets built via
ListView.builder-style lazy construction;RepaintBoundaryper slot card; scroll controllers shared gutter/header (15 §9). - Desktop: each slot wrapped in
Draggable<TimetableEntry>; cells areDragTarget<TimetableEntry>(10 §2, 15 §8). Mobile:LongPressDraggableoff — long-press opens actions instead. - A11y: single
Semanticsnode per slot with full label (06 §S1).
TimetableSlotCard (extends AppCard)
- Props:
entry(subjectLabel, teacherLabel, roomLabel, startTime, endTime),conflict: bool(client-derived same-grid overlap),onTap,onLongPress,onDrag(desktop). - Content:
{subject shortName}(titleSmall),{teacher} · {room}(bodySmall); conflict →errorContainerborder +event_busyicon + label "double-booked". - Room label shows room
code(room.schema.ts:21-22) — absent room → no chip. - Tokens:
surfaceContainerLow,e-1; hover (desktop)e-2+ tint (10 §2).
GridCell
- Props:
day,startTime,endTimeimplicit row,onTap(empty cell → S4),acceptsDrop(desktop). - Empty state: dashed outline (
outlineVariant), subtle hover fill. - A11y: cell tap targets ≥ 44 dp; announced "Monday, 08:00, free".
DayHeader / TimeGutter
DayHeader: day label (short) + date-of-current-week (client computed); pinned top, sticky.TimeGutter:HH:MMlabels (mono,tabularFigures, 02 §2), pinned left.- A11y: announced as column/row headers (06 §S1).
ConflictBanner (extends AppBanner)
- Props:
kind: teacher | room,clash(day, startTime, endTime, otherPartyLabel),onDismiss,onChangeTime. - Copy: "Teacher already booked Tue 08:00–09:00 (R. Kumar)" / "Room already in use Tue 08:00–09:00 (Room 201)".
- Tokens:
errorContainer/onErrorContainer(11 §2); live-region announce.
WeekNavigator
- Props:
offset,onPrev,onNext,onToday. - Content:
‹[Week of {date} · {day range}]›+ "Today" text button. - Pure client state (06 §S6); disabled state: none (always navigable).
- Keyboard:
←/→when grid focused (desktop).
EntryEditorSheet (extends AppBottomSheet / AppDialog)
- Props:
prefill(classId, day, startTime, endTime, subject/teacher/room from duplicate),onSaved(entry),onConflict(409). - Hosts the F1 form (08) +
ConflictBanner; phone sheetr-xltop corners, desktop dialoge-4.
SlotDetailPopover (extends AppMenu surface)
- Props:
entrywith resolved names; actions: Duplicate (→ editor), Edit/Delete(planned)— hidden unlesstimetable.create+ coordinator role.
TeacherScheduleTab (composition)
- Wraps
TimetableGridwith teacher scope (GET /timetable?teacherId=,timetable.controller.ts:26) in the Teachers module's detail screen — same grid, read-only for teacher role.
B. Reused shared components
| Shared component | Used in |
|---|---|
AppDropdown | scope picker (S1/S2), class/day/year pickers (S4) |
AppSearchBar-style searchable picker | subject/teacher/room pickers (S4) |
AppSkeleton | grid loading ghost |
AppEmptyState | S1/S2/S3 empty |
AppErrorState | grid/editor errors (envelope code) |
AppBanner | offline reads, conflict (via ConflictBanner) |
AppSnackbar | "Slot added", "Slot removed (planned)" |
AppFAB | S1 "Add slot" (hidden for teacher role) |
AppButton | Save (filled), Cancel (text), Change time (outlined) |
AppTimePicker-style field | start/end time inputs (S4) |
AppChips | day picker quick-select (S4) |
AppOfflineBanner | module-wide offline reads |
AppSectionHeader | grid section titles, editor groups |
AppDialog | destructive confirm (planned), editor on desktop |
C. Composition rules
- No screen renders raw ObjectIds — every ref resolved via catalogs (subjects, teachers, rooms, classes, years) or "—" fallback (04 §6).
DayOfWeekdisplay order fixed: Mon→Sat (timetable.schema.ts:7-14) — never locale-reordered.- Conflict styling centralized in
TimetableSlotCard/ConflictBanner— no ad-hoc red in screens (11 §2). - Times rendered
mono+tabularFigureseverywhere (gutter, cards, editor). - New components only if shared library lacks the need (YAGNI — check shared first).
- Golden tests:
TimetableSlotCard(normal/conflict × light/dark),TimeGutter,ConflictBanner,WeekNavigator(00-shared/03 §G).