From 6070ffce0d64e753bb79dfb6643745fdcaf37e27 Mon Sep 17 00:00:00 2001 From: ssbg04 Date: Tue, 18 Aug 2026 20:20:37 +0800 Subject: [PATCH] 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 --- .../ui/screens/reports/reports_screen.dart | 177 +++++++++++------- .../widgets/transparency_board_section.dart | 12 +- .../students/widgets/add_student_modal.dart | 20 +- .../students/widgets/edit_student_modal.dart | 13 +- 4 files changed, 134 insertions(+), 88 deletions(-) diff --git a/frontend/lib/ui/screens/reports/reports_screen.dart b/frontend/lib/ui/screens/reports/reports_screen.dart index f29d964..af91ef6 100644 --- a/frontend/lib/ui/screens/reports/reports_screen.dart +++ b/frontend/lib/ui/screens/reports/reports_screen.dart @@ -36,7 +36,7 @@ class _ReportsScreenState extends ConsumerState { 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 { }, ), ), - if (totalPages > 1) ...[ + if (totalRows > 0) ...[ Divider( height: 1, color: isDark ? AppColors.darkBorder : null, @@ -2519,74 +2519,125 @@ class _ReportsScreenState extends ConsumerState { 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( + 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( + 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 { 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'), ], ), diff --git a/frontend/lib/ui/screens/reports/widgets/transparency_board_section.dart b/frontend/lib/ui/screens/reports/widgets/transparency_board_section.dart index 22e3802..3fbee1e 100644 --- a/frontend/lib/ui/screens/reports/widgets/transparency_board_section.dart +++ b/frontend/lib/ui/screens/reports/widgets/transparency_board_section.dart @@ -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), diff --git a/frontend/lib/ui/screens/students/widgets/add_student_modal.dart b/frontend/lib/ui/screens/students/widgets/add_student_modal.dart index fa90d00..ee67a72 100644 --- a/frontend/lib/ui/screens/students/widgets/add_student_modal.dart +++ b/frontend/lib/ui/screens/students/widgets/add_student_modal.dart @@ -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 { String fileName, String fileSize, ) async { + if (_isLoading) return; setState(() { _errorMessage = null; _ocrScannedFile = file; @@ -1115,21 +1115,9 @@ class _AddStudentModalState extends ConsumerState { 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( diff --git a/frontend/lib/ui/screens/students/widgets/edit_student_modal.dart b/frontend/lib/ui/screens/students/widgets/edit_student_modal.dart index 6127bf5..af9a1a0 100644 --- a/frontend/lib/ui/screens/students/widgets/edit_student_modal.dart +++ b/frontend/lib/ui/screens/students/widgets/edit_student_modal.dart @@ -49,6 +49,7 @@ class _EditStudentModalState extends ConsumerState { // Cache of all enrollments loaded for the student being edited List? _loadedEnrollments; + int _statusDropdownKey = 0; static const _statuses = ['Enrolled', 'Graduated', 'Transferred', 'Dropped', 'Inactive']; static const _extSuggestions = [ @@ -354,8 +355,8 @@ class _EditStudentModalState extends ConsumerState { ); final statusDropdown = DropdownButtonFormField( - 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 { '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; + }); }, );