From a7f0b6a66afbdb391196a9d75ca5df7050de9461 Mon Sep 17 00:00:00 2001 From: ssbg04 Date: Sun, 16 Aug 2026 02:16:44 +0800 Subject: [PATCH] feat(students): dynamic database filter options with cascading resets and clean dropdowns --- frontend/AGENTS.md | 14 +-- .../ui/screens/students/students_screen.dart | 118 +++++++----------- 2 files changed, 50 insertions(+), 82 deletions(-) diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md index b35ccdc..e78ae7b 100644 --- a/frontend/AGENTS.md +++ b/frontend/AGENTS.md @@ -147,18 +147,8 @@ Files backup/restore - mirror mode or zip --> - SF9 is equal to report card, Form 138, School Form 9, Student Report Card, SF1 for JHS/SF9 for SHS - SF10 is equal to School Form 10, Form 137, Form 137-A, Student Permanent Record --> -# DASHBOARD SCREEN (Teachers side) -- Analytics an KPIs - - make fetch only their students, not all students in the database - - digitalization rate of their students - - document status distribution of their students - - document type breakdown of their students - - top students by documents of their student - - needs attention of their students - -# DOCUMENT SCREEN (Teachers side) -- hide or unauthorize teacher role to print and upload documents -- hide or disable hold and right click, multi select, copy, just hide the show menu and hold/right click functions for teachers role +# STUDENTS SCREEN +- remove the count in school year and grade level in filter modal --- diff --git a/frontend/lib/ui/screens/students/students_screen.dart b/frontend/lib/ui/screens/students/students_screen.dart index 7acab9c..ac58f6e 100644 --- a/frontend/lib/ui/screens/students/students_screen.dart +++ b/frontend/lib/ui/screens/students/students_screen.dart @@ -62,7 +62,6 @@ class _StudentsScreenState extends ConsumerState { 'High-Low Attention', ]; - static const _gradeLevels = ['All Grades', '7', '8', '9', '10', '11', '12']; static const _statusItems = [ 'All Status', 'Enrolled', @@ -1095,6 +1094,7 @@ class _StudentsScreenState extends ConsumerState { onPressed: () => _openFilterDialog( query, ref.read(academicYearsListProvider), + ref.read(gradeLevelsListProvider), ref.read(sectionsListProvider), ), icon: Badge( @@ -1119,6 +1119,7 @@ class _StudentsScreenState extends ConsumerState { void _openFilterDialog( StudentQueryParams query, AsyncValue academicYearsAsync, + AsyncValue gradeLevelsAsync, AsyncValue sectionsAsync, ) { // Sync pending state from current applied query before opening @@ -1158,6 +1159,7 @@ class _StudentsScreenState extends ConsumerState { child: _buildFilterPanelContent( query, academicYearsAsync, + gradeLevelsAsync, sectionsAsync, setDialogState, () => Navigator.of(ctx).pop(), @@ -1200,6 +1202,7 @@ class _StudentsScreenState extends ConsumerState { Widget _buildFilterPanelContent( StudentQueryParams query, AsyncValue academicYearsAsync, + AsyncValue gradeLevelsAsync, AsyncValue sectionsAsync, StateSetter setDialogState, VoidCallback onApply, @@ -1213,16 +1216,41 @@ class _StudentsScreenState extends ConsumerState { ) ?? ['All School Years']; + final dbGradeLevels = + gradeLevelsAsync.whenOrNull( + data: (grades) => + (grades as List).map((g) => g.level.toString()).toList(), + ) ?? + ['7', '8', '9', '10', '11', '12']; + + List availableGrades; + if (_pendingSchoolYear != 'All School Years' && sectionsAsync.value != null) { + final sList = sectionsAsync.value as List; + final gradesInYear = sList + .where((s) => s.academicYearRange == _pendingSchoolYear) + .map((s) => s.gradeLevel.toString()) + .toSet(); + availableGrades = gradesInYear.isNotEmpty + ? dbGradeLevels.where((g) => gradesInYear.contains(g)).toList() + : dbGradeLevels; + } else { + availableGrades = dbGradeLevels; + } + + final gradeLevelItems = ['All Grades', ...availableGrades]; + final sectionItems = sectionsAsync.whenOrNull( data: (sections) { final filteredSections = (sections as List).where((s) { if (_pendingGradeLevel != 'All Grades' && - s.gradeLevel.toString() != _pendingGradeLevel) + s.gradeLevel.toString() != _pendingGradeLevel) { return false; + } if (_pendingSchoolYear != 'All School Years' && - s.academicYearRange != _pendingSchoolYear) + s.academicYearRange != _pendingSchoolYear) { return false; + } return true; }); final raw = [ @@ -1234,33 +1262,6 @@ class _StudentsScreenState extends ConsumerState { ) ?? ['All Sections']; - // Precompute counts for badges - final Map syGradeCount = {}; - if (academicYearsAsync.value != null && sectionsAsync.value != null) { - final sList = sectionsAsync.value as List; - for (final sy in syItems.skip(1)) { - syGradeCount[sy] = sList - .where((s) => s.academicYearRange == sy) - .map((s) => s.gradeLevel) - .toSet() - .length; - } - } - - final Map gradeSectionCount = {}; - if (sectionsAsync.value != null) { - final sList = sectionsAsync.value as List; - for (final grade in _gradeLevels.skip(1)) { - gradeSectionCount[grade] = sList.where((s) { - final matchGrade = s.gradeLevel.toString() == grade; - final matchSy = - _pendingSchoolYear == 'All School Years' || - s.academicYearRange == _pendingSchoolYear; - return matchGrade && matchSy; - }).length; - } - } - final isDark = Theme.of(context).brightness == Brightness.dark; return Container( @@ -1357,6 +1358,7 @@ class _StudentsScreenState extends ConsumerState { label: 'School Year', onReset: () => setDialogState(() { _pendingSchoolYear = 'All School Years'; + _pendingGradeLevel = 'All Grades'; _pendingSection = 'All Sections'; }), child: _buildFilterDropdown( @@ -1364,9 +1366,9 @@ class _StudentsScreenState extends ConsumerState { ? _pendingSchoolYear : 'All School Years', items: syItems, - counts: syGradeCount, onChanged: (v) => setDialogState(() { _pendingSchoolYear = v!; + _pendingGradeLevel = 'All Grades'; _pendingSection = 'All Sections'; }), ), @@ -1382,11 +1384,10 @@ class _StudentsScreenState extends ConsumerState { _pendingSection = 'All Sections'; }), child: _buildFilterDropdown( - value: _gradeLevels.contains(_pendingGradeLevel) + value: gradeLevelItems.contains(_pendingGradeLevel) ? _pendingGradeLevel : 'All Grades', - items: _gradeLevels.toList(), - counts: gradeSectionCount, + items: gradeLevelItems, onChanged: (v) => setDialogState(() { _pendingGradeLevel = v!; _pendingSection = 'All Sections'; @@ -1408,9 +1409,13 @@ class _StudentsScreenState extends ConsumerState { items: sectionItems, hint: _pendingGradeLevel == 'All Grades' ? 'Select Grade Level first' - : null, - enabled: _pendingGradeLevel != 'All Grades', - onChanged: _pendingGradeLevel == 'All Grades' + : (sectionItems.length <= 1 + ? 'No sections available' + : null), + enabled: _pendingGradeLevel != 'All Grades' && + sectionItems.length > 1, + onChanged: (_pendingGradeLevel == 'All Grades' || + sectionItems.length <= 1) ? null : (v) => setDialogState(() => _pendingSection = v!), ), @@ -1658,7 +1663,6 @@ class _StudentsScreenState extends ConsumerState { Widget _buildFilterDropdown({ required String value, required List items, - Map? counts, ValueChanged? onChanged, String? hint, bool enabled = true, @@ -1706,38 +1710,12 @@ class _StudentsScreenState extends ConsumerState { .map( (i) => DropdownMenuItem( value: i, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - i, - style: TextStyle( - fontSize: 14, - color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary, - ), - ), - if (counts != null && counts[i] != null && counts[i]! > 0) - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 2, - ), - decoration: BoxDecoration( - color: AppColors.primaryGreen.withValues( - alpha: 0.1, - ), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - counts[i].toString(), - style: const TextStyle( - fontSize: 11, - color: AppColors.primaryGreen, - fontWeight: FontWeight.bold, - ), - ), - ), - ], + child: Text( + i, + style: TextStyle( + fontSize: 14, + color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary, + ), ), ), )