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 (Teachers 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.


S1 — Teachers List

Layout (phone / tablet / desktop)

AppBar: "Teachers" | [Search icon → /search]
─────────────────────────────────────────────
AppSearchBar (q)                     [Filter chips ▾]
[Filter chips row: status · department · designation]
─────────────────────────────────────────────
[AppListTile × N]  avatar  Name (titleMedium)
                   Employee #TCH001 · Dept (bodyMedium)
                   [StatusChip]        [›]
─────────────────────────────────────────────
[infinite scroll loader / end-of-list footer]
FAB: "Add teacher"
  • Breakpoints: phone single column list; tablet 2-column grid of AppCards; desktop master-detail (tap row → detail in right pane, 05_Global_Information_Architecture.md:58).

Behaviour & states

StateUISource
Initial/Loading8 × AppSkeleton rows06_State_Management.md §3.1
Success + datalist, status chips colored
Success + empty (no filters)AppEmptyState "No teachers yet" + CTA add
Success + empty (filters)AppEmptyState "No teachers match" + clear filters
ErrorAppErrorState(code, message, retry)envelope error.code

Data & interactions

  • Fetch: GET /teachers?page=&limit=20 (teacher.controller.ts:27-29); meta drives hasNext (buildPaginationMeta, pagination-query.dto.ts:41-55).
  • q/sort params: sent but ignored by TeacherService.find (teacher.service.ts:66-78) — client filters cached list by q locally; (planned) server search (OQ-3).
  • Filter chips: status (4 enum values, teacher.schema.ts:7-12), department, designation — no server filter params exist → client-side filtering of loaded pages (OQ-3); document as limitation.
  • Row tap → S2. Overflow menu: Edit → S6, Deactivate → S8.
  • Pull-to-refresh (RefreshIndicator) bypasses cache; FAB scroll-hide behaviour.

Accessibility

  • Chips: FilterChip with selected state semantics (09_Accessibility_Baseline.md §5).
  • Status never color-only: chip = icon + label (status enum text) (09 §9).
  • Live region announces search result count.

S2 — Teacher Detail · Profile tab

Layout

AppBar: "{displayName}"          [Edit] [⋮ menu]
─────────────────────────────────────────────
Header card (AppCard, tonal):
  [Avatar 48]  Name (titleLarge)   StatusChip (labelLarge)
               Employee #TCH001 (mono) 
─────────────────────────────────────────────
Info section (AppSectionHeader "Employment"):
  InfoRow  Department        "Science"
  InfoRow  Designation       "Teacher"        (designations examples,
  InfoRow  Joining date      "12 Aug 2024"      COLLECTIONS.md:1560-1574)
  InfoRow  Qualification     "M.Sc. Physics"
  InfoRow  Experience        "6 years"
AppSectionHeader "Subjects taught":   [SubjectBadge × N]
AppSectionHeader "Class teacher for":[ClassBadge × N]
AppSectionHeader "Metadata" (only if non-empty): key/value rows

Data & states

  • GET /teachers/:id (teacher.controller.ts:30-32). 404 → AppErrorState RESOURCE_NOT_FOUND + "back to list" (treat as removed/soft-deleted).
  • Reference names resolved from cached catalogs; missing ref id → "—".
  • Empty subjects[]/classTeacherFor[] → "Not set" bodyMedium text, no empty-state.

Interactions

  • Edit → S6 (prefill from current doc).
  • Menu → Deactivate (S8). If employmentStatus === 'terminated' → deactivate hidden.
  • If on_leaveAppBanner info: "On leave since {updatedAt}".

S3 — Teacher Detail · Assignments tab

Layout

AppSectionHeader "Assignments"   [Academic year ▾]  [+ Add]
─────────────────────────────────────────────────────
[AssignmentCard × N]
   subject name (titleMedium)         [⋮ remove]
   class name · academic year (bodyMedium)
─────────────────────────────────────────────────────
empty: AppEmptyState "No assignments for {year}" + CTA

Data & states

  • GET /subject-assignments/by-teacher/:teacherId?academicYearId= (subject-assignment.controller.ts:30-35); academicYearId required in practice (subject-assignment.service.ts:26-31 returns [] when missing).
  • Year picker from GET /academic-years (academic-year.controller.ts:27-30), default = isCurrent (academic-year.schema.ts:31-32).
  • Loading: 3 skeleton cards. Error: inline AppErrorState(compact) + retry.
  • Remove: confirm AppDialogDELETE /subject-assignments/:id (subject-assignment.controller.ts:36-38); 404 → treat as already removed.

S4 — Teacher Detail · Schedule tab

Layout

AppSectionHeader "Weekly schedule"  [Week ▾ (current week)]
Grid: 7 columns (Mon..Sun) × time rows; period cards:
   "10:00–10:45 · Mathematics · 10-A"

Data & states

  • GET /timetable?teacherId=<id> (timetable.controller.ts:21-27), server-sorted dayOfWeek, startTime (timetable.service.ts:55-60).
  • Empty → "No classes scheduled." Stale-year handling: show current term by default.

S5 — Create Teacher form

Layout & flow

AppBar "Add teacher"  [Cancel] [Save]
─────────────────────────────────────────
1. Identity        user picker* (from Users list, email searchable)
2. Employment      employeeNumber* (mono, e.g. TCH001)
3. Org placement   department ▾ · designation ▾ (pickers from staff catalogs)
4. Timeline        joiningDate (date picker) · employmentStatus ▾ (default active)
5. Qualifications  qualification (multiline) · experienceYears (number)
6. Assignments     subjects multi-select (chips) · classTeacherFor multi-select
[Save] sticky bottom on phone; top-right on desktop

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

FieldServer (create-teacher.dto.ts)Client rules
userIdrequired @IsMongoId (line 6-7)required; from user picker
employeeNumberrequired @IsString (10-11)required; trim; unique check against loaded roster (server 409 = race-safe)
departmentIdoptional @IsMongoId (15-16)picker
designationIdoptional @IsMongoId (20-21)picker
joiningDateoptional @IsDateString (25-26)date picker; ≤ today (proposed)
employmentStatusoptional string, enum hint ['active','inactive','on_leave','terminated'] (28-33)default active; server does not enum-validate → client restricts to 4 values
qualificationoptional @IsString (37-38)free text
experienceYearsoptional number, no validator (41-42)client: int ≥ 0 (proposed)
subjectsoptional string[], no array/MongoId validator (45-46)client: array of valid ObjectIds
classTeacherForoptional string[], no validator (48-52)client: array of valid ObjectIds

Submit & errors

  • POST /teachers → 200 data = teacher doc (unwrapped by envelope interceptor).
  • 409 → inline AppBanner(error) per field ("A teacher profile already exists for this user." / "Employee number … already exists.", teacher.service.ts:29-37).
  • 400 → map error.details[].field to form fields (00-shared/07 §3).
  • Double-submit guard (anti-double-submit, 08_Interaction_&_Motion.md §6).
  • Offline → blocked with banner (no offline write queue for teachers, 00-shared/06 §3.7).

S6 — Edit Teacher form

  • Same spec as S5 with: userId field hidden & immutable (update-teacher.dto.ts:4-8 lacks userId), metadata editor (proposed) simple key/value rows (update-teacher.dto.ts:51-53), prefilled values from detail doc.
  • Submit PATCH /teachers/:id → 200 updated doc; 404 → leave form + list.
  • metadata is Record<string, unknown> — client sends only primitive values (proposed).

S7 — Subject Assignment Editor (bottom sheet on phone, dialog on desktop)

Layout

Sheet "Assign {teacherName}"          [Add]
  Subject ▾ (required, searchable picker, GET /subjects)
  Class ▾   (required, year-scoped, GET /classes)
  Academic year ▾ (default isCurrent)
[Add] disabled until all three set

Rules

  • All four ids @IsMongoId required (create-subject-assignment.dto.ts:5-19); teacherId prefilled from context (S3) or user-selected.
  • Client duplicate guard: exact (teacher, subject, class, academicYear) already in matrix → disable Add + inline error "Already assigned" (server gap, OQ-2).
  • Submit POST /subject-assignments → 200 doc appended to matrix; snackbar "Assigned".
  • Errors: 400 → inline; 500 → generic + requestId; no optimistic insert (server confirms only, 00-shared/06 §3.5).

S8 — Deactivate confirmation dialog

AspectSpec
Title"Deactivate {displayName}?"
Body"{employeeNumber} will be hidden from all lists. Historical assignments and audit records are kept. This action cannot be undone in the app."
Actions[Cancel] [Deactivate — destructive AppButton, error colors]
FlowDELETE /teachers/:id → 200 → snackbar "Teacher deactivated" → pop to list; 404 → snackbar "Already deactivated" → pop
Focustrapped in dialog; ESC/back cancels (03_Component_Library.md AppDialog)
A11ySemantics destructive-action label; haptic heavyImpact() on confirm (08 §3)

S9/S10 — My Teaching / My Schedule (teacher self) — (planned)

  • Spec deferred to backend support for self-resolution (OQ-2); wire behaviour per S3/S4 with read-only treatment (no add/remove for teacher role).
  • Own-profile banner when on_leave: "Your profile is on leave — schedule may be reassigned."