@@ -13,7 +13,7 @@ import OptimiserHeader from './OptimiserHeader';
13
13
import OptimiserForm from './OptimiserForm' ;
14
14
import OptimiserButton from './OptimiserButton' ;
15
15
import OptimiserResults from './OptimiserResults' ;
16
- import { LessonOption , LessonDaysData , FreeDayConflict } from './types' ;
16
+ import { LessonOption , FreeDayConflict } from './types' ;
17
17
18
18
const OptimiserContent : React . FC = ( ) => {
19
19
const activeSemester = useSelector ( ( { app } : State ) => app . activeSemester ) ;
@@ -39,6 +39,7 @@ const OptimiserContent: React.FC = () => {
39
39
const [ error , setError ] = useState < Error | null > ( null ) ;
40
40
41
41
// Generate lesson options from current timetable
42
+ // find the lesson and the type in lessonOptions, and then find the days of that combination in lessonDaysData
42
43
const lessonOptions = useMemo ( ( ) => {
43
44
const options : LessonOption [ ] = [ ] ;
44
45
@@ -69,71 +70,86 @@ const OptimiserContent: React.FC = () => {
69
70
return options ;
70
71
} , [ timetable , modules , activeSemester , colors ] ) ;
71
72
72
- const lessonDaysData = useMemo ( ( ) => {
73
- const lessonDays : LessonDaysData [ ] = [ ] ;
74
-
73
+ // group by classNo
74
+ const lessonGroupsData = useMemo ( ( ) => {
75
+ const moduleGroupMap = new Map < string , Map < string , string [ ] > > ( ) ;
76
+ // each item has moduleCode-lessonType, combination, so get all the groups with days for that group
75
77
lessonOptions . forEach ( ( option ) => {
76
78
const module = modules [ option . moduleCode ] ;
77
- if ( ! module ) return ;
78
-
79
+ // get the timetable so that you can get the groups and the days for that group
79
80
const moduleTimetable = getModuleTimetable ( module , activeSemester ) ;
80
- const lessonsForType = moduleTimetable . filter (
81
- ( lesson ) => lesson . lessonType === option . lessonType ,
82
- ) ;
83
-
84
- const days = new Set < string > ( ) ;
85
- lessonsForType . forEach ( ( lesson ) => {
86
- days . add ( lesson . day ) ;
87
- } ) ;
88
- if ( days . has ( 'Saturday' ) ) {
89
- setHasSaturday ( true ) ;
90
- }
91
- lessonDays . push ( {
92
- uniqueKey : option . uniqueKey ,
93
- moduleCode : option . moduleCode ,
94
- lessonType : option . lessonType ,
95
- displayText : option . displayText ,
96
- days,
81
+ // now get the groups and the days for that group
82
+ // the DS for this is a list of maps, {groupName(string): days(list type)}
83
+ const groupDayMap = new Map < string , string [ ] > ( ) ;
84
+
85
+ moduleTimetable . forEach ( ( lesson ) => {
86
+ if ( lesson . lessonType === option . lessonType ) {
87
+ if ( groupDayMap . has ( lesson . classNo ) ) {
88
+ // find the the item with that key and push the day to the days array
89
+ const days = groupDayMap . get ( lesson . classNo ) || [ ] ;
90
+ days . push ( lesson . day ) ;
91
+ groupDayMap . set ( lesson . classNo , days ) ;
92
+ } else {
93
+ groupDayMap . set ( lesson . classNo , [ lesson . day ] ) ;
94
+ }
95
+ }
97
96
} ) ;
97
+ moduleGroupMap . set ( option . uniqueKey , groupDayMap ) ;
98
+ // now you have all the groups for that module-lessonType combination
98
99
} ) ;
99
-
100
- return lessonDays ;
100
+ return moduleGroupMap ;
101
101
} , [ lessonOptions , modules , activeSemester ] ) ;
102
102
103
+ // Unselected module-lessonType combinations are recorded lessons `module lessonType`
103
104
const recordings = useMemo ( ( ) => {
104
105
const selectedKeys = new Set ( selectedLessons . map ( ( lesson ) => lesson . uniqueKey ) ) ;
105
106
return lessonOptions
106
107
. filter ( ( lesson ) => ! selectedKeys . has ( lesson . uniqueKey ) )
107
- . map ( ( lesson ) => lesson . displayText ) ;
108
+ . map ( ( lesson ) => lesson . uniqueKey ) ;
108
109
} , [ selectedLessons , lessonOptions ] ) ;
109
110
110
111
// Validate free days against non-recorded lessons
111
112
useEffect ( ( ) => {
112
- const recordingsSet = new Set ( recordings ) ;
113
113
const conflicts : FreeDayConflict [ ] = [ ] ;
114
114
115
- // Check each non-recorded lesson (physical lessons that user plans to attend)
116
- lessonDaysData . forEach ( ( lessonData ) => {
117
- // Skip if this lesson is recorded (not attending in person)
118
- if ( recordingsSet . has ( lessonData . displayText ) ) return ;
119
-
120
- // Check if ALL days for this lesson are selected as free days
121
- const lessonDaysArray = Array . from ( lessonData . days ) ;
122
- const conflictingDays = lessonDaysArray . filter ( ( day ) => selectedFreeDays . has ( day ) ) ;
123
-
124
- // If all lesson days are selected as free days, it's a conflict
125
- if ( conflictingDays . length === lessonDaysArray . length && conflictingDays . length > 0 ) {
115
+ // check if all groups are not possible to attend, then it's a conflict, then day of conflict is the days of one of the groups
116
+ // go thorugh each module-lessonType combination, and within that,
117
+ // go through each group and within that check if any of the selected free days are in them, if so, that group is invalid
118
+ lessonGroupsData . forEach ( ( groupMap , uniqueKey ) => {
119
+ let validGroups = 0 ;
120
+ const groupDays = new Set < string > ( ) ;
121
+ groupMap . forEach ( ( days , _groupName ) => {
122
+ if (
123
+ recordings . includes ( uniqueKey ) || // if it is a recorded lesson, dont trigger a conflict
124
+ ! days . some ( ( day ) => selectedFreeDays . has ( day ) )
125
+ ) {
126
+ validGroups += 1 ;
127
+ }
128
+ days . forEach ( ( day ) => groupDays . add ( day ) ) ;
129
+ } ) ;
130
+ if ( selectedFreeDays . size > 0 && validGroups === 0 ) {
131
+ // check if conflict with the same moduleCode and lessonType already exists, if so, remove the old one and add the new one
132
+ const existingConflict = conflicts . find (
133
+ ( conflict ) =>
134
+ conflict . moduleCode === uniqueKey . split ( '-' ) [ 0 ] &&
135
+ conflict . lessonType === uniqueKey . split ( '-' ) [ 1 ] ,
136
+ ) ;
137
+ if ( existingConflict ) {
138
+ conflicts . splice ( conflicts . indexOf ( existingConflict ) , 1 ) ;
139
+ }
140
+ if ( groupDays . has ( 'Saturday' ) ) {
141
+ setHasSaturday ( true ) ;
142
+ }
126
143
conflicts . push ( {
127
- moduleCode : lessonData . moduleCode ,
128
- lessonType : lessonData . lessonType ,
129
- displayText : lessonData . displayText ,
130
- conflictingDays,
144
+ moduleCode : uniqueKey . split ( '-' ) [ 0 ] ,
145
+ lessonType : uniqueKey . split ( '-' ) [ 1 ] ,
146
+ displayText : uniqueKey . split ( '-' ) . join ( ' ' ) ,
147
+ conflictingDays : Array . from ( selectedFreeDays ) . filter ( ( day ) => groupDays . has ( day ) ) ,
131
148
} ) ;
132
149
}
133
150
} ) ;
134
-
135
151
setFreeDayConflicts ( conflicts ) ;
136
- } , [ selectedFreeDays , lessonDaysData , recordings ] ) ;
152
+ } , [ selectedFreeDays , lessonGroupsData , recordings ] ) ;
137
153
138
154
useEffect ( ( ) => {
139
155
const availableKeys = new Set ( lessonOptions . map ( ( option ) => option . uniqueKey ) ) ;
0 commit comments