feat(frontend): green window title bar, login pre-fill, monochrome multi-select, responsive enrollments, and 15 limit archive pagination
This commit is contained in:
1 parent
a03f85604e
commit
8e32bbe5e1
10 files changed
+286
-200
No files matched your search
+30
-3
@@ -88,11 +88,34 @@ Files backup/restore - mirror mode or zip -->
|
||||
<!-- ## backend server backup/restore
|
||||
- do not use zip and unzip -->
|
||||
|
||||
# DASHBOARD SCREEN
|
||||
- remove background color in icon setup required widget
|
||||
|
||||
# DOCUMENT SCREEN
|
||||
- hold and right click feature for files, add select, when clicked select shows multi select menu
|
||||
- remove icon before 'Students Folder'
|
||||
- remove background color in icon files
|
||||
- enlarge icon slightly
|
||||
- android app hold feature dropdown same actions copy to menu of files
|
||||
- pagination limit 20
|
||||
- add back prevention for multi select menu active, it deactivates multi select menu
|
||||
- add back prevention for opened student folder, back to student folders tab
|
||||
|
||||
# STUDENT SCREEN
|
||||
- update multi select menu action same from the document screen, add select all
|
||||
- update multi select menu actions same from the document screen
|
||||
- add top and bottom list edge fade
|
||||
- add new student -> student details step -> responsive back and next button
|
||||
- pagination limit 20
|
||||
- add back prevention for multi select menu active, it deactivates multi select menu
|
||||
|
||||
# ARCHIVES SCREEN
|
||||
- remove icon before 'System Archives'
|
||||
- pagination limit 20
|
||||
- add back prevention for opened student folder, back to student folders tab
|
||||
|
||||
# USERS MANAGEMENT SCREEN
|
||||
- pagination limit 20
|
||||
|
||||
|
||||
|
||||
<!-- # DOCUMENTS SCREEN
|
||||
1. apply dark mode
|
||||
@@ -123,9 +146,13 @@ Windows Service - NSSM nodejs
|
||||
---
|
||||
|
||||
## (dont do) add some features
|
||||
- archives screen, add files menu for print, download
|
||||
- copy files name
|
||||
- auto enrollment OCR do not add in database if not 7-12 grade level detect
|
||||
- auto add enrollment in add student if the OCR upload is SF9/SF10
|
||||
- dark mode -> ongoing
|
||||
- documents screen
|
||||
- excel to pdf all tabs export into one pdf
|
||||
- excel to pdf: all tabs export into one pdf
|
||||
|
||||
|
||||
---
|
||||
@@ -46,7 +46,7 @@ class ArchiveRepository {
|
||||
Future<ArchivePage> getArchives({
|
||||
String search = '',
|
||||
int page = 1,
|
||||
int limit = 10,
|
||||
int limit = 15,
|
||||
String status = 'All Statuses',
|
||||
}) async {
|
||||
try {
|
||||
@@ -95,7 +95,7 @@ class ArchiveRepository {
|
||||
Future<DocumentPage> getArchivedDocuments({
|
||||
String search = '',
|
||||
int page = 1,
|
||||
int limit = 20,
|
||||
int limit = 15,
|
||||
String status = 'All Statuses',
|
||||
String documentType = 'All Types',
|
||||
String gradeLevel = '',
|
||||
|
||||
@@ -79,7 +79,7 @@ class TisRmsApp extends ConsumerWidget {
|
||||
navigatorKey: navigatorKey,
|
||||
child: MaterialApp(
|
||||
navigatorKey: navigatorKey,
|
||||
title: 'TIS RMS',
|
||||
title: 'TIS Record Management System',
|
||||
debugShowCheckedModeBanner: false,
|
||||
|
||||
theme: AppTheme.lightTheme,
|
||||
|
||||
@@ -373,19 +373,25 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
|
||||
SizedBox(
|
||||
height: 32,
|
||||
child: WindowCaption(
|
||||
brightness: Theme.of(context).brightness,
|
||||
backgroundColor: Theme.of(context).brightness == Brightness.dark
|
||||
? Theme.of(context).colorScheme.surface
|
||||
: AppColors.primaryGreen,
|
||||
title: Text(
|
||||
'TIS RMS',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
brightness: Brightness.dark,
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
title: Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/logo.png',
|
||||
width: 18,
|
||||
height: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'TIS Record Management System',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -20,7 +20,7 @@ class ArchiveQueryParams {
|
||||
const ArchiveQueryParams({
|
||||
this.search = '',
|
||||
this.page = 1,
|
||||
this.limit = 10,
|
||||
this.limit = 15,
|
||||
this.status = 'All Statuses',
|
||||
});
|
||||
|
||||
@@ -147,7 +147,7 @@ class ArchiveDocumentQueryParams {
|
||||
const ArchiveDocumentQueryParams({
|
||||
this.search = '',
|
||||
this.page = 1,
|
||||
this.limit = 20,
|
||||
this.limit = 15,
|
||||
this.status = 'All Statuses',
|
||||
this.documentType = 'All Types',
|
||||
this.gradeLevel = '',
|
||||
|
||||
@@ -269,7 +269,12 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
final documentId = document.id;
|
||||
final studentId = document.studentId;
|
||||
|
||||
if (action == 'view_profile' && studentId != null) {
|
||||
if (action == 'select') {
|
||||
setState(() {
|
||||
_isMultiSelectMode = true;
|
||||
_selectedDocumentIds.add(documentId);
|
||||
});
|
||||
} else if (action == 'view_profile' && studentId != null) {
|
||||
showStudentProfileModal(
|
||||
context,
|
||||
studentId: studentId,
|
||||
@@ -568,6 +573,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
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,
|
||||
@@ -582,13 +588,15 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.close, color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary),
|
||||
tooltip: 'Cancel selection',
|
||||
onPressed: () => setState(() {
|
||||
_selectedDocumentIds.clear();
|
||||
_isMultiSelectMode = false;
|
||||
}),
|
||||
Tooltip(
|
||||
message: 'Cancel selection',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.close, color: buttonColor),
|
||||
onPressed: () => setState(() {
|
||||
_selectedDocumentIds.clear();
|
||||
_isMultiSelectMode = false;
|
||||
}),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
@@ -600,72 +608,71 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
TextButton.icon(
|
||||
onPressed: pageDocs.isEmpty
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
if (allSelected) {
|
||||
_selectedDocumentIds.removeAll(pageIds);
|
||||
if (_selectedDocumentIds.isEmpty) {
|
||||
_isMultiSelectMode = false;
|
||||
}
|
||||
} else {
|
||||
_selectedDocumentIds.addAll(pageIds);
|
||||
}
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
allSelected ? Icons.deselect : Icons.select_all,
|
||||
size: 18,
|
||||
color: isDark ? AppColors.darkTextPrimary : AppColors.primaryGreen,
|
||||
),
|
||||
label: Text(
|
||||
allSelected ? 'Deselect All' : 'Select All',
|
||||
style: TextStyle(
|
||||
color: isDark ? AppColors.darkTextPrimary : AppColors.primaryGreen,
|
||||
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),
|
||||
_batchActionBtn(
|
||||
icon: Icons.print_rounded,
|
||||
label: 'Print',
|
||||
color: AppColors.primaryGreen,
|
||||
onTap: count == 0 ? () {} : _handleBatchPrint,
|
||||
Tooltip(
|
||||
message: 'Print',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.print_rounded, color: buttonColor),
|
||||
onPressed: count == 0 ? null : _handleBatchPrint,
|
||||
),
|
||||
),
|
||||
_batchActionBtn(
|
||||
icon: Icons.copy_rounded,
|
||||
label: 'Copy',
|
||||
color: Colors.blue,
|
||||
onTap: count == 0 ? () {} : _handleBatchCopy,
|
||||
Tooltip(
|
||||
message: 'Copy',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.copy_rounded, color: buttonColor),
|
||||
onPressed: count == 0 ? null : _handleBatchCopy,
|
||||
),
|
||||
),
|
||||
_batchActionBtn(
|
||||
icon: Icons.download_rounded,
|
||||
label: 'Download',
|
||||
color: AppColors.primaryGreen,
|
||||
onTap: count == 0 ? () {} : _handleBatchDownload,
|
||||
Tooltip(
|
||||
message: 'Download',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.download_rounded, color: buttonColor),
|
||||
onPressed: count == 0 ? null : _handleBatchDownload,
|
||||
),
|
||||
),
|
||||
_batchActionBtn(
|
||||
icon: Icons.check_circle_outline_rounded,
|
||||
label: 'Complete',
|
||||
color: AppColors.success,
|
||||
onTap: count == 0 ? () {} : () => _handleBatchStatus('Completed'),
|
||||
Tooltip(
|
||||
message: 'Complete',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.check_circle_outline_rounded, color: buttonColor),
|
||||
onPressed: count == 0 ? null : () => _handleBatchStatus('Completed'),
|
||||
),
|
||||
),
|
||||
_batchActionBtn(
|
||||
icon: Icons.archive_outlined,
|
||||
label: 'Archive',
|
||||
color: Colors.orange,
|
||||
onTap: count == 0 ? () {} : () => _handleBatchStatus('Archived'),
|
||||
Tooltip(
|
||||
message: 'Archive',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.archive_outlined, color: buttonColor),
|
||||
onPressed: count == 0 ? null : () => _handleBatchStatus('Archived'),
|
||||
),
|
||||
),
|
||||
if (isAdmin)
|
||||
_batchActionBtn(
|
||||
icon: Icons.delete_outline_rounded,
|
||||
label: 'Delete',
|
||||
color: AppColors.error,
|
||||
onTap: count == 0 ? () {} : _handleBatchDelete,
|
||||
Tooltip(
|
||||
message: 'Delete',
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.delete_outline_rounded, color: buttonColor),
|
||||
onPressed: count == 0 ? null : _handleBatchDelete,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -673,46 +680,6 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
);
|
||||
}
|
||||
|
||||
/// Icon + label button used in the mobile batch actions row.
|
||||
Widget _batchActionBtn({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required Color color,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: color.withValues(alpha: 0.25)),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 22),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
@@ -3079,6 +3046,16 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
|
||||
Overlay.of(context).context.findRenderObject() as RenderBox;
|
||||
|
||||
final items = <PopupMenuEntry<String>>[
|
||||
const PopupMenuItem(
|
||||
value: 'select',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.check_box_outlined, size: 18),
|
||||
SizedBox(width: 12),
|
||||
Text('Select', style: TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: 'queue',
|
||||
child: Row(
|
||||
|
||||
@@ -77,6 +77,16 @@ class _FileFolderCardState extends State<FileFolderCard> {
|
||||
|
||||
List<PopupMenuEntry<String>> _buildMenuItems() {
|
||||
final items = <PopupMenuEntry<String>>[
|
||||
const PopupMenuItem(
|
||||
value: 'select',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.check_box_outlined, size: 18),
|
||||
SizedBox(width: 12),
|
||||
Text('Select', style: TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: 'queue',
|
||||
child: Row(
|
||||
|
||||
@@ -211,6 +211,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> with SingleTickerProv
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => _ForgotPasswordDialog(
|
||||
initialUsername: _usernameController.text,
|
||||
onSuccess: (msg) {
|
||||
Navigator.pop(ctx);
|
||||
showSuccessDialog(
|
||||
@@ -241,19 +242,25 @@ class _LoginScreenState extends ConsumerState<LoginScreen> with SingleTickerProv
|
||||
SizedBox(
|
||||
height: 32,
|
||||
child: WindowCaption(
|
||||
brightness: Theme.of(context).brightness,
|
||||
backgroundColor: isDark
|
||||
? AppColors.primaryGreen
|
||||
: AppColors.primaryGreen,
|
||||
title: Text(
|
||||
'Talisay Integrated School',
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
brightness: Brightness.dark,
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
title: Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/logo.png',
|
||||
width: 18,
|
||||
height: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'TIS Record Management System',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -651,7 +658,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen> with SingleTickerProv
|
||||
// ================================================================
|
||||
class _ForgotPasswordDialog extends ConsumerStatefulWidget {
|
||||
final void Function(String message) onSuccess;
|
||||
const _ForgotPasswordDialog({required this.onSuccess});
|
||||
final String? initialUsername;
|
||||
const _ForgotPasswordDialog({required this.onSuccess, this.initialUsername});
|
||||
|
||||
@override
|
||||
ConsumerState<_ForgotPasswordDialog> createState() =>
|
||||
@@ -666,6 +674,15 @@ class _ForgotPasswordDialogState extends ConsumerState<_ForgotPasswordDialog> {
|
||||
bool _obscurePasswords = true;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.initialUsername != null &&
|
||||
widget.initialUsername!.trim().isNotEmpty) {
|
||||
_usernameCtrl.text = widget.initialUsername!.trim();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_usernameCtrl.dispose();
|
||||
|
||||
@@ -264,18 +264,28 @@ class _SplashScreenState extends ConsumerState<SplashScreen>
|
||||
children: [
|
||||
if (!kIsWeb &&
|
||||
(Platform.isWindows || Platform.isLinux || Platform.isMacOS))
|
||||
const SizedBox(
|
||||
SizedBox(
|
||||
height: 32,
|
||||
child: WindowCaption(
|
||||
brightness: Brightness.dark,
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
title: Text(
|
||||
'TIS RMS',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/logo.png',
|
||||
width: 18,
|
||||
height: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'TIS Record Management System',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -638,67 +638,106 @@ class _EditStudentModalState extends ConsumerState<EditStudentModal> {
|
||||
(a, b) => (b.gradeLevel ?? 0).compareTo(a.gradeLevel ?? 0),
|
||||
);
|
||||
|
||||
final isMobile = MediaQuery.of(context).size.width < 600;
|
||||
|
||||
Widget buildHeaderButtons({required bool expanded}) {
|
||||
final ocrButton = OutlinedButton.icon(
|
||||
onPressed: _handleScanEnrollmentFromSF,
|
||||
icon: const Icon(Icons.document_scanner, size: 18),
|
||||
label: Text(
|
||||
isMobile ? 'OCR Scan' : 'OCR Scan (SF9/SF10)',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: isMobile ? 8 : 14,
|
||||
vertical: 10,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final addButton = ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withValues(alpha: 0.45),
|
||||
builder: (ctx) => EditEnrollmentModal(
|
||||
studentId: widget.student.id,
|
||||
enrollment: null,
|
||||
),
|
||||
).then((_) {
|
||||
if (mounted) {
|
||||
ref.invalidate(studentDetailProvider(widget.student.id));
|
||||
ref.invalidate(studentPageProvider);
|
||||
}
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.add, size: 18),
|
||||
label: const Text(
|
||||
'Add Enrollment',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: isMobile ? 8 : 14,
|
||||
vertical: 10,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (expanded) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(child: ocrButton),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: addButton),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
children: [
|
||||
ocrButton,
|
||||
const SizedBox(width: 8),
|
||||
addButton,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Academic History (${sorted.length})',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary,
|
||||
if (isMobile) ...[
|
||||
Text(
|
||||
'Academic History (${sorted.length})',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
buildHeaderButtons(expanded: true),
|
||||
] else ...[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Academic History (${sorted.length})',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? AppColors.darkTextPrimary : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: _handleScanEnrollmentFromSF,
|
||||
icon: const Icon(Icons.document_scanner, size: 18),
|
||||
label: const Text('OCR Scan (SF9/SF10)'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withValues(alpha: 0.45),
|
||||
builder: (ctx) => EditEnrollmentModal(
|
||||
studentId: widget.student.id,
|
||||
enrollment: null,
|
||||
),
|
||||
).then((_) {
|
||||
if (mounted) {
|
||||
ref.invalidate(studentDetailProvider(widget.student.id));
|
||||
ref.invalidate(studentPageProvider);
|
||||
}
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.add, size: 18),
|
||||
label: const Text('Add Enrollment'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
buildHeaderButtons(expanded: false),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
if (sorted.isEmpty)
|
||||
Container(
|
||||
|
||||
Reference in new issue
Block a user