15 — Flutter Implementation Guide (Teachers Module)
- 1. Module folder
- 2. Domain models
- 3. Repository
- 4. Cubits
- 5. Router
- 6. Theme & components
- 7. i18n keys
- 8. Tests
- 9. Known backend gaps to coordinate (from 14-QA)
Extends 00-shared/11. Forward-looking: no client repo exists (shared ledger A1). Everything below derives from
src/modules/teachers/**,src/modules/academics/**(subject-assignments),src/modules/staff/**(catalogs),src/modules/timetable/**.
1. Module folder
lib/features/teachers/
├── data/
│ ├── dto/teacher_dto.dart # envelope payload mapper
│ ├── dto/subject_assignment_dto.dart
│ ├── dto/teacher_draft_dto.dart # create/update payloads (F1/F2)
│ ├── models/teacher.dart # + EmploymentStatus enum
│ ├── models/subject_assignment.dart
│ └── repositories/teacher_repository.dart
├── domain/
│ └── teachers_service.dart # optional: catalog-join helper
└── presentation/
├── cubit/teachers_list_cubit.dart
├── cubit/teacher_detail_cubit.dart
├── cubit/teacher_form_cubit.dart
├── cubit/assignment_editor_cubit.dart
├── pages/teachers_list_page.dart
├── pages/teacher_detail_page.dart
├── pages/teacher_form_page.dart
└── widgets/status_chip.dart, teacher_list_tile.dart,
teacher_card.dart, assignment_card.dart,
assignment_matrix_header.dart, subject_badge.dart
2. Domain models
enum EmploymentStatus { active, inactive, onLeave, terminated }
class Teacher {
final String id;
final String userId; // identity link (teacher.schema.ts:16-17)
final String employeeNumber; // mono display
final String? departmentId, designationId;
final DateTime? joiningDate;
final EmploymentStatus employmentStatus;
final String? qualification;
final int experienceYears; // default 0 (teacher.schema.ts:41-42)
final List<String> subjects; // ObjectIds (teacher.schema.ts:44-45)
final List<String> classTeacherFor;
final Map<String, dynamic>? metadata;
}
class SubjectAssignment {
final String id, teacherId, subjectId, classId, academicYearId;
}
- DTO→model:
fromJsonwith strict types; enums viaEnumByNamewith unknown →activefallback(proposed)(server enum not validated on input, but stored values are enum-safe,teacher.schema.ts:31-36). - Ref-name resolution:
TeachersService.joinNames(teacher, catalogs)— catalogs (departments/designations/subjects/classes/years) loaded once, cached 24 h (13 §3).
3. Repository
class TeacherRepository {
TeacherRepository(this._dio); // AppDio (00-shared/11 §5)
Future<Page<Teacher>> list({int page = 1, int limit = 20});
Future<Teacher> byId(String id);
Future<Teacher> create(TeacherDraftDto dto); // POST /teachers
Future<Teacher> update(String id, TeacherPatchDto dto);// PATCH /teachers/:id
Future<void> remove(String id); // DELETE /teachers/:id
Future<List<SubjectAssignment>> assignmentsByTeacher(String teacherId, String academicYearId);
Future<SubjectAssignment> createAssignment(Map<String, String> triple); // POST /subject-assignments
Future<void> removeAssignment(String id); // DELETE /subject-assignments/:id
}
- Errors: interceptor maps envelope →
ApiException(code, status, fieldDetails)(00-shared/11 §5); 409 exposesconflictflavor for banner copy. - Pagination:
Page<T>helper (items +meta) shared with other modules.
4. Cubits
TeachersListCubit—PaginatedListMixin<Teacher>(00-shared/06 §3.2); local filter application for status/dept/designation/q (server ignoressort/q,teacher.service.ts:66-78);RefreshTeachersbypasses cache.TeacherDetailCubit— parallel loads: profile + assignments + timetable (three repos) with per-tabLoadState;ChangeYearemits token, drops stale responses.TeacherFormCubit— mirrorsTeacherDraftDto/TeacherPatchDto; on 400 mapsfieldDetails; on 409 setsConflictType;dirtyguards back-navigation.AssignmentEditorCubit— duplicate check against current matrix (13 §2state); submit disabled while pending.
5. Router
// go_router additions (00-shared/05 §4 + 00-shared/11 §6)
GoRoute(path: '/staff/teachers', builder: TeachersListPage.new),
GoRoute(path: '/staff/teachers/new', builder: TeacherFormPage(mode: create)),
GoRoute(path: '/staff/teachers/:id', builder: TeacherDetailPage.new),
GoRoute(path: '/staff/teachers/:id/edit', builder: TeacherFormPage(mode: edit)),
// (planned) teacher self
GoRoute(path: '/my/teaching', builder: MyTeachingPage.new),
- Guards:
permissionGuard('staff.read')client-side mirror ofpermissions.constants.ts:19(server is JWT-only today —01 §5, OQ-1). - Deep link:
studylyon://teachers/:id.
6. Theme & components
- All tokens via
AppTheme(00-shared/04); module components in07; status colors only insideStatusChip(11 §7). employeeNumberrendered withmono+tabularFigures(02 §2).
7. i18n keys
teachers.title, teachers.search.hint, teachers.add,
teachers.status.{active,inactive,on_leave,terminated},
teachers.create.conflict.user, teachers.create.conflict.employeeNumber,
teachers.notFound, teachers.deactivate.title, teachers.deactivate.body,
teachers.deactivate.done, teachers.empty.title, teachers.empty.filtered,
assignments.title, assignments.add, assignments.duplicate,
assignments.remove.confirm, assignments.notFound, assignments.empty.{teacher,year},
schedule.empty, teachers.self.banner.onLeave, errors.server, errors.rateLimited
8. Tests
| Layer | Cases |
|---|---|
| Unit | TeachersListCubit pagination merge + local filters; TeacherFormCubit 400/409 mapping; AssignmentEditorCubit duplicate guard; DTO↔model mappers |
| Widget | S1 3-state (skeleton/error/empty), S5 409 banner, S7 duplicate block, StatusChip golden ×4×2 |
| Integration | create→assign→edit→deactivate journey; 404 detail handling |
| E2E | P0: full teacher lifecycle + cross-tenant 404 (per 00-shared/10 §9) |
9. Known backend gaps to coordinate (from 14-QA)
- Duplicate-assignment guard (QA-1), PATCH employeeNumber 409 (QA-9), deactivate guard (QA-12), server search/sort (QA-18)
-
TeacherCreatedemail intent + in-app notification enum mismatch (QA-25/26) - Search-index title for teacher events (QA-27)
-
GET /teachers/mefor teacher self-view (OQ-2)