feat(reports): enhance compliance table tooltips, yearly comparison filtering, and horizontal scrolling

This commit is contained in:
ssbg04 committed 2026-08-29 22:44:04 +08:00
1 parent ae2cfe1495
commit 3da79ec92f
4 files changed
+412 -142

No files matched your search

+2 -4
View File
@@ -314,12 +314,10 @@ exports.getYearlyComparison = (req, res) => {
)
) sly ON s.id = sly.student_id
GROUP BY ay.id, ay.year_range
ORDER BY ay.id DESC
LIMIT 5
ORDER BY ay.year_range ASC
`;
const data = db.prepare(query).all();
// Return oldest first for charting (left to right)
res.json(data.reverse());
res.json(data);
} catch (error) {
res.status(500).json({ message: 'Failed to fetch yearly comparison', error: error.message });
}
+27 -16
View File
@@ -387,18 +387,22 @@ Files backup/restore - mirror mode or zip -->
<!-- # DOCUMENTS SCREEN
- archive files hide in list -> move archive files into in more icon where the switch grid includes, show the "archives" when the student folder opened or if the student have archived files -> hide the archives files in the documents list, only show completed files -->
<!-- # REPORTS SCREEN
- compliance tab -> "students per year" section -> year filter list all acad year -> deafult selected is 4 consecutive years (ex. 2022-2023, 2023-2024, 2024-2025, 2025-2026)
- horizontal scroll bar hint for android app
- filter by year default as active year
- compliance hover on missing count shows all missing documents separated jhs shs
- compliance tab -> "Student list" section -> remove missing requirements -> make the missing column hover just like on "STUDENT SCREEN", can see missing documents separate jhs and shs same from "STUDENT SCREEN" -->
---
# DONT DO
- recycle bin -> search history not delete history
- reports screen -> compliance -> students per year -> year filter
- reports screen -> horizontal scroll bar hint
- report screen -> dynamic year filter default as active year
- reports screen -> compliance hover on missing count shows all missing documents separated jhs shs
- document screen -> upload document limit document type limit -> duplicate files
- user management -> expired the jwt of deactivated user
- dashboard school -> add missing students by document type kpi
@@ -406,23 +410,30 @@ Files backup/restore - mirror mode or zip -->
- dashboard screen -> user friendly logs/histories
- dashboard screen -> assign section modal
- student screen -> add new student screen -> remove N/A on suffix dropdown
- notification separate admin and teacher
- bug in bulk add student enrollment
# CHINE
- forget password sa admin side (email)
- search history dapat naalis
- lrn should not be fixed
- n/a add student in suffix part -> remove
- inactive academic year
- multiple selection of files in select document type
- how about when the pages needed to upload have two pages or more?
- archive still popping up even after archiving & overall number of docs are inaccurate in docs status
- overall number of docs are inaccurate in docs status
- table header on students directory
- accurate reports
Aug 28 2026:
- missing kpi in dashboard
- grade level by teacher
- report all export
Dashboard
Document breakdown- missing docs
those who didnt submit
What specific year level have complied or partially complied
Documents (no. of completed) over ilan ang docu
Orient ang client as extension proj of our sch
Graph depending sa no. of 4ps
features
- submit missing requirement
- specific yr level dashboard
- number of completed
- how many document
---
# TIS_RMS Server Manager (dont do)
@@ -12,9 +12,30 @@ final selectedAcademicYearIdProvider =
NotifierProvider<SelectedYearNotifier, int?>(() => SelectedYearNotifier());
class SelectedYearNotifier extends Notifier<int?> {
bool _hasExplicitSelection = false;
bool get hasExplicitSelection => _hasExplicitSelection;
@override
int? build() => null;
void select(int? id) => state = id;
int? build() {
_hasExplicitSelection = false;
return null;
}
void select(int? id) {
_hasExplicitSelection = true;
state = id;
}
void setDefaultIfUnset(int activeYearId) {
if (!_hasExplicitSelection && state == null) {
state = activeYearId;
}
}
void reset() {
_hasExplicitSelection = false;
state = null;
}
}
// Academic years list
@@ -87,7 +108,7 @@ final missingDocsFilterExpandedProvider = NotifierProvider<_BoolNotifier, bool>(
// ── Yearly Comparison Filters ─────────────────────────────────────────────────
/// Selected years for yearly comparison (empty = all years)
/// Selected years for yearly comparison (empty = default 4 consecutive years)
final yearlyComparisonSelectedYearsProvider =
NotifierProvider<SelectedYearsNotifier, Set<String>>(
() => SelectedYearsNotifier(),
@@ -107,6 +128,8 @@ class SelectedYearsNotifier extends Notifier<Set<String>> {
state = current;
}
void setYears(Set<String> years) => state = years;
void clear() => state = {};
}
@@ -32,12 +32,13 @@ class ReportsScreen extends ConsumerStatefulWidget {
class _ReportsScreenState extends ConsumerState<ReportsScreen> {
bool _isExporting = false;
final ScrollController _scrollController = ScrollController();
final ScrollController _tableHorizontalScrollController = ScrollController();
final ScrollController _chartHorizontalScrollController = ScrollController();
ProviderSubscription<String>? _tabListener;
Timer? _pollingTimer;
int _currentPage = 0;
int _rowsPerPage = 10;
int _lastTotalRows = -1;
int _selectedViewMode = 0; // 0: DepEd Transparency Board, 1: Compliance & Analytics, 2: Combined
// Table search & sort state
@@ -99,6 +100,8 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
_pollingTimer?.cancel();
_tabListener?.close();
_scrollController.dispose();
_tableHorizontalScrollController.dispose();
_chartHorizontalScrollController.dispose();
_searchController.dispose();
super.dispose();
}
@@ -477,6 +480,25 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
Widget build(BuildContext context) {
final statsAsync = ref.watch(reportStatsProvider);
final storageAsync = ref.watch(storageStatsProvider);
final yearsAsync = ref.watch(academicYearsProvider);
final selectedYearId = ref.watch(selectedAcademicYearIdProvider);
final selectedYearNotifier = ref.read(selectedAcademicYearIdProvider.notifier);
// Auto-select active academic year on initial load if not explicitly chosen
if (!selectedYearNotifier.hasExplicitSelection && selectedYearId == null && yearsAsync.hasValue) {
final yearsList = yearsAsync.value ?? [];
if (yearsList.isNotEmpty) {
final activeYear = yearsList.firstWhere(
(y) => y.status.toLowerCase() == 'active',
orElse: () => yearsList.last,
);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
selectedYearNotifier.setDefaultIfUnset(activeYear.id);
}
});
}
}
return Scaffold(
resizeToAvoidBottomInset: false,
@@ -982,7 +1004,7 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
data: (yearsList) =>
DropdownButtonFormField<int?>(
isExpanded: true,
initialValue: selectedYearId,
value: selectedYearId,
decoration: _filterDecoration(
'School Year',
),
@@ -2038,7 +2060,114 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
);
}
// ── Interactive Compliance Table ──────────────────────────────────────────
// ── Missing Documents Hover Tooltip ──────────────────────────────────────────
Widget _buildMissingDocsTooltip({
required BuildContext context,
required int missingCount,
required String? missingRequirementsStr,
required Widget child,
}) {
final isDark = Theme.of(context).brightness == Brightness.dark;
String tooltipMessage = '';
if (missingCount == 0 ||
missingRequirementsStr == null ||
missingRequirementsStr.trim().isEmpty) {
tooltipMessage = 'All documents completed';
} else {
final rawList = missingRequirementsStr
.split(',')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toList();
final jhsDocs = rawList
.where((d) => d.toUpperCase().startsWith('[JHS]'))
.map((d) => d.replaceFirst(RegExp(r'^\[JHS\]\s*', caseSensitive: false), '').trim())
.toList();
final shsDocs = rawList
.where((d) => d.toUpperCase().startsWith('[SHS]'))
.map((d) => d.replaceFirst(RegExp(r'^\[SHS\]\s*', caseSensitive: false), '').trim())
.toList();
final otherDocs = rawList
.where((d) =>
!d.toUpperCase().startsWith('[JHS]') &&
!d.toUpperCase().startsWith('[SHS]'))
.map((d) => d.trim())
.toList();
final lines = <String>['Missing Documents:'];
if (jhsDocs.isNotEmpty) {
lines.add('JHS:');
lines.addAll(jhsDocs.map((d) => ' • $d'));
}
if (shsDocs.isNotEmpty) {
lines.add('SHS:');
lines.addAll(shsDocs.map((d) => ' • $d'));
}
if (otherDocs.isNotEmpty) {
if (jhsDocs.isNotEmpty || shsDocs.isNotEmpty) lines.add('Other:');
lines.addAll(otherDocs.map((d) => ' • $d'));
}
tooltipMessage = lines.join('\n');
}
return Tooltip(
message: tooltipMessage,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: isDark ? AppColors.darkSurfaceCard : const Color(0xFF2C3437),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isDark ? AppColors.darkBorder : Colors.transparent,
),
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 6,
offset: Offset(0, 2),
),
],
),
textStyle: const TextStyle(
color: Colors.white,
fontSize: 12,
height: 1.4,
),
preferBelow: false,
child: child,
);
}
// ── Horizontal Scroll Hint for Mobile/Android ───────────────────────────────
Widget _buildHorizontalScrollHint(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.swap_horiz_rounded,
size: 15,
color: isDark ? AppColors.darkTextSecondary : AppColors.textSecondary,
),
const SizedBox(width: 4),
Text(
'Scroll horizontally to view more',
style: TextStyle(
fontSize: 11,
fontStyle: FontStyle.italic,
color: isDark ? AppColors.darkTextSecondary : AppColors.textSecondary,
),
),
],
),
);
}
// ── Interactive Compliance Table ──────────────────────────────────────────
Widget _buildComplianceTable(ReportStats data) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final showOnlyMissing = ref.watch(showOnlyMissingDocsProvider);
@@ -2052,94 +2181,71 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
activeStatus != null ||
showOnlyMissing;
var filteredStudents = showOnlyMissing
? data.students.where((s) => s.missingCount > 0).toList()
: List<ReportStudent>.from(data.students);
// Apply search
// Filter students
final filteredStudents = data.students.where((student) {
if (showOnlyMissing && student.missingCount == 0) return false;
if (_searchQuery.isNotEmpty) {
final q = _searchQuery.toLowerCase();
filteredStudents = filteredStudents
.where(
(s) =>
s.fullName.toLowerCase().contains(q) ||
s.lrn.toLowerCase().contains(q),
)
.toList();
final matchLrn = student.lrn.toLowerCase().contains(q);
final matchName = student.fullName.toLowerCase().contains(q);
if (!matchLrn && !matchName) return false;
}
return true;
}).toList();
// Apply sort
// Sort students
if (_sortColumn != null) {
filteredStudents.sort((a, b) {
int cmp;
int comparison = 0;
switch (_sortColumn) {
case 'name':
cmp = a.fullName.compareTo(b.fullName);
break;
case 'lrn':
cmp = a.lrn.compareTo(b.lrn);
comparison = a.lrn.compareTo(b.lrn);
break;
case 'name':
comparison = a.fullName.compareTo(b.fullName);
break;
case 'missing':
cmp = a.missingCount.compareTo(b.missingCount);
comparison = a.missingCount.compareTo(b.missingCount);
break;
default:
cmp = 0;
}
return _sortAscending ? cmp : -cmp;
return _sortAscending ? comparison : -comparison;
});
}
final totalRows = filteredStudents.length;
if (totalRows != _lastTotalRows) {
Future.microtask(() {
if (mounted) {
final totalPages = (totalRows / _rowsPerPage).ceil().clamp(1, 99999);
final startIndex = (_currentPage * _rowsPerPage).clamp(0, totalRows);
final endIndex = (startIndex + _rowsPerPage).clamp(0, totalRows);
final paginatedStudents = totalRows == 0
? <ReportStudent>[]
: filteredStudents.sublist(startIndex, endIndex);
Widget sortableHeader(String label, String columnKey) {
final isSelected = _sortColumn == columnKey;
return InkWell(
onTap: () {
setState(() {
_currentPage = 0;
_lastTotalRows = totalRows;
});
}
});
}
final totalPages = totalRows > 0 ? (totalRows / _rowsPerPage).ceil() : 1;
final startIndex = _currentPage * _rowsPerPage;
final endIndex = (startIndex + _rowsPerPage > totalRows)
? totalRows
: startIndex + _rowsPerPage;
final safeStartIndex = startIndex.clamp(0, totalRows);
final safeEndIndex = endIndex.clamp(0, totalRows);
final paginatedStudents = filteredStudents.sublist(
safeStartIndex,
safeEndIndex,
);
// Sortable column header
Widget sortableHeader(String label, String colKey) {
final isActive = _sortColumn == colKey;
return GestureDetector(
onTap: () => setState(() {
if (_sortColumn == colKey) {
if (_sortColumn == columnKey) {
_sortAscending = !_sortAscending;
} else {
_sortColumn = colKey;
_sortColumn = columnKey;
_sortAscending = true;
_currentPage = 0;
}
}),
});
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(width: 3),
const SizedBox(width: 4),
Icon(
isActive
isSelected
? (_sortAscending
? Icons.arrow_upward_rounded
: Icons.arrow_downward_rounded)
: Icons.unfold_more_rounded,
size: 13,
color: isActive
? Icons.arrow_upward
: Icons.arrow_downward)
: Icons.unfold_more,
size: 14,
color: isSelected
? AppColors.primaryGreen
: (isDark ? AppColors.darkTextMuted : Colors.grey.shade400),
),
@@ -2160,14 +2266,14 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Student List',
'Student Document Compliance',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
@@ -2176,7 +2282,7 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
),
const SizedBox(height: 4),
Text(
'Tap a student to see their full profile. You can sort by LRN, Name, or missing count.',
'List of students with their document compliance status.',
style: TextStyle(
color: isDark ? AppColors.darkTextSecondary : AppColors.textSecondary,
fontSize: 13,
@@ -2185,18 +2291,17 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
],
),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: AppColors.primaryGreen.withValues(alpha: 0.08),
color: AppColors.primaryGreen.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'${data.students.length} students',
'Total: $totalRows',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@@ -2207,7 +2312,7 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
],
),
const SizedBox(height: 12),
// ── Search Field ─────────────────────────────────────────
// ── Search Field ───────────────────────────────────────────
TextField(
controller: _searchController,
onChanged: (val) => setState(() {
@@ -2270,7 +2375,7 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
),
),
),
// ── Clear All Chip ─────────────────────────────────────────
// ── Clear All Chip ─────────────────────────────────────────
if (hasActiveFilters || _searchQuery.isNotEmpty) ...[
const SizedBox(height: 8),
ActionChip(
@@ -2354,12 +2459,6 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
),
),
DataColumn(label: sortableHeader('Missing', 'missing')),
const DataColumn(
label: Text(
'Missing Requirements',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
],
rows: paginatedStudents.isEmpty
? <DataRow>[
@@ -2375,7 +2474,6 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
],
),
]
@@ -2473,7 +2571,11 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
),
),
DataCell(
Container(
_buildMissingDocsTooltip(
context: context,
missingCount: student.missingCount,
missingRequirementsStr: student.missingRequirements,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
@@ -2487,8 +2589,35 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
? const Color(0xFF76BA8A).withValues(alpha: 0.14)
: Colors.green.shade50),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: student.missingCount > 0
? (isDark
? const Color(0xFFD67878).withValues(alpha: 0.3)
: Colors.red.shade200)
: (isDark
? const Color(0xFF76BA8A).withValues(alpha: 0.3)
: Colors.green.shade200),
width: 0.8,
),
child: Text(
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
student.missingCount > 0
? Icons.info_outline_rounded
: Icons.check_circle_outline_rounded,
size: 13,
color: student.missingCount > 0
? (isDark
? const Color(0xFFD67878)
: Colors.red.shade700)
: (isDark
? const Color(0xFF76BA8A)
: Colors.green.shade700),
),
const SizedBox(width: 4),
Text(
student.missingCount.toString(),
style: TextStyle(
color: student.missingCount > 0
@@ -2502,25 +2631,9 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
fontSize: 12,
),
),
],
),
),
DataCell(
Text(
student.missingRequirements ??
'None — Complete',
style: TextStyle(
color: student.missingCount > 0
? (isDark
? AppColors.darkTextSecondary
: Colors.grey.shade700)
: (isDark
? const Color(0xFF76BA8A)
: Colors.green.shade700),
fontSize: 12,
fontStyle: student.missingCount > 0
? FontStyle.normal
: FontStyle.italic,
),
),
),
],
@@ -2530,9 +2643,21 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
if (isWideTable) {
return tableWidget;
}
return SingleChildScrollView(
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHorizontalScrollHint(context),
Scrollbar(
controller: _tableHorizontalScrollController,
thumbVisibility: true,
trackVisibility: true,
child: SingleChildScrollView(
controller: _tableHorizontalScrollController,
scrollDirection: Axis.horizontal,
child: tableWidget,
),
),
],
);
},
),
@@ -2601,7 +2726,9 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
),
const SizedBox(width: 16),
Text(
'Showing ${safeStartIndex + 1} - $safeEndIndex of $totalRows',
totalRows == 0
? 'Showing 0 of 0'
: 'Showing ${startIndex + 1} - $endIndex of $totalRows',
style: TextStyle(
color: isDark ? AppColors.darkTextSecondary : AppColors.textSecondary,
fontSize: 12,
@@ -3260,24 +3387,36 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
const SizedBox(height: 16),
// ── Underline Filter Dropdown Row ─────────────────────────────────
// ── Underline Filter Dropdown Row ──────────────────────────────────────────
yearlyAsync.when(
skipLoadingOnReload: true,
loading: () => const SizedBox.shrink(),
error: (_, e) => const SizedBox.shrink(),
data: (allData) {
// All available year strings from DB, sorted ascending
final allYearStrings = allData.map((d) => d.year).toSet().toList()
// Combine all available academic years from setup and data
final allSysYears = (ref.watch(academicYearsProvider).asData?.value ?? [])
.map((y) => y.yearRange)
.toSet();
final allDataYears = allData.map((d) => d.year).toSet();
final allYearStrings = {...allSysYears, ...allDataYears}.toList()
..sort();
final default4 = allYearStrings.length <= 4
? allYearStrings
: allYearStrings.sublist(allYearStrings.length - 4);
return Row(
children: [
// Year multi-select dropdown
_buildUnderlineDropdown(
label: selectedYears.isEmpty
? 'Last 4 Years'
? (default4.length > 1
? '${default4.first} – ${default4.last}'
: 'Latest 4 Years')
: selectedYears.length == 1
? selectedYears.first
: selectedYears.length == allYearStrings.length
? 'All Years'
: '${selectedYears.length} Years',
icon: Icons.calendar_today_outlined,
onTap: (btnCtx) => _showYearMultiSelectMenu(
@@ -3319,7 +3458,7 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
const SizedBox(height: AppSizes.p24),
// ── Chart ─────────────────────────────────────────────────────────
// ── Chart ──────────────────────────────────────────────────────────
yearlyAsync.when(
skipLoadingOnReload: true,
loading: () => const Center(
@@ -3372,9 +3511,7 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
constraints.maxWidth > (data.length * 150.0)
? constraints.maxWidth
: (data.length * 150.0);
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
final Widget chartWidget = SizedBox(
width: chartWidth,
height: 300,
child: BarChart(
@@ -3501,14 +3638,34 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
}).toList(),
),
),
);
if (chartWidth <= constraints.maxWidth) {
return chartWidget;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHorizontalScrollHint(context),
Scrollbar(
controller: _chartHorizontalScrollController,
thumbVisibility: true,
trackVisibility: true,
child: SingleChildScrollView(
controller: _chartHorizontalScrollController,
scrollDirection: Axis.horizontal,
child: chartWidget,
),
),
],
);
},
);
},
),
const SizedBox(height: 24),
// ── Legend (dynamic based on selected statuses) ───────────────────
// ── Legend (dynamic based on selected statuses) ─────────────────────
Consumer(
builder: (context, ref, _) {
final statuses = ref.watch(
@@ -3644,13 +3801,20 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
final currentSelected = ref.watch(
yearlyComparisonSelectedYearsProvider,
);
return Column(
final default4 = allYears.length <= 4
? allYears.toSet()
: allYears.sublist(allYears.length - 4).toSet();
return SizedBox(
width: 290,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Row(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Select School Years',
@@ -3659,8 +3823,55 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
fontSize: 13,
),
),
const SizedBox(height: 6),
Row(
children: [
TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
ref
.read(
yearlyComparisonSelectedYearsProvider
.notifier,
)
.setYears(default4);
},
child: const Text(
'Latest 4',
style: TextStyle(fontSize: 12),
),
),
const SizedBox(width: 4),
TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
ref
.read(
yearlyComparisonSelectedYearsProvider
.notifier,
)
.setYears(allYears.toSet());
},
child: const Text(
'All Years',
style: TextStyle(fontSize: 12),
),
),
const Spacer(),
TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
ref
.read(
@@ -3670,16 +3881,24 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
.clear();
},
child: const Text(
'Clear All',
'Clear',
style: TextStyle(fontSize: 12),
),
),
],
),
],
),
),
const Divider(height: 1),
...allYears.map((year) {
final isChecked = currentSelected.contains(year);
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 280),
child: ListView(
shrinkWrap: true,
children: allYears.map((year) {
final isChecked = currentSelected.isEmpty
? default4.contains(year)
: currentSelected.contains(year);
return CheckboxListTile(
dense: true,
value: isChecked,
@@ -3687,15 +3906,34 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
activeColor: AppColors.primaryGreen,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (val) {
if (currentSelected.isEmpty) {
final updated = Set<String>.from(default4);
if (updated.contains(year)) {
updated.remove(year);
} else {
updated.add(year);
}
ref
.read(
yearlyComparisonSelectedYearsProvider.notifier,
yearlyComparisonSelectedYearsProvider
.notifier,
)
.setYears(updated);
} else {
ref
.read(
yearlyComparisonSelectedYearsProvider
.notifier,
)
.toggle(year);
}
},
);
}),
}).toList(),
),
),
],
),
);
},
),