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

15 — Flutter Implementation Guide (Leave Module)

Concrete build guide for the Flutter client against the leave API surface. App architecture, DI, and conventions per 00-shared/11 (Bloc/Cubit recommendation). All wire contracts here are literal to the NestJS module.


1. Folder layout

lib/features/leave/
├── models/
│   ├── leave_request.dart          # LeaveRequest (schema leave-request.schema.ts:16-48)
│   ├── leave_balance_entry.dart    # LeaveBalanceEntry (leave.service.ts:66-74)
│   ├── leave_type.dart             # LeaveType (leave-type.schema.ts:9-25)
│   └── substitution.dart           # Substitution (substitution.schema.ts:14-47)
├── repositories/
│   ├── leave_repository.dart       # all 9 endpoints (12 §1)
│   └── leave_repository_impl.dart
├── cubits/
│   ├── leave_balance_cubit.dart
│   ├── leave_types_cubit.dart
│   ├── leave_requests_cubit.dart
│   ├── leave_request_form_cubit.dart
│   ├── approvals_cubit.dart
│   ├── substitutions_cubit.dart
│   ├── substitution_form_cubit.dart
│   ├── leave_calendar_cubit.dart
│   └── leave_types_admin_cubit.dart
├── widgets/
│   ├── status_chip.dart            # 07 §1
│   ├── balance_card.dart           # 07 §2
│   ├── request_tile.dart           # 07 §3
│   ├── approval_tile.dart
│   ├── substitution_tile.dart      # 07 §4
│   ├── approval_action_sheet.dart  # 07 §5
│   ├── leave_type_card.dart        # 07 §6
│   └── calendar_month_grid.dart    # 07 §7
├── screens/
│   ├── leave_balance_screen.dart
│   ├── leave_request_form_screen.dart
│   ├── my_requests_screen.dart
│   ├── approvals_queue_screen.dart
│   ├── substitutions_screen.dart
│   ├── leave_types_screen.dart
│   └── leave_calendar_screen.dart
└── leave_di.dart                   # get_it registrations (00-shared/11)

2. Models (wire-literal)

enum LeaveRequestStatus { pending, approved, rejected, cancelled } // leave-request.schema.ts:7-12
enum SubstitutionStatus { assigned, completed, cancelled }         // substitution.schema.ts:7-11

class LeaveRequest {
  final String id, userId, leaveTypeId;
  final DateTime startDate, endDate;
  final int daysRequested;
  final String? reason;
  final LeaveRequestStatus status;
  final String? decidedBy; final DateTime? decidedAt; final String? decisionNote;
  // fromJson: parse dates as UTC (server sends ISO; render in user tz,
  // user.schema.ts:52-53)
}
  • Never serialize userId/daysRequested in create payload — server owns them (leave.service.ts:136-138).
  • LeaveBalanceEntry fields: leaveTypeId, code, name, daysPerYear, carriedForward, daysUsed, daysRemaining — use tabular figures for the headline (11 §2).

3. Repository

class LeaveRepository {
  Future<LeaveRequest> createRequest(CreateLeaveRequestDto dto);          // POST /leave/requests
  Future<List<LeaveRequest>> listRequests({LeaveRequestStatus? status, String? userId});
  Future<LeaveRequest> decide(String id, {required String action, String? note}); // PATCH .../approve
  Future<List<LeaveBalanceEntry>> getBalance(String userId);              // GET /leave/balance/:userId
  Future<List<LeaveType>> listTypes();                                    // GET /leave/types
  Future<LeaveType> createType(CreateLeaveTypeDto dto);                   // POST /leave/types
  Future<Substitution> assignSubstitution(AssignSubstitutionDto dto);     // POST /leave/substitutions
  Future<List<Substitution>> listSubstitutionsForTeacher(String teacherId);
  Future<List<LeaveRequest>> calendar({String? from, String? to});        // GET /leave/calendar
}

Error mapping: parse envelope (00-shared/07); throw typed LeaveApiException(statusCode, message); surface 400/404/409 messages verbatim (they are user-actionable, 12 §4); map Mongo 11000 to "Code already exists".

4. Cubits (patterns per 13)

class LeaveRequestFormCubit extends Cubit<FormState> {
  // draft: leaveTypeId, startDate, endDate, reason
  int get previewDays => dateDifferenceInclusive(start, end); // mirror leave.service.ts:310-312
  Future<void> submit() async {
    emit(submitting);
    try { final r = await repo.createRequest(draft); emit(submitted(r)); }
    on LeaveApiException catch (e) { emit(failure(e)); }       // 404 → reload types
  }
}
  • LeaveBalanceCubit.load() on screen open — never reuse session cache for balance (live-computed, leave.service.ts:87-88).
  • ApprovalsCubit.decide(id, action, note) → on 409 "already decided" refetch queue; on 409 "insufficient balance" emit per-row error (10 §4).
  • LeaveCalendarCubit.loadMonth(month)from = yyyy-MM-01T00:00:00.000Z, to = last day of month (server default semantics, :283-287).

5. Screens

ScreenCubitNotes
BalanceLeaveBalanceCubit + LeaveTypesCubitskeleton per default type count (5, leave.service.ts:30-64); caption "weekends count"
Request formLeaveRequestFormCubitdropdown types; date pickers chained (end ≥ start); live days preview; block offline (10 §8)
My RequestsLeaveRequestsCubitfilter chips → server ?status=; pull-to-refresh; detail bottom sheet
ApprovalsApprovalsCubitdefault pending; hide own rows (self-decision 409, :179-180); decision sheet = ApprovalActionSheet
SubstitutionsSubstitutionsCubitneeds own teacherId from teachers module lookup
Types adminLeaveTypesAdminCubitcreate sheet; E11000 inline
CalendarLeaveCalendarCubitmonth grid; day tap → approved-request sheet

6. Offline & caching

  • Hive/drift cache: requests list, substitutions, last calendar month, types (TTL: session). Balance: snapshot only, flagged stale.
  • Writes blocked offline (mutations need server truth: balance check on decide :184-190, clash check :241-250).

7. Tests

  • Unit: model fromJson; day-count function (incl. weekend + DST cases, 14 §3); cubit error mapping (404/409/11000).
  • Widget: StatusChip renders all 4 statuses; BalanceCard progress clamp; form preview days.
  • Integration: repository against mock HTTP with envelope fixtures (00-shared/07); contract fixtures generated from 12 §3 shapes.
  • Golden: chips, cards, empty states (tokens per 00-shared/02).

8. Rollout order

  1. Balance + My Requests (read surfaces, high frequency).
  2. Request form (write path with server validation).
  3. Approvals queue + decision (admin path, 409 handling).
  4. Substitutions (needs teachers lookup).
  5. Calendar; notifications (planned) last (event-driven, IMPLEMENTATION_PLAN.md:163).