|
| 1 | +import 'package:collection/collection.dart'; |
| 2 | +import 'package:flutter/foundation.dart'; |
| 3 | +import 'package:flutter_it/flutter_it.dart'; |
| 4 | +import 'package:school_data_hub_client/school_data_hub_client.dart'; |
| 5 | +import 'package:school_data_hub_flutter/common/domain/filters/filters_state_manager.dart'; |
| 6 | +import 'package:school_data_hub_flutter/features/books/domain/models/enums.dart'; |
| 7 | +import 'package:school_data_hub_flutter/features/books/domain/pupil_book_lending_manager.dart'; |
| 8 | +import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart'; |
| 9 | + |
| 10 | +typedef PupilBookLendingFilterRecord = ({ |
| 11 | + PupilBookLendingFilter filter, |
| 12 | + bool value, |
| 13 | +}); |
| 14 | + |
| 15 | +class PupilBookLendingFilterManager { |
| 16 | + // Lazy getters to avoid circular dependency issues during initialization |
| 17 | + FiltersStateManager get _filtersStateManager => di<FiltersStateManager>(); |
| 18 | + PupilBookLendingManager get _pupilBookLendingManager => |
| 19 | + di<PupilBookLendingManager>(); |
| 20 | + PupilsFilter get _pupilsFilter => di<PupilsFilter>(); |
| 21 | + |
| 22 | + final _pupilBookLendingFilterState = |
| 23 | + ValueNotifier<Map<PupilBookLendingFilter, bool>>( |
| 24 | + initialPupilBookLendingFilterValues, |
| 25 | + ); |
| 26 | + |
| 27 | + ValueListenable<Map<PupilBookLendingFilter, bool>> |
| 28 | + get pupilBookLendingFilterState => _pupilBookLendingFilterState; |
| 29 | + |
| 30 | + final _filteredPupilBookLendings = ValueNotifier<List<PupilBookLending>>([]); |
| 31 | + ValueListenable<List<PupilBookLending>> get filteredPupilBookLendings => |
| 32 | + _filteredPupilBookLendings; |
| 33 | + |
| 34 | + final _pupilIdsWithFilteredPupilBookLendings = ValueNotifier<Set<int>>({}); |
| 35 | + ValueListenable<Set<int>> get pupilIdsWithFilteredPupilBookLendings => |
| 36 | + _pupilIdsWithFilteredPupilBookLendings; |
| 37 | + |
| 38 | + PupilBookLendingFilterManager(); |
| 39 | + |
| 40 | + void dispose() { |
| 41 | + _pupilBookLendingFilterState.dispose(); |
| 42 | + _filteredPupilBookLendings.dispose(); |
| 43 | + _pupilIdsWithFilteredPupilBookLendings.dispose(); |
| 44 | + return; |
| 45 | + } |
| 46 | + |
| 47 | + void resetFilters() { |
| 48 | + _pupilBookLendingFilterState.value = { |
| 49 | + ...initialPupilBookLendingFilterValues, |
| 50 | + }; |
| 51 | + _filtersStateManager.setFilterState( |
| 52 | + filterState: FilterState.pupilBookLending, |
| 53 | + value: false, |
| 54 | + ); |
| 55 | + } |
| 56 | + |
| 57 | + void setFilter({ |
| 58 | + required List<PupilBookLendingFilterRecord> pupilBookLendingFilters, |
| 59 | + }) { |
| 60 | + // Check if "all" filter is being set to true |
| 61 | + final allFilterRecord = pupilBookLendingFilters.firstWhere( |
| 62 | + (record) => record.filter == PupilBookLendingFilter.all, |
| 63 | + orElse: () => (filter: PupilBookLendingFilter.all, value: false), |
| 64 | + ); |
| 65 | + |
| 66 | + if (allFilterRecord.value == true) { |
| 67 | + // If "all" is being activated, reset all filters to initial values |
| 68 | + _pupilBookLendingFilterState.value = { |
| 69 | + ...initialPupilBookLendingFilterValues, |
| 70 | + PupilBookLendingFilter.all: true, |
| 71 | + }; |
| 72 | + } else { |
| 73 | + // Normal filter update |
| 74 | + for (PupilBookLendingFilterRecord record in pupilBookLendingFilters) { |
| 75 | + _pupilBookLendingFilterState.value = { |
| 76 | + ..._pupilBookLendingFilterState.value, |
| 77 | + record.filter: record.value, |
| 78 | + }; |
| 79 | + } |
| 80 | + |
| 81 | + // If any other filter is being activated, turn off "all" |
| 82 | + final anyOtherFilterActive = _pupilBookLendingFilterState.value.entries |
| 83 | + .where((e) => e.key != PupilBookLendingFilter.all) |
| 84 | + .any((e) => e.value == true); |
| 85 | + |
| 86 | + if (anyOtherFilterActive) { |
| 87 | + _pupilBookLendingFilterState.value = { |
| 88 | + ..._pupilBookLendingFilterState.value, |
| 89 | + PupilBookLendingFilter.all: false, |
| 90 | + }; |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + final pupilBookLendingFilterStateEqualsInitialValues = const MapEquality() |
| 95 | + .equals( |
| 96 | + _pupilBookLendingFilterState.value, |
| 97 | + initialPupilBookLendingFilterValues, |
| 98 | + ); |
| 99 | + |
| 100 | + _filtersStateManager.setFilterState( |
| 101 | + filterState: FilterState.pupilBookLending, |
| 102 | + value: !pupilBookLendingFilterStateEqualsInitialValues, |
| 103 | + ); |
| 104 | + |
| 105 | + // Filter the pupil book lendings |
| 106 | + final allLendings = _pupilBookLendingManager.allPupilBookLendings; |
| 107 | + final filtered = _applyFilters(allLendings); |
| 108 | + _filteredPupilBookLendings.value = filtered; |
| 109 | + |
| 110 | + _pupilsFilter.refreshs(); |
| 111 | + } |
| 112 | + |
| 113 | + List<PupilBookLending> _applyFilters( |
| 114 | + List<PupilBookLending> pupilBookLendings, |
| 115 | + ) { |
| 116 | + List<PupilBookLending> filteredLendings = []; |
| 117 | + Set<int> filteredPupilIds = {}; |
| 118 | + |
| 119 | + final activeFilters = _pupilBookLendingFilterState.value; |
| 120 | + |
| 121 | + // If "all" filter is active, return all lendings without filtering |
| 122 | + if (activeFilters[PupilBookLendingFilter.all]!) { |
| 123 | + filteredLendings = pupilBookLendings; |
| 124 | + filteredPupilIds = pupilBookLendings.map((e) => e.pupilId).toSet(); |
| 125 | + // Sort pupil book lendings, latest first |
| 126 | + filteredLendings.sort((a, b) => b.lentAt.compareTo(a.lentAt)); |
| 127 | + _pupilIdsWithFilteredPupilBookLendings.value = filteredPupilIds; |
| 128 | + return filteredLendings; |
| 129 | + } |
| 130 | + |
| 131 | + DateTime sevenDaysAgo = DateTime.now().subtract(const Duration(days: 7)); |
| 132 | + DateTime thirtyDaysAgo = DateTime.now().subtract(const Duration(days: 30)); |
| 133 | + |
| 134 | + bool filterIsActive = false; |
| 135 | + |
| 136 | + for (PupilBookLending lending in pupilBookLendings) { |
| 137 | + bool isMatched = true; |
| 138 | + |
| 139 | + bool complementaryFilter = false; |
| 140 | + |
| 141 | + //- Hard filters - these exclude items completely |
| 142 | + //- we use continue for hard filters |
| 143 | + |
| 144 | + // Filter by last seven days |
| 145 | + if (activeFilters[PupilBookLendingFilter.lastSevenDays]! && |
| 146 | + lending.lentAt.isBefore(sevenDaysAgo)) { |
| 147 | + continue; |
| 148 | + } |
| 149 | + |
| 150 | + // Filter by last thirty days |
| 151 | + if (activeFilters[PupilBookLendingFilter.lastThirtyDays]! && |
| 152 | + lending.lentAt.isBefore(thirtyDaysAgo)) { |
| 153 | + continue; |
| 154 | + } |
| 155 | + |
| 156 | + // Filter by currently borrowed (not returned) |
| 157 | + if (activeFilters[PupilBookLendingFilter.currentlyBorrowed]! && |
| 158 | + lending.returnedAt != null) { |
| 159 | + continue; |
| 160 | + } |
| 161 | + |
| 162 | + // Filter by returned |
| 163 | + if (activeFilters[PupilBookLendingFilter.returned]! && |
| 164 | + lending.returnedAt == null) { |
| 165 | + continue; |
| 166 | + } |
| 167 | + |
| 168 | + //- Complementary filters - these are OR logic |
| 169 | + //- at least one must match if any are active |
| 170 | + |
| 171 | + // High score filter (score >= 3) |
| 172 | + if (activeFilters[PupilBookLendingFilter.highScore]!) { |
| 173 | + if (lending.score >= 3) { |
| 174 | + isMatched = true; |
| 175 | + complementaryFilter = true; |
| 176 | + } else if (!complementaryFilter) { |
| 177 | + isMatched = false; |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + // Low score filter (score > 0 and < 3) |
| 182 | + if (activeFilters[PupilBookLendingFilter.lowScore]!) { |
| 183 | + if (lending.score > 0 && lending.score < 3) { |
| 184 | + isMatched = true; |
| 185 | + complementaryFilter = true; |
| 186 | + } else if (!complementaryFilter) { |
| 187 | + isMatched = false; |
| 188 | + } |
| 189 | + } |
| 190 | + |
| 191 | + // No score filter (score == 0) |
| 192 | + if (activeFilters[PupilBookLendingFilter.noScore]!) { |
| 193 | + if (lending.score == 0) { |
| 194 | + isMatched = true; |
| 195 | + complementaryFilter = true; |
| 196 | + } else if (!complementaryFilter) { |
| 197 | + isMatched = false; |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + if (!isMatched) { |
| 202 | + filterIsActive = true; |
| 203 | + continue; |
| 204 | + } |
| 205 | + |
| 206 | + filteredLendings.add(lending); |
| 207 | + filteredPupilIds.add(lending.pupilId); |
| 208 | + } |
| 209 | + |
| 210 | + if (filterIsActive) { |
| 211 | + _filtersStateManager.setFilterState( |
| 212 | + filterState: FilterState.pupilBookLending, |
| 213 | + value: true, |
| 214 | + ); |
| 215 | + } |
| 216 | + |
| 217 | + // Sort pupil book lendings, latest first |
| 218 | + filteredLendings.sort((a, b) => b.lentAt.compareTo(a.lentAt)); |
| 219 | + _pupilIdsWithFilteredPupilBookLendings.value = filteredPupilIds; |
| 220 | + |
| 221 | + return filteredLendings; |
| 222 | + } |
| 223 | + |
| 224 | + bool filterByCurrentlyBorrowed(PupilBookLending lending) { |
| 225 | + return lending.returnedAt == null; |
| 226 | + } |
| 227 | + |
| 228 | + bool filterByReturned(PupilBookLending lending) { |
| 229 | + return lending.returnedAt != null; |
| 230 | + } |
| 231 | + |
| 232 | + bool filterByLastSevenDays(PupilBookLending lending) { |
| 233 | + DateTime sevenDaysAgo = DateTime.now().subtract(const Duration(days: 7)); |
| 234 | + return lending.lentAt.isAfter(sevenDaysAgo); |
| 235 | + } |
| 236 | + |
| 237 | + bool filterByLastThirtyDays(PupilBookLending lending) { |
| 238 | + DateTime thirtyDaysAgo = DateTime.now().subtract(const Duration(days: 30)); |
| 239 | + return lending.lentAt.isAfter(thirtyDaysAgo); |
| 240 | + } |
| 241 | + |
| 242 | + bool filterByHighScore(PupilBookLending lending) { |
| 243 | + return lending.score >= 3; |
| 244 | + } |
| 245 | + |
| 246 | + bool filterByLowScore(PupilBookLending lending) { |
| 247 | + return lending.score > 0 && lending.score < 3; |
| 248 | + } |
| 249 | + |
| 250 | + bool filterByNoScore(PupilBookLending lending) { |
| 251 | + return lending.score == 0; |
| 252 | + } |
| 253 | +} |
0 commit comments