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:
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;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user