fix: dashboard notification and refresh bugs, student filter and preview updates

This commit is contained in:
ssbg04 committed 2026-06-28 11:14:38 +08:00
1 parent fb4f62b528
commit 871541885c
7 files changed
+123 -39

No files matched your search

@@ -176,7 +176,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
ref.read(notificationsProvider.notifier).markAsRead(note.id);
Future.delayed(const Duration(milliseconds: 100), () {
if (context.mounted) {
if (note.title.toLowerCase().contains('password')) {
final role = ref.read(authProvider).value?.role;
if (note.title.toLowerCase().contains('password') && role == 'admin') {
ResetRequestsModal.show(context);
} else {
ViewActivityModal.show(
@@ -670,7 +671,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
child: TextButton.icon(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const UserHistoryScreen()),
),
).then((_) => _handleRefresh()),
icon: const Icon(Icons.history, size: 18),
label: const Text('User History'),
style: TextButton.styleFrom(foregroundColor: AppColors.primaryGreen, padding: EdgeInsets.zero),
@@ -679,7 +680,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
TextButton.icon(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const RecentActivitiesScreen()),
),
).then((_) => _handleRefresh()),
icon: const Icon(Icons.open_in_new, size: 18),
label: const Text('View All'),
style: TextButton.styleFrom(foregroundColor: AppColors.primaryGreen, padding: EdgeInsets.zero),
@@ -701,7 +702,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
TextButton.icon(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const UserHistoryScreen()),
),
).then((_) => _handleRefresh()),
icon: const Icon(Icons.history, size: 18),
label: const Text('User History'),
style: TextButton.styleFrom(foregroundColor: AppColors.primaryGreen),
@@ -709,7 +710,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
TextButton.icon(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const RecentActivitiesScreen()),
),
).then((_) => _handleRefresh()),
icon: const Icon(Icons.open_in_new, size: 18),
label: const Text('View All'),
style: TextButton.styleFrom(foregroundColor: AppColors.primaryGreen),
@@ -191,17 +191,12 @@ class _RecentActivitiesScreenState
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
iconTheme: const IconThemeData(color: Colors.white),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => ref.invalidate(recentActivitiesPageProvider),
),
],
),
body: Column(
children: [
// ── Filter Bar ───────────────────────────────────────────────────
Container(
width: double.infinity,
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Wrap(
@@ -110,8 +110,11 @@ class _UserHistoryScreenState extends ConsumerState<UserHistoryScreen> {
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
),
items: ['All Actions', 'CREATED', 'UPDATED', 'DELETED']
.map((v) => DropdownMenuItem(value: v, child: Text(v)))
items: ['All Actions', 'created', 'updated', 'deleted']
.map((v) => DropdownMenuItem(
value: v,
child: Text(v == 'All Actions' ? v : v.toUpperCase()),
))
.toList(),
onChanged: (v) => setDialogState(() => pendingAction = v!),
),
@@ -185,17 +188,12 @@ class _UserHistoryScreenState extends ConsumerState<UserHistoryScreen> {
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
iconTheme: const IconThemeData(color: Colors.white),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => ref.invalidate(userHistoryPageProvider),
),
],
),
body: Column(
children: [
// ── Filter Bar ───────────────────────────────────────────────────
Container(
width: double.infinity,
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Wrap(
@@ -1146,7 +1146,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
if (!isIconOnly) ...[
const SizedBox(width: 6),
Text(
'Multi-Select',
'Select',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
@@ -479,20 +479,23 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
],
if (isDesktop) ...[
const SizedBox(width: 8),
OutlinedButton.icon(
onPressed: () => _openFilterDialog(query, ref.read(academicYearsListProvider), ref.read(sectionsListProvider)),
icon: const Icon(Icons.tune_rounded, size: 20),
label: const Text('Filter'),
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryGreen,
side: const BorderSide(color: AppColors.primaryGreen),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
SizedBox(
height: 42,
child: OutlinedButton.icon(
onPressed: () => _openFilterDialog(query, ref.read(academicYearsListProvider), ref.read(sectionsListProvider)),
icon: const Icon(Icons.tune_rounded, size: 20),
label: const Text('Filter'),
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryGreen,
side: const BorderSide(color: AppColors.primaryGreen),
),
),
),
if (widget.userRole != 'teacher') ...[
const SizedBox(width: 8),
SizedBox(
width: 100,
height: 42,
child: PrimaryButton(
label: 'ADD',
onPressed: () => _openModal(),
@@ -739,12 +742,38 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
final sectionItems = sectionsAsync.whenOrNull(
data: (sections) {
final raw = ['All Sections', ...(sections as List).map((s) => s.name as String)];
final filteredSections = (sections as List).where((s) {
if (_pendingGradeLevel != 'All Grades' && s.gradeLevel.toString() != _pendingGradeLevel) return false;
if (_pendingSchoolYear != 'All School Years' && s.academicYearRange != _pendingSchoolYear) return false;
return true;
});
final raw = ['All Sections', ...filteredSections.map((s) => s.name as String)];
return raw.toSet().toList();
},
) ??
['All Sections'];
// Precompute counts for badges
final Map<String, int> 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<String, int> 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;
}
}
return Container(
margin: const EdgeInsets.only(top: 10),
decoration: BoxDecoration(
@@ -787,11 +816,18 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
// School Year
_buildFilterSection(
label: 'School Year',
onReset: () => setDialogState(() => _pendingSchoolYear = 'All School Years'),
onReset: () => setDialogState(() {
_pendingSchoolYear = 'All School Years';
_pendingSection = 'All Sections';
}),
child: _buildFilterDropdown(
value: syItems.contains(_pendingSchoolYear) ? _pendingSchoolYear : 'All School Years',
items: syItems,
onChanged: (v) => setDialogState(() => _pendingSchoolYear = v!),
counts: syGradeCount,
onChanged: (v) => setDialogState(() {
_pendingSchoolYear = v!;
_pendingSection = 'All Sections';
}),
),
),
@@ -807,6 +843,7 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
child: _buildFilterDropdown(
value: _gradeLevels.contains(_pendingGradeLevel) ? _pendingGradeLevel : 'All Grades',
items: _gradeLevels.toList(),
counts: gradeSectionCount,
onChanged: (v) => setDialogState(() {
_pendingGradeLevel = v!;
_pendingSection = 'All Sections';
@@ -949,6 +986,7 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
Widget _buildFilterDropdown({
required String value,
required List<String> items,
Map<String, int>? counts,
ValueChanged<String?>? onChanged,
String? hint,
bool enabled = true,
@@ -981,7 +1019,28 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
items: items
.map((i) => DropdownMenuItem(
value: i,
child: Text(i, style: const TextStyle(fontSize: 14)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(i, style: const TextStyle(fontSize: 14)),
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,
),
),
),
],
),
))
.toList(),
onChanged: enabled ? onChanged : null,
@@ -1237,10 +1296,9 @@ class _StudentsScreenState extends ConsumerState<StudentsScreen> {
),
// BOTTOM SECTION
Wrap(
spacing: AppSizes.p12,
runSpacing: AppSizes.p12,
crossAxisAlignment: WrapCrossAlignment.center,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.min,
@@ -49,6 +49,7 @@ class _AddEditStudentModalState extends ConsumerState<AddEditStudentModal>
final _formKey = GlobalKey<FormState>();
final _scrollCtrl = ScrollController();
late TabController _tabController;
final TransformationController _previewTransformationController = TransformationController();
late TextEditingController _lrnController;
late TextEditingController _firstNameController;
@@ -97,6 +98,7 @@ class _AddEditStudentModalState extends ConsumerState<AddEditStudentModal>
@override
void dispose() {
_tabController.dispose();
_previewTransformationController.dispose();
_lrnController.dispose();
_firstNameController.dispose();
_middleNameController.dispose();
@@ -506,11 +508,38 @@ class _AddEditStudentModalState extends ConsumerState<AddEditStudentModal>
color: AppColors.textPrimary,
),
),
const Spacer(),
if (!isPdf) ...[
IconButton(
icon: const Icon(Icons.zoom_in, size: 20, color: AppColors.textSecondary),
tooltip: 'Zoom In',
onPressed: () {
_previewTransformationController.value =
_previewTransformationController.value.clone()..scale(1.5);
},
),
IconButton(
icon: const Icon(Icons.zoom_out, size: 20, color: AppColors.textSecondary),
tooltip: 'Zoom Out',
onPressed: () {
_previewTransformationController.value =
_previewTransformationController.value.clone()..scale(0.666);
},
),
IconButton(
icon: const Icon(Icons.center_focus_strong, size: 20, color: AppColors.textSecondary),
tooltip: 'Reset View',
onPressed: () {
_previewTransformationController.value = Matrix4.identity();
},
),
],
],
),
const SizedBox(height: 12),
Expanded(
child: Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(AppSizes.radiusMedium),
@@ -525,8 +554,11 @@ class _AddEditStudentModalState extends ConsumerState<AddEditStudentModal>
canShowScrollStatus: false,
)
: InteractiveViewer(
minScale: 0.5,
maxScale: 4.0,
transformationController: _previewTransformationController,
minScale: 0.1,
maxScale: 10.0,
boundaryMargin: const EdgeInsets.all(double.infinity),
constrained: true,
child: Center(
child: Image.file(file, fit: BoxFit.contain),
),