diff --git a/apps/student_app/lib/data/repositories/curriculum_repository.dart b/apps/student_app/lib/data/repositories/curriculum_repository.dart index 199f2b7..4457b43 100644 --- a/apps/student_app/lib/data/repositories/curriculum_repository.dart +++ b/apps/student_app/lib/data/repositories/curriculum_repository.dart @@ -31,9 +31,23 @@ class GradeLevelModel { GradeLevelModel(key: 'tawjihi_2008', name: 'الثاني ثانوي — توجيهي 2008', number: 12), ]; + static String normalizeGradeKey(String raw) { + final clean = raw.toLowerCase().trim(); + if (clean.contains('عاشر') || clean.contains('grade_10') || clean.contains('grade 10') || clean.contains('grade10') || clean == '10') return 'grade_10'; + if (clean.contains('تاسع') || clean.contains('grade_9') || clean.contains('grade 9') || clean.contains('grade9') || clean == '9') return 'grade_9'; + if (clean.contains('ثامن') || clean.contains('grade_8') || clean.contains('grade 8') || clean.contains('grade8') || clean == '8') return 'grade_8'; + if (clean.contains('سابع') || clean.contains('grade_7') || clean.contains('grade 7') || clean.contains('grade7') || clean == '7') return 'grade_7'; + if (clean.contains('سادس') || clean.contains('grade_6') || clean.contains('grade 6') || clean.contains('grade6') || clean == '6') return 'grade_6'; + if (clean.contains('خامس') || clean.contains('grade_5') || clean.contains('grade 5') || clean.contains('grade5') || clean == '5') return 'grade_5'; + if (clean.contains('حادي') || clean.contains('أول ثانوي') || clean.contains('grade_11') || clean.contains('grade 11') || clean == '11') return 'grade_11'; + if (clean.contains('ثاني عشر') || clean.contains('توجيهي') || clean.contains('tawjihi') || clean.contains('grade_12') || clean.contains('grade 12') || clean == '12') return 'tawjihi_2008'; + return raw; + } + static String getGradeName(String key) { + final norm = normalizeGradeKey(key); final match = allK12Grades.firstWhere( - (g) => g.key == key || g.key == key.toLowerCase(), + (g) => g.key == norm || g.key == key || g.key == key.toLowerCase(), orElse: () => GradeLevelModel(key: key, name: key, number: 0), ); return match.name; @@ -62,25 +76,20 @@ class CurriculumRepository { final List available = []; for (var grade in GradeLevelModel.allK12Grades) { - if (tree.containsKey(grade.key) || tree.containsKey(grade.key.replaceAll('grade_', ''))) { + final norm = GradeLevelModel.normalizeGradeKey(grade.key); + final shortKey = norm.replaceAll('grade_', ''); + if (tree.containsKey(grade.key) || tree.containsKey(norm) || tree.containsKey(shortKey)) { available.add(grade); } } - // If server has custom keys, include them as well - tree.forEach((key, val) { - if (!available.any((g) => g.key == key)) { - final name = (val is Map && val['name'] != null) ? val['name'].toString() : GradeLevelModel.getGradeName(key); - available.add(GradeLevelModel(key: key, name: name, number: 0)); - } - }); - - return available; + return available.isNotEmpty ? available : GradeLevelModel.allK12Grades; } /// Fetch subjects for a specific grade level dynamically from Live Backend Future> getSubjects({String gradeLevel = 'grade_10', String stream = 'scientific'}) async { - AppLogger.log('Fetching live curriculum tree from /api/curriculum/tree (Grade: $gradeLevel)...', tag: 'CURRICULUM_REPO'); + final normGrade = GradeLevelModel.normalizeGradeKey(gradeLevel); + AppLogger.log('Fetching live curriculum tree from /api/curriculum/tree (Grade: $gradeLevel -> $normGrade)...', tag: 'CURRICULUM_REPO'); final tree = await getRawCurriculumTree(); if (tree.isEmpty) { @@ -90,19 +99,37 @@ class CurriculumRepository { final List liveSubjects = []; - // Match exact grade key, or fallback variants (e.g. grade_10 vs 10 vs tawjihi_2008) + // Match canonical key, exact key, or fuzzy iterate tree Map? gradeData; - if (tree.containsKey(gradeLevel) && tree[gradeLevel] is Map) { + if (tree.containsKey(normGrade) && tree[normGrade] is Map) { + gradeData = Map.from(tree[normGrade]); + } else if (tree.containsKey(gradeLevel) && tree[gradeLevel] is Map) { gradeData = Map.from(tree[gradeLevel]); } else { - final shortKey = gradeLevel.replaceAll('grade_', ''); + final shortKey = normGrade.replaceAll('grade_', ''); if (tree.containsKey(shortKey) && tree[shortKey] is Map) { gradeData = Map.from(tree[shortKey]); + } else { + for (var entry in tree.entries) { + if (GradeLevelModel.normalizeGradeKey(entry.key) == normGrade) { + if (entry.value is Map) { + gradeData = Map.from(entry.value); + break; + } + } + if (entry.value is Map && entry.value['name'] != null) { + final name = entry.value['name'].toString(); + if (GradeLevelModel.normalizeGradeKey(name) == normGrade || name.contains(normGrade)) { + gradeData = Map.from(entry.value); + break; + } + } + } } } if (gradeData == null || gradeData['subjects'] == null || gradeData['subjects'] is! Map) { - AppLogger.log('No subjects found in grade $gradeLevel on server.', tag: 'CURRICULUM_REPO'); + AppLogger.log('No subjects found in grade $gradeLevel ($normGrade) on server.', tag: 'CURRICULUM_REPO'); return []; } diff --git a/apps/student_app/lib/logic/cubits/curriculum_cubit.dart b/apps/student_app/lib/logic/cubits/curriculum_cubit.dart index 6891df6..028efe4 100644 --- a/apps/student_app/lib/logic/cubits/curriculum_cubit.dart +++ b/apps/student_app/lib/logic/cubits/curriculum_cubit.dart @@ -61,16 +61,17 @@ class CurriculumCubit extends Cubit { super(CurriculumInitial()); Future fetchSubjects({String gradeLevel = 'grade_10', String stream = 'scientific'}) async { - AppLogger.log('CurriculumCubit: Loading subjects for Grade: $gradeLevel ($stream)...', tag: 'CURRICULUM_CUBIT'); + final normGrade = GradeLevelModel.normalizeGradeKey(gradeLevel); + AppLogger.log('CurriculumCubit: Loading subjects for Grade: $gradeLevel ($normGrade - $stream)...', tag: 'CURRICULUM_CUBIT'); emit(CurriculumLoading()); try { final grades = await _repo.getAvailableGrades(); - final subjects = await _repo.getSubjects(gradeLevel: gradeLevel, stream: stream); + final subjects = await _repo.getSubjects(gradeLevel: normGrade, stream: stream); emit(CurriculumLoaded( subjects: subjects, filteredSubjects: subjects, availableGrades: grades, - selectedGrade: gradeLevel, + selectedGrade: normGrade, selectedStream: stream, )); } catch (e) { diff --git a/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart b/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart index 2d5db23..62124d9 100644 --- a/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart +++ b/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart @@ -23,7 +23,7 @@ class _SubjectsGridScreenState extends State { @override void initState() { super.initState(); - final grade = widget.initialGrade ?? 'grade_10'; + final grade = GradeLevelModel.normalizeGradeKey(widget.initialGrade ?? 'grade_10'); context.read().fetchSubjects(gradeLevel: grade); } diff --git a/backend/app/Controllers/AuthController.php b/backend/app/Controllers/AuthController.php index dec3861..70dcb7c 100644 --- a/backend/app/Controllers/AuthController.php +++ b/backend/app/Controllers/AuthController.php @@ -514,9 +514,11 @@ class AuthController [$userId] ); if ($user) { - if (($user['grade_level'] ?? '') === 'tawjihi_2008') { - Database::query("UPDATE students SET grade_level = 'grade_10', updated_at = NOW() WHERE id = ?", [$userId]); - $user['grade_level'] = 'grade_10'; + $rawGrade = (string)($user['grade_level'] ?? 'grade_10'); + $normGrade = \App\Services\StudentAccessControlService::normalizeGrade($rawGrade); + if ($rawGrade === 'tawjihi_2008' || $rawGrade !== $normGrade) { + Database::query("UPDATE students SET grade_level = ?, updated_at = NOW() WHERE id = ?", [$normGrade, $userId]); + $user['grade_level'] = $normGrade; } $isCompleted = !empty($user['full_name']) && $user['full_name'] !== 'طالب جديد' && $user['full_name'] !== 'الطالب المتميز' && !empty($user['grade_level']); $userData = [ @@ -669,7 +671,8 @@ class AuthController $identityToken = $body['identity_token'] ?? ''; $fullName = trim((string)($body['full_name'] ?? '')); - $gradeLevel = trim((string)($body['grade_level'] ?? 'grade_10')); + $rawGrade = trim((string)($body['grade_level'] ?? 'grade_10')); + $gradeLevel = \App\Services\StudentAccessControlService::normalizeGrade($rawGrade); $stream = trim((string)($body['stream'] ?? 'scientific')); $nationalId = trim((string)($body['national_id'] ?? '')); $deviceFingerprint = trim((string)($request->getHeader('x-device-fingerprint', '')));