Skip to content

Commit 5015946

Browse files
implemented pupil book lendings filter
1 parent 089809f commit 5015946

9 files changed

Lines changed: 418 additions & 144 deletions

File tree

school_data_hub_flutter/lib/common/domain/filters/filters_state_manager.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:school_data_hub_flutter/features/_attendance/domain/filters/atte
55
import 'package:school_data_hub_flutter/features/_schoolday_events/domain/filters/schoolday_event_filter_manager.dart';
66
import 'package:school_data_hub_flutter/features/authorizations/domain/filters/authorization_filter_manager.dart';
77
import 'package:school_data_hub_flutter/features/authorizations/domain/filters/pupil_authorization_filter_manager.dart';
8+
import 'package:school_data_hub_flutter/features/books/domain/filters/pupil_book_lending_filter_manager.dart';
89
import 'package:school_data_hub_flutter/features/learning_support/domain/filters/learning_support_filter_manager.dart';
910
import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupil_filter_manager.dart';
1011
import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_filter.dart';
@@ -20,6 +21,7 @@ enum FilterState {
2021
matrixUser,
2122
matrixRoom,
2223
user,
24+
pupilBookLending,
2325
}
2426

2527
const Map<FilterState, bool> _initialFilterGlobalValues = {
@@ -32,6 +34,7 @@ const Map<FilterState, bool> _initialFilterGlobalValues = {
3234
FilterState.matrixUser: false,
3335
FilterState.matrixRoom: false,
3436
FilterState.user: false,
37+
FilterState.pupilBookLending: false,
3538
};
3639

3740
abstract class FiltersStateManager {
@@ -114,6 +117,7 @@ class FiltersStateManagerImplementation implements FiltersStateManager {
114117
di<AuthorizationFilterManager>().resetFilters();
115118
di<PupilAuthorizationFilterManager>().resetFilters();
116119
di<LearningSupportFilterManager>().resetFilters();
120+
di<PupilBookLendingFilterManager>().resetFilters();
117121

118122
_filterStates.value = {..._initialFilterGlobalValues};
119123
_filtersActive.value = false;

school_data_hub_flutter/lib/core/init/init_on_user_auth.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:school_data_hub_flutter/features/authorizations/domain/authoriza
1313
import 'package:school_data_hub_flutter/features/authorizations/domain/filters/authorization_filter_manager.dart';
1414
import 'package:school_data_hub_flutter/features/authorizations/domain/filters/pupil_authorization_filter_manager.dart';
1515
import 'package:school_data_hub_flutter/features/books/domain/book_manager.dart';
16+
import 'package:school_data_hub_flutter/features/books/domain/filters/pupil_book_lending_filter_manager.dart';
1617
import 'package:school_data_hub_flutter/features/books/domain/pupil_book_lending_manager.dart';
1718
import 'package:school_data_hub_flutter/features/learning/domain/competence_manager.dart';
1819
import 'package:school_data_hub_flutter/features/learning/domain/filters/competence_filter_manager.dart';
@@ -154,6 +155,11 @@ class InitOnUserAuth {
154155
dependsOn: [PupilProxyManager],
155156
dispose: (m) => m.dispose(),
156157
);
158+
di.registerSingletonWithDependencies<PupilBookLendingFilterManager>(
159+
() => PupilBookLendingFilterManager(),
160+
dependsOn: [PupilBookLendingManager],
161+
dispose: (m) => m.dispose(),
162+
);
157163

158164
di.registerSingleton<FiltersStateManager>(
159165
FiltersStateManagerImplementation(),
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
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+
}

school_data_hub_flutter/lib/features/books/domain/models/enums.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,40 @@ enum ReadingLevel {
3939

4040
const ReadingLevel(this.value);
4141
}
42+
43+
enum BookFilter {
44+
all('Alle'),
45+
available('Verfügbar'),
46+
borrowed('Ausgeliehen');
47+
48+
final String value;
49+
const BookFilter(this.value);
50+
}
51+
52+
Map<BookFilter, bool> initialBookFilterValues = {
53+
BookFilter.all: true,
54+
BookFilter.available: false,
55+
BookFilter.borrowed: false,
56+
};
57+
58+
enum PupilBookLendingFilter {
59+
all,
60+
currentlyBorrowed,
61+
returned,
62+
lastSevenDays,
63+
lastThirtyDays,
64+
highScore,
65+
lowScore,
66+
noScore,
67+
}
68+
69+
Map<PupilBookLendingFilter, bool> initialPupilBookLendingFilterValues = {
70+
PupilBookLendingFilter.all: false,
71+
PupilBookLendingFilter.currentlyBorrowed: false,
72+
PupilBookLendingFilter.returned: false,
73+
PupilBookLendingFilter.lastSevenDays: false,
74+
PupilBookLendingFilter.lastThirtyDays: false,
75+
PupilBookLendingFilter.highScore: false,
76+
PupilBookLendingFilter.lowScore: false,
77+
PupilBookLendingFilter.noScore: false,
78+
};

school_data_hub_flutter/lib/features/learning/domain/competence_manager.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ class CompetenceManager {
3535
final _competenceGoalApiService = CompetenceGoalApiService();
3636
final _competences = ValueNotifier<List<Competence>>([]);
3737
ValueListenable<List<Competence>> get competences => _competences;
38-
38+
ValueListenable<SelectedContent> get selectedLearningContent =>
39+
_selectedLearningContent;
3940
// Learning content selection state
40-
final selectedLearningContent = ValueNotifier<SelectedContent>(
41+
final _selectedLearningContent = ValueNotifier<SelectedContent>(
4142
SelectedContent.books,
4243
);
4344

@@ -53,7 +54,7 @@ class CompetenceManager {
5354
CompetenceManager();
5455
void dispose() {
5556
_competences.dispose();
56-
selectedLearningContent.dispose();
57+
_selectedLearningContent.dispose();
5758

5859
return;
5960
}
@@ -68,6 +69,10 @@ class CompetenceManager {
6869
_competences.value = [];
6970
}
7071

72+
void setSelectedContent(SelectedContent selectedContent) {
73+
_selectedLearningContent.value = selectedContent;
74+
}
75+
7176
//-TODO: Workaround to avoid registration error
7277
//- when inclduing the CompetenceFilterManager because
7378
//- the CompetenceFilterManager is not registered in the di yet

school_data_hub_flutter/lib/features/learning/presentation/pupil_list_learning_page/widgets/learning_list_card/learning_list_card.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,7 @@ class LearningListCard extends WatchingWidget {
3030
// Watch only the specific properties this widget uses directly
3131
final firstName = watchPropertyValue((m) => m.firstName, target: pupil);
3232
final lastName = watchPropertyValue((m) => m.lastName, target: pupil);
33-
final competenceChecks = watchPropertyValue(
34-
(m) => m.competenceChecks,
35-
target: pupil,
36-
);
33+
3734
final pupilBookLendingManager = watch(di<PupilBookLendingManager>());
3835
final pupilBookLendings = pupilBookLendingManager.getPupilBookLendings(
3936
pupil.pupilId,

0 commit comments

Comments
 (0)