fix(students): ensure accurate active-year student loading and navigate to all tab on search

This commit is contained in:
ssbg04 committed 2026-09-29 05:24:01 +08:00
1 parent 4f20a67fda
commit 3e2a52770c
4 files changed
+96 -26

No files matched your search

+8 -2
View File
@@ -921,8 +921,14 @@ Files backup/restore - mirror mode or zip -->
- Tab resets to Enrolled on each navigation away/back to Students screen.
# STUDENT SCREEN
- loads accurate active year students
- when searching navigates to all tab
- [DONE] loads accurate active year students
- Fixed backend studentController.js to use LEFT JOIN sections (prevents dropping enrolled students with unassigned sections).
- Added academicYearsListProvider listener in students_screen.dart so active academic year is reliably applied to the query as soon as provider data resolves.
- Added fallback await on academicYearsListProvider.future in _applyViewTab to avoid empty year range on initial load.
- [DONE] when searching navigates to all tab
- When typing or submitting a search, students_screen automatically switches view tab to "All Students" and clears specific status/year filters.
- Clicking the "Enrolled" tab automatically clears the search field and restores the active year enrolled filter.
- Also cleared view-tab filters when search is triggered from the mobile search dialog in android_bottom_nav_layout.dart.
---
@@ -126,6 +126,14 @@ class _AndroidBottomNavLayoutState extends ConsumerState<AndroidBottomNavLayout>
maxWidth: 600,
onSubmitted: (value) {
Navigator.of(ctx).pop();
if (value.trim().isNotEmpty) {
ref.read(studentQueryProvider.notifier).setFilters(
status: '',
schoolYear: '',
gradeLevel: '',
section: '',
);
}
ref.read(studentQueryProvider.notifier).setSearch(value);
if (navigateToStudents) {
ref.invalidate(studentPageProvider);
@@ -95,6 +95,7 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
final initialQuery = ref.read(studentQueryProvider).search;
if (initialQuery.isNotEmpty) {
_searchController.text = initialQuery;
if (mounted) setState(() => _viewTab = _StudentViewTab.all);
} else {
// Apply the default enrolled-tab filter on first load
_applyViewTab(_StudentViewTab.enrolled);
@@ -135,8 +136,10 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
_searchController.text = currentQuery;
if (mounted) setState(() {});
}
// Re-apply the enrolled filter when returning to this tab
if (mounted) {
if (currentQuery.isNotEmpty) {
if (mounted) setState(() => _viewTab = _StudentViewTab.all);
} else if (mounted) {
// Re-apply the enrolled filter when returning to this tab
_applyViewTab(_viewTab);
}
}
@@ -163,21 +166,41 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
void _onSearchChanged() {
_debounce?.cancel();
final text = _searchController.text;
if (text.trim().isNotEmpty && _viewTab != _StudentViewTab.all) {
if (mounted) setState(() => _viewTab = _StudentViewTab.all);
ref.read(studentQueryProvider.notifier).setFilters(
status: '',
schoolYear: '',
gradeLevel: '',
section: '',
);
}
_debounce = Timer(const Duration(milliseconds: 500), () {
ref.read(studentQueryProvider.notifier).setSearch(_searchController.text);
ref.read(studentQueryProvider.notifier).setSearch(text);
});
}
// ----------------------------------------------------------------
// VIEW TAB: Enrolled (current year) vs All Students
// ----------------------------------------------------------------
void _applyViewTab(_StudentViewTab tab) {
Future<void> _applyViewTab(_StudentViewTab tab) async {
if (!mounted) return;
setState(() => _viewTab = tab);
if (tab == _StudentViewTab.enrolled) {
// Find the currently active academic year
final years = ref.read(academicYearsListProvider).asData?.value ?? [];
// Clear any search when switching back to the enrolled tab
if (_searchController.text.isNotEmpty) {
_searchController.clear();
}
// Ensure we have the loaded list of academic years
List<AcademicYearModel> years = ref.read(academicYearsListProvider).asData?.value ?? [];
if (years.isEmpty) {
try {
years = await ref.read(academicYearsListProvider.future);
} catch (_) {}
}
if (!mounted) return;
final active = years.firstWhere(
(y) => y.status.toLowerCase() == 'active',
orElse: () => years.isNotEmpty ? years.first : AcademicYearModel(id: 0, yearRange: '', status: ''),
@@ -189,6 +212,9 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
gradeLevel: '',
section: '',
);
if (ref.read(studentQueryProvider).search.isNotEmpty) {
ref.read(studentQueryProvider.notifier).setSearch('');
}
} else {
// All students: clear status and school year filters
ref.read(studentQueryProvider.notifier).setFilters(
@@ -202,6 +228,15 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
void _onSearchSubmitted(String query) {
_debounce?.cancel();
if (query.trim().isNotEmpty && _viewTab != _StudentViewTab.all) {
if (mounted) setState(() => _viewTab = _StudentViewTab.all);
ref.read(studentQueryProvider.notifier).setFilters(
status: '',
schoolYear: '',
gradeLevel: '',
section: '',
);
}
ref.read(studentQueryProvider.notifier).setSearch(query);
}
@@ -1004,6 +1039,26 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
}
});
ref.listen<AsyncValue<List<AcademicYearModel>>>(academicYearsListProvider, (prev, next) {
if (!mounted) return;
final years = next.asData?.value;
if (years != null && years.isNotEmpty && _viewTab == _StudentViewTab.enrolled) {
final currentQuery = ref.read(studentQueryProvider);
if (currentQuery.search.isEmpty) {
final active = years.firstWhere(
(y) => y.status.toLowerCase() == 'active',
orElse: () => years.first,
);
if (active.yearRange.isNotEmpty && currentQuery.schoolYear != active.yearRange) {
ref.read(studentQueryProvider.notifier).setFilters(
status: 'Enrolled',
schoolYear: active.yearRange,
);
}
}
}
});
final isAndroid = defaultTargetPlatform == TargetPlatform.android;
return CallbackShortcuts(
@@ -1059,26 +1114,27 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
),
),
const SizedBox(height: AppSizes.p24),
] else if (!isAndroid) ...[
Padding(
padding: const EdgeInsets.only(
left: AppSizes.p24,
right: AppSizes.p24,
top: AppSizes.p24,
] else ...[
if (!isAndroid)
Padding(
padding: const EdgeInsets.only(
left: AppSizes.p24,
right: AppSizes.p24,
top: AppSizes.p24,
),
child: _buildHeaderControls(
context,
query,
ref,
activeCount,
),
),
child: _buildHeaderControls(
context,
query,
ref,
activeCount,
),
),
// ── View Tab Switcher ──
Padding(
padding: const EdgeInsets.only(
left: AppSizes.p24,
right: AppSizes.p24,
top: AppSizes.p12,
padding: EdgeInsets.only(
left: isAndroid ? 16 : AppSizes.p24,
right: isAndroid ? 16 : AppSizes.p24,
top: isAndroid ? 12 : AppSizes.p12,
),
child: _buildViewTabSwitcher(context),
),