feat(frontend): complete phase 3 screen decomposition and phase 4 platform optimizations
This commit is contained in:
1 parent
6f2d8865c9
commit
c98fbb6a6e
7 files changed
+317
-194
No files matched your search
+20
-4
@@ -45,10 +45,26 @@ void main() async {
|
||||
);
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
]);
|
||||
final view = WidgetsBinding.instance.platformDispatcher.views.first;
|
||||
final physicalSize = view.physicalSize;
|
||||
final devicePixelRatio = view.devicePixelRatio;
|
||||
final width = physicalSize.width / devicePixelRatio;
|
||||
final height = physicalSize.height / devicePixelRatio;
|
||||
final shortestSide = width < height ? width : height;
|
||||
|
||||
if (shortestSide < 600) {
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
]);
|
||||
} else {
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
DeviceOrientation.landscapeLeft,
|
||||
DeviceOrientation.landscapeRight,
|
||||
]);
|
||||
}
|
||||
|
||||
await NotificationService().initialize();
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@ import '../../../core/utils/download_service.dart';
|
||||
import '../../../core/network/api_constants.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'widgets/recycle_bin_modal.dart';
|
||||
import 'widgets/bulk_operations_bar.dart';
|
||||
import 'widgets/documents_header.dart';
|
||||
import '../../../domain/entities/document_model.dart';
|
||||
import '../../../core/utils/excel_to_pdf_converter.dart';
|
||||
|
||||
class DocumentsScreen extends ConsumerStatefulWidget {
|
||||
final String userRole;
|
||||
@@ -307,8 +308,6 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
if (!mounted) return;
|
||||
showErrorDialog(context, 'Copy Failed', e.toString());
|
||||
}
|
||||
} else if (action == 'convert_pdf') {
|
||||
await _handleConvertToPdf(document);
|
||||
} else if (action == 'preview') {
|
||||
showDocumentPreview(context: context, document: document);
|
||||
} else if (action == 'download') {
|
||||
@@ -334,52 +333,6 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
}
|
||||
}
|
||||
|
||||
bool _isExcelFile(DocumentModel doc) {
|
||||
final name = doc.fileName.toLowerCase();
|
||||
return name.endsWith('.xls') || name.endsWith('.xlsx') || name.endsWith('.csv');
|
||||
}
|
||||
|
||||
Future<void> _handleConvertToPdf(DocumentModel doc) async {
|
||||
if (doc.studentId == null) {
|
||||
showErrorDialog(context, 'Conversion Failed', 'No student folder found for this document.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final bytes =
|
||||
await ref.read(documentRepositoryProvider).downloadDocumentBytes(doc.id);
|
||||
if (!mounted) return;
|
||||
final pdfBytes = await convertExcelBytesToPdf(bytes);
|
||||
if (pdfBytes.length > 10 * 1024 * 1024) {
|
||||
showErrorDialog(
|
||||
context,
|
||||
'Conversion Failed',
|
||||
'Converted PDF exceeds the 10MB upload limit.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final baseName = doc.fileName.replaceFirst(
|
||||
RegExp(r'\.(xls|xlsx|csv)$', caseSensitive: false),
|
||||
'',
|
||||
);
|
||||
await ref.read(documentRepositoryProvider).uploadDocumentBytes(
|
||||
studentId: doc.studentId!,
|
||||
documentType: doc.documentType ?? 'Document',
|
||||
requirementId: doc.requirementId,
|
||||
fileName: '$baseName.pdf',
|
||||
bytes: pdfBytes,
|
||||
);
|
||||
if (!mounted) return;
|
||||
ref.invalidate(documentPageProvider);
|
||||
showSuccessDialog(
|
||||
context,
|
||||
message: 'Converted to PDF and saved in the student folder.',
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
showErrorDialog(context, 'Conversion Failed', e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void _confirmDelete(int id) {
|
||||
showDialog(
|
||||
context: context,
|
||||
@@ -600,117 +553,35 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
}
|
||||
|
||||
Widget _buildInlineMultiSelectHeader() {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final count = _selectedDocumentIds.length;
|
||||
final isAdmin = widget.userRole != 'teacher';
|
||||
|
||||
final pageDocs = ref.watch(documentPageProvider).value?.documents ?? [];
|
||||
final pageIds = pageDocs.map((d) => d.id).toSet();
|
||||
final bool allSelected = pageIds.isNotEmpty && pageIds.every((id) => _selectedDocumentIds.contains(id));
|
||||
final buttonColor = isDark ? Colors.white : Colors.black;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? AppColors.darkSurfaceCard : AppColors.primaryGreen.withValues(alpha: 0.1),
|
||||
border: Border(
|
||||
bottom: BorderSide(color: isDark ? AppColors.darkBorder : AppColors.primaryGreen.withValues(alpha: 0.2)),
|
||||
),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
Tooltip(
|
||||
message: 'Cancel selection',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.close, color: buttonColor),
|
||||
onPressed: () => setState(() {
|
||||
return BulkOperationsBar(
|
||||
selectedCount: _selectedDocumentIds.length,
|
||||
allSelected: allSelected,
|
||||
isAdmin: widget.userRole != 'teacher',
|
||||
onCancel: () => setState(() {
|
||||
_selectedDocumentIds.clear();
|
||||
_isMultiSelectMode = false;
|
||||
}),
|
||||
onToggleSelectAll: pageDocs.isEmpty
|
||||
? () {}
|
||||
: () {
|
||||
setState(() {
|
||||
if (allSelected) {
|
||||
_selectedDocumentIds.clear();
|
||||
_isMultiSelectMode = false;
|
||||
}),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
count == 0 ? 'Select items' : '$count selected',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: allSelected ? 'Unselect All' : 'Select All',
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
allSelected ? Icons.deselect : Icons.select_all,
|
||||
color: buttonColor,
|
||||
),
|
||||
onPressed: pageDocs.isEmpty
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
if (allSelected) {
|
||||
_selectedDocumentIds.clear();
|
||||
} else {
|
||||
_selectedDocumentIds.addAll(pageIds);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Container(width: 1, height: 24, color: isDark ? AppColors.darkBorder : Colors.grey.shade400),
|
||||
const SizedBox(width: 12),
|
||||
Tooltip(
|
||||
message: 'Print',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.print_rounded, color: buttonColor),
|
||||
onPressed: count == 0 ? null : _handleBatchPrint,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Copy',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.copy_rounded, color: buttonColor),
|
||||
onPressed: count == 0 ? null : _handleBatchCopy,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Download',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.download_rounded, color: buttonColor),
|
||||
onPressed: count == 0 ? null : _handleBatchDownload,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Complete',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.check_circle_outline_rounded, color: buttonColor),
|
||||
onPressed: count == 0 ? null : () => _handleBatchStatus('Completed'),
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Archive',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.archive_outlined, color: buttonColor),
|
||||
onPressed: count == 0 ? null : () => _handleBatchStatus('Archived'),
|
||||
),
|
||||
),
|
||||
if (isAdmin)
|
||||
Tooltip(
|
||||
message: 'Delete',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.delete_outline_rounded, color: buttonColor),
|
||||
onPressed: count == 0 ? null : _handleBatchDelete,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
} else {
|
||||
_selectedDocumentIds.addAll(pageIds);
|
||||
}
|
||||
});
|
||||
},
|
||||
onBatchPrint: _handleBatchPrint,
|
||||
onBatchCopy: _handleBatchCopy,
|
||||
onBatchDownload: _handleBatchDownload,
|
||||
onBatchStatus: _handleBatchStatus,
|
||||
onBatchArchive: () => _handleBatchStatus('Archived'),
|
||||
onBatchDelete: _handleBatchDelete,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2870,18 +2741,6 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_isExcelFile(doc as DocumentModel)) ...[
|
||||
const PopupMenuItem(
|
||||
value: 'convert_pdf',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.picture_as_pdf, size: 16),
|
||||
SizedBox(width: 10),
|
||||
Text('Convert to PDF', style: TextStyle(fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
const PopupMenuDivider(),
|
||||
const PopupMenuItem(
|
||||
value: 'view_profile',
|
||||
@@ -3185,22 +3044,6 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
),
|
||||
];
|
||||
|
||||
if (_isExcelFile(doc)) {
|
||||
items.insert(
|
||||
4,
|
||||
const PopupMenuItem(
|
||||
value: 'convert_pdf',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.picture_as_pdf, size: 18),
|
||||
SizedBox(width: 12),
|
||||
Text('Convert to PDF', style: TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.userRole != 'teacher') {
|
||||
items.add(const PopupMenuDivider());
|
||||
items.add(
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/constants/app_colors.dart';
|
||||
import '../../../../core/utils/theme_extension.dart';
|
||||
|
||||
class BulkOperationsBar extends StatelessWidget {
|
||||
final int selectedCount;
|
||||
final bool allSelected;
|
||||
final bool isAdmin;
|
||||
final VoidCallback onCancel;
|
||||
final VoidCallback onToggleSelectAll;
|
||||
final VoidCallback onBatchPrint;
|
||||
final VoidCallback onBatchCopy;
|
||||
final VoidCallback onBatchDownload;
|
||||
final ValueChanged<String> onBatchStatus;
|
||||
final VoidCallback onBatchArchive;
|
||||
final VoidCallback? onBatchDelete;
|
||||
|
||||
const BulkOperationsBar({
|
||||
super.key,
|
||||
required this.selectedCount,
|
||||
required this.allSelected,
|
||||
required this.isAdmin,
|
||||
required this.onCancel,
|
||||
required this.onToggleSelectAll,
|
||||
required this.onBatchPrint,
|
||||
required this.onBatchCopy,
|
||||
required this.onBatchDownload,
|
||||
required this.onBatchStatus,
|
||||
required this.onBatchArchive,
|
||||
this.onBatchDelete,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = context.isDark;
|
||||
final buttonColor = isDark ? Colors.white : Colors.black;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? AppColors.darkSurfaceCard : AppColors.primaryGreen.withValues(alpha: 0.1),
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: isDark ? AppColors.darkBorder : AppColors.primaryGreen.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
Tooltip(
|
||||
message: 'Cancel selection',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.close, color: buttonColor),
|
||||
onPressed: onCancel,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
selectedCount == 0 ? 'Select items' : '$selectedCount selected',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: allSelected ? 'Unselect All' : 'Select All',
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
allSelected ? Icons.deselect : Icons.select_all,
|
||||
color: buttonColor,
|
||||
),
|
||||
onPressed: onToggleSelectAll,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Container(
|
||||
width: 1,
|
||||
height: 24,
|
||||
color: isDark ? AppColors.darkBorder : Colors.grey.shade400,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Tooltip(
|
||||
message: 'Print',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.print_rounded, color: buttonColor),
|
||||
onPressed: selectedCount == 0 ? null : onBatchPrint,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Copy',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.copy_rounded, color: buttonColor),
|
||||
onPressed: selectedCount == 0 ? null : onBatchCopy,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Download',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.download_rounded, color: buttonColor),
|
||||
onPressed: selectedCount == 0 ? null : onBatchDownload,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Complete',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.check_circle_outline_rounded, color: buttonColor),
|
||||
onPressed: selectedCount == 0 ? null : () => onBatchStatus('Completed'),
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Archive',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.archive_outlined, color: buttonColor),
|
||||
onPressed: selectedCount == 0 ? null : onBatchArchive,
|
||||
),
|
||||
),
|
||||
if (isAdmin) ...[
|
||||
Tooltip(
|
||||
message: 'Delete',
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.delete_outline_rounded, color: AppColors.error),
|
||||
onPressed: selectedCount == 0 ? null : onBatchDelete,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/constants/app_colors.dart';
|
||||
import '../../../../core/utils/theme_extension.dart';
|
||||
|
||||
class DocumentsHeader extends StatelessWidget {
|
||||
final String? openedFolderName;
|
||||
final int tabIndex;
|
||||
final bool isStudentFiltered;
|
||||
final bool isMobile;
|
||||
final VoidCallback onBackToFolders;
|
||||
final VoidCallback onClearStudentFilter;
|
||||
final VoidCallback onOpenSearch;
|
||||
final VoidCallback onClearSearch;
|
||||
final bool hasActiveSearch;
|
||||
|
||||
const DocumentsHeader({
|
||||
super.key,
|
||||
required this.openedFolderName,
|
||||
required this.tabIndex,
|
||||
required this.isStudentFiltered,
|
||||
required this.isMobile,
|
||||
required this.onBackToFolders,
|
||||
required this.onClearStudentFilter,
|
||||
required this.onOpenSearch,
|
||||
required this.onClearSearch,
|
||||
required this.hasActiveSearch,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = context.isDark;
|
||||
final isFolderOpened = openedFolderName != null;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: isFolderOpened
|
||||
? Row(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: onBackToFolders,
|
||||
child: Text(
|
||||
'Student Folders',
|
||||
style: TextStyle(
|
||||
fontSize: 21,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isDark ? AppColors.darkTextSecondary : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
' / ',
|
||||
style: TextStyle(
|
||||
fontSize: 21,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isDark ? AppColors.darkTextSecondary : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
openedFolderName!,
|
||||
style: TextStyle(
|
||||
fontSize: isMobile ? 17 : 21,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
tabIndex == 0
|
||||
? 'Student Folders'
|
||||
: tabIndex == 1
|
||||
? (isStudentFiltered ? 'Student Documents' : 'All Documents')
|
||||
: 'Recycle Bin',
|
||||
style: TextStyle(
|
||||
fontSize: isMobile ? 17 : 21,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isStudentFiltered && !isFolderOpened) ...[
|
||||
const SizedBox(width: 12),
|
||||
Flexible(
|
||||
child: TextButton.icon(
|
||||
onPressed: onClearStudentFilter,
|
||||
icon: const Icon(Icons.close, size: 14),
|
||||
label: const Text('All Students', style: TextStyle(fontSize: 12)),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AppColors.primaryGreen,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (!isFolderOpened) ...[
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
hasActiveSearch ? Icons.close : Icons.search,
|
||||
size: 28,
|
||||
color: isDark ? AppColors.darkTextPrimary : Colors.black87,
|
||||
),
|
||||
tooltip: hasActiveSearch ? 'Clear Search' : 'Search Documents',
|
||||
onPressed: hasActiveSearch ? onClearSearch : onOpenSearch,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1287,7 +1287,7 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
|
||||
crossAxisCount: cols,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
mainAxisExtent: 145,
|
||||
mainAxisExtent: 160,
|
||||
),
|
||||
children: [
|
||||
_buildPrimaryKpiCard(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -1131,9 +1132,21 @@ class _AddStudentModalState extends ConsumerState<AddStudentModal> {
|
||||
minHeight: 3,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: stepContent,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
Builder(
|
||||
|
||||
@@ -333,17 +333,19 @@ class _BulkOcrImportDialogState extends ConsumerState<BulkOcrImportDialog> {
|
||||
item.trackStrand ??= _sharedTrackStrand;
|
||||
}
|
||||
|
||||
// Validate enrollment completeness
|
||||
// Validate enrollment completeness & grade level bounds (7-12)
|
||||
final missingEnr = _items
|
||||
.where((i) =>
|
||||
i.status == _FileStatus.done &&
|
||||
(i.academicYearId == null ||
|
||||
i.gradeLevel == null ||
|
||||
i.gradeLevel! < 7 ||
|
||||
i.gradeLevel! > 12 ||
|
||||
i.sectionId == null))
|
||||
.toList();
|
||||
if (missingEnr.isNotEmpty && mounted) {
|
||||
showErrorDialog(context, 'Missing Enrollment',
|
||||
'Please select Academic Year, Grade Level, and Section before importing.');
|
||||
showErrorDialog(context, 'Invalid Enrollment',
|
||||
'Please select a valid Academic Year, Grade Level (Grades 7–12), and Section before importing.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user