fix: single OCR modal popup on add student, sync status revert on invalid graduation, clean transparency titles 1-3, and student list rows per page dropdown with clean legend

This commit is contained in:
ssbg04 committed 2026-08-18 20:20:37 +08:00
1 parent e538710a34
commit 6070ffce0d
4 files changed
+134 -88

No files matched your search

@@ -36,7 +36,7 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
Timer? _pollingTimer;
int _currentPage = 0;
final int _rowsPerPage = 10;
int _rowsPerPage = 10;
int _lastTotalRows = -1;
int _selectedViewMode = 0; // 0: DepEd Transparency Board, 1: Compliance & Analytics, 2: Combined
@@ -2506,7 +2506,7 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
},
),
),
if (totalPages > 1) ...[
if (totalRows > 0) ...[
Divider(
height: 1,
color: isDark ? AppColors.darkBorder : null,
@@ -2519,74 +2519,125 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
child: Wrap(
alignment: WrapAlignment.spaceBetween,
crossAxisAlignment: WrapCrossAlignment.center,
runSpacing: 8,
runSpacing: 10,
children: [
Text(
'Showing ${safeStartIndex + 1} - $safeEndIndex / $totalRows',
style: TextStyle(
color: isDark ? AppColors.darkTextSecondary : AppColors.textSecondary,
fontSize: 11,
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.chevron_left, size: 20),
onPressed: _currentPage > 0
? () => setState(() => _currentPage--)
: null,
Text(
'Rows per page:',
style: TextStyle(
color: isDark ? AppColors.darkTextSecondary : AppColors.textSecondary,
fontSize: 12,
),
),
...List.generate(
totalPages,
(i) => i,
).where((p) => (p - _currentPage).abs() <= 2).map((p) {
final isActive = p == _currentPage;
return GestureDetector(
onTap: () => setState(() => _currentPage = p),
child: Container(
width: 32,
height: 32,
margin:
const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: isActive
? AppColors.primaryGreen
: Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: isActive
? null
: Border.all(
color: isDark
? AppColors.darkBorder
: Colors.grey.shade300,
),
),
child: Center(
child: Text(
'${p + 1}',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: isActive
? Colors.white
: (isDark
? AppColors.darkTextSecondary
: AppColors.textSecondary),
),
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: isDark ? AppColors.darkSurface2 : Colors.grey.shade100,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: isDark ? AppColors.darkBorder : Colors.grey.shade300,
),
);
}),
IconButton(
icon: const Icon(Icons.chevron_right, size: 20),
onPressed: _currentPage < totalPages - 1
? () => setState(() => _currentPage++)
: null,
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
value: _rowsPerPage,
isDense: true,
dropdownColor: isDark ? AppColors.darkSurfaceCard : Colors.white,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary,
),
items: const [10, 25, 50, 100].map((count) {
return DropdownMenuItem<int>(
value: count,
child: Text('$count'),
);
}).toList(),
onChanged: (newCount) {
if (newCount != null) {
setState(() {
_rowsPerPage = newCount;
_currentPage = 0;
});
}
},
),
),
),
const SizedBox(width: 16),
Text(
'Showing ${safeStartIndex + 1} - $safeEndIndex of $totalRows',
style: TextStyle(
color: isDark ? AppColors.darkTextSecondary : AppColors.textSecondary,
fontSize: 12,
),
),
],
),
if (totalPages > 1)
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.chevron_left, size: 20),
onPressed: _currentPage > 0
? () => setState(() => _currentPage--)
: null,
),
...List.generate(
totalPages,
(i) => i,
).where((p) => (p - _currentPage).abs() <= 2).map((p) {
final isActive = p == _currentPage;
return GestureDetector(
onTap: () => setState(() => _currentPage = p),
child: Container(
width: 32,
height: 32,
margin:
const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: isActive
? AppColors.primaryGreen
: Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: isActive
? null
: Border.all(
color: isDark
? AppColors.darkBorder
: Colors.grey.shade300,
),
),
child: Center(
child: Text(
'${p + 1}',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: isActive
? Colors.white
: (isDark
? AppColors.darkTextSecondary
: AppColors.textSecondary),
),
),
),
),
);
}),
IconButton(
icon: const Icon(Icons.chevron_right, size: 20),
onPressed: _currentPage < totalPages - 1
? () => setState(() => _currentPage++)
: null,
),
],
),
],
),
),
@@ -3010,8 +3061,8 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
spacing: 16,
runSpacing: 6,
children: [
_buildLegendItem(AppColors.primaryGreen, '≥ 80% Good'),
_buildLegendItem(Colors.orange, '50–79% Moderate'),
_buildLegendItem(AppColors.primaryGreen, '≥ 80% Good'),
_buildLegendItem(Colors.orange, '50–79% Moderate'),
_buildLegendItem(Colors.red, '< 50% Critical'),
],
),
@@ -163,11 +163,11 @@ class _TransparencyBoardContent extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ── Section 1: ACCESS — Enrollment ─────────────────────────
// ── Section 1: ACCESS — Enrollment ──────────────────────────
_buildSectionHeader(
context,
icon: Icons.bar_chart,
label: '1. ACCESS — Enrollment by Sex & Year',
label: '1. ACCESS — Enrollment by Sex & Year',
color: AppColors.primaryGreen,
),
const SizedBox(height: AppSizes.p16),
@@ -182,11 +182,11 @@ class _TransparencyBoardContent extends StatelessWidget {
),
const SizedBox(height: AppSizes.p24),
// ── Section 2: ACCESS — Dropouts & Transferees ─────────────
// ── Section 2: ACCESS — Dropouts & Transferees ───────────────
_buildSectionHeader(
context,
icon: Icons.trending_down,
label: '2. ACCESS — Dropouts & Transferees',
label: '2. ACCESS — Dropouts & Transferees',
color: Colors.redAccent,
),
const SizedBox(height: AppSizes.p16),
@@ -201,11 +201,11 @@ class _TransparencyBoardContent extends StatelessWidget {
),
const SizedBox(height: AppSizes.p24),
// ── Section 3: EQUITY — 4Ps Beneficiaries ──────────────────
// ── Section 3: EQUITY — 4Ps Beneficiaries ────────────────────
_buildSectionHeader(
context,
icon: Icons.family_restroom,
label: '3. EQUITY — 4Ps Beneficiaries',
label: '3. EQUITY — 4Ps Beneficiaries',
color: Colors.deepPurple,
),
const SizedBox(height: AppSizes.p16),
@@ -1,5 +1,4 @@
import 'dart:io';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -276,6 +275,7 @@ class _AddStudentModalState extends ConsumerState<AddStudentModal> {
String fileName,
String fileSize,
) async {
if (_isLoading) return;
setState(() {
_errorMessage = null;
_ocrScannedFile = file;
@@ -1115,21 +1115,9 @@ class _AddStudentModalState extends ConsumerState<AddStudentModal> {
minHeight: 3,
),
Expanded(
child: DropTarget(
onDragDone: (details) async {
if (details.files.isNotEmpty && _currentStep == 0) {
final path = details.files.first.path;
final droppedFile = File(path);
final fileName = path.split(RegExp(r'[\\/]')).last;
final length = await droppedFile.length();
final fileSize = '${(length / 1024).toStringAsFixed(1)} KB';
_handleOcrScan(droppedFile, fileName, fileSize);
}
},
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: stepContent,
),
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: stepContent,
),
),
Builder(
@@ -49,6 +49,7 @@ class _EditStudentModalState extends ConsumerState<EditStudentModal> {
// Cache of all enrollments loaded for the student being edited
List<dynamic>? _loadedEnrollments;
int _statusDropdownKey = 0;
static const _statuses = ['Enrolled', 'Graduated', 'Transferred', 'Dropped', 'Inactive'];
static const _extSuggestions = [
@@ -354,8 +355,8 @@ class _EditStudentModalState extends ConsumerState<EditStudentModal> {
);
final statusDropdown = DropdownButtonFormField<String>(
key: ValueKey('edit_status_dropdown_$_selectedStatus'),
value: _selectedStatus,
key: ValueKey('edit_status_dropdown_${_selectedStatus}_$_statusDropdownKey'),
initialValue: _selectedStatus,
decoration: const InputDecoration(
labelText: 'STATUS',
prefixIcon: Icon(Icons.info_outline),
@@ -372,10 +373,16 @@ class _EditStudentModalState extends ConsumerState<EditStudentModal> {
'Graduation status is only applicable for Grade 10 and Grade 12 students.\n\n'
'This student is currently ${grade != null ? 'in Grade $grade' : 'not enrolled in Grade 10 or 12'}.',
);
// Force dropdown to re-render back to previous valid status
setState(() {
_statusDropdownKey++;
});
return;
}
}
setState(() => _selectedStatus = v);
setState(() {
_selectedStatus = v;
});
},
);