Skip to content

Commit 20f9988

Browse files
committed
refactor: WIP refactor optimiser form
1 parent 47f04f0 commit 20f9988

18 files changed

+908
-838
lines changed

website/src/types/optimiser.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import { DayText, LessonType, ModuleCode } from './modules';
1+
import { DayText, LessonTime, LessonType, ModuleCode } from './modules';
22
import { ColorIndex } from './timetables';
33

44
export type LessonKey = string;
55
export type DisplayText = string;
66

7+
export type TimeRange = {
8+
earliest: LessonTime;
9+
latest: LessonTime;
10+
};
11+
712
export type LessonOption = {
813
moduleCode: ModuleCode;
914
lessonType: LessonType;

website/src/utils/optimiser.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
get,
77
groupBy,
88
isEmpty,
9+
range,
910
uniq,
1011
values,
1112
} from 'lodash';
@@ -19,10 +20,11 @@ import {
1920
Semester,
2021
WorkingDays,
2122
} from 'types/modules';
22-
import { DisplayText, FreeDayConflict, LessonOption, LessonKey } from 'types/optimiser';
23+
import { DisplayText, FreeDayConflict, LessonOption, LessonKey, TimeRange } from 'types/optimiser';
2324
import { ColorMapping } from 'types/reducers';
2425
import { LessonSlot, OptimiseResponse } from 'apis/optimiser';
2526
import { getModuleTimetable } from './modules';
27+
import { convertIndexToTime, convertTimeToIndex, NUM_INTERVALS_PER_HOUR } from './timify';
2628

2729
export function getLessonKey(moduleCode: ModuleCode, lessonType: LessonType): LessonKey {
2830
return `${moduleCode}|${lessonType}`;
@@ -150,3 +152,10 @@ export function isSaturdayInOptions(lessonOptions: LessonOption[]): boolean {
150152
.flatMap((lessonOption) => lessonOption.days)
151153
.some((day) => day === 'Saturday');
152154
}
155+
156+
export function getTimeValues(timeRange: TimeRange) {
157+
const earliestIndex = convertTimeToIndex(timeRange.earliest);
158+
const latestIndex = convertTimeToIndex(timeRange.latest) + 1;
159+
const stride = NUM_INTERVALS_PER_HOUR / 2;
160+
return range(earliestIndex, latestIndex, stride).map((index) => convertIndexToTime(index));
161+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Dispatch, SetStateAction, useState } from 'react';
2+
import { DayText } from 'types/modules';
3+
import { LessonOption, TimeRange } from 'types/optimiser';
4+
5+
const defaultLessonTimeRange: TimeRange = {
6+
earliest: '0800',
7+
latest: '1900',
8+
};
9+
10+
const defaultLunchTimeRange: TimeRange = {
11+
earliest: '1200',
12+
latest: '1400',
13+
};
14+
15+
const defaultMaxConsecutiveHours = 4;
16+
17+
export type OptimiserFormFields = {
18+
physicalLessonOptions: LessonOption[];
19+
setPhysicalLessonOptions: Dispatch<SetStateAction<LessonOption[]>>;
20+
freeDays: Set<DayText>;
21+
setFreeDays: Dispatch<SetStateAction<Set<DayText>>>;
22+
lessonTimeRange: TimeRange;
23+
setLessonTimeRange: Dispatch<SetStateAction<TimeRange>>;
24+
lunchTimeRange: TimeRange;
25+
setLunchTimeRange: Dispatch<SetStateAction<TimeRange>>;
26+
maxConsecutiveHours: number;
27+
setMaxConsecutiveHours: Dispatch<SetStateAction<number>>;
28+
};
29+
30+
export default function useOptimiserForm(): OptimiserFormFields {
31+
const [physicalLessonOptions, setPhysicalLessonOptions] = useState<LessonOption[]>([]);
32+
const [freeDays, setFreeDays] = useState<Set<DayText>>(new Set());
33+
const [lessonTimeRange, setLessonTimeRange] = useState(defaultLessonTimeRange);
34+
const [maxConsecutiveHours, setMaxConsecutiveHours] = useState(defaultMaxConsecutiveHours);
35+
const [lunchTimeRange, setLunchTimeRange] = useState(defaultLunchTimeRange);
36+
37+
return {
38+
physicalLessonOptions,
39+
setPhysicalLessonOptions,
40+
freeDays,
41+
setFreeDays,
42+
lessonTimeRange,
43+
setLessonTimeRange,
44+
lunchTimeRange,
45+
setLunchTimeRange,
46+
maxConsecutiveHours,
47+
setMaxConsecutiveHours,
48+
};
49+
}

website/src/views/optimiser/OptimiserContent.tsx

Lines changed: 37 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useCallback, useMemo, useEffect } from 'react';
1+
import React, { useState, useMemo, useEffect } from 'react';
22
import { useSelector } from 'react-redux';
33
import { getSemesterTimetableColors, getSemesterTimetableLessons } from 'selectors/timetables';
44
import { State } from 'types/state';
@@ -16,10 +16,10 @@ import {
1616
isSaturdayInOptions,
1717
} from 'utils/optimiser';
1818
import { FreeDayConflict, LessonOption } from 'types/optimiser';
19-
import { DayText } from 'types/modules';
19+
import useOptimiserForm from 'views/hooks/useOptimiserForm';
2020
import styles from './OptimiserContent.scss';
2121
import OptimiserHeader from './OptimiserHeader';
22-
import OptimiserForm from './OptimiserForm';
22+
import OptimiserForm from './OptimiserForm/OptimiserForm';
2323
import OptimiserButton from './OptimiserButton';
2424
import OptimiserResults from './OptimiserResults';
2525

@@ -30,16 +30,19 @@ const OptimiserContent: React.FC = () => {
3030
const modulesMap = useSelector(({ moduleBank }: State) => moduleBank.modules);
3131
const acadYear = useSelector((state: State) => state.timetables.academicYear);
3232

33-
const [selectedLessons, setSelectedLessons] = useState<LessonOption[]>([]);
34-
const [selectedFreeDays, setSelectedFreeDays] = useState<Set<DayText>>(new Set());
35-
const [earliestTime, setEarliestTime] = useState<string>('0800');
36-
const [latestTime, setLatestTime] = useState<string>('1900');
37-
const [earliestLunchTime, setEarliestLunchTime] = useState<string>('1200');
38-
const [latestLunchTime, setLatestLunchTime] = useState<string>('1400');
39-
const [unAssignedLessons, setUnAssignedLessons] = useState<LessonOption[]>([]);
33+
const optimiserFormFields = useOptimiserForm();
34+
const {
35+
physicalLessonOptions,
36+
setPhysicalLessonOptions,
37+
freeDays,
38+
lessonTimeRange,
39+
lunchTimeRange,
40+
maxConsecutiveHours,
41+
} = optimiserFormFields;
42+
43+
const [hasSaturday, setHasSaturday] = useState(false);
44+
const [unassignedLessons, setUnassignedLessons] = useState<LessonOption[]>([]);
4045
const [shareableLink, setShareableLink] = useState<string>('');
41-
const [hasSaturday, setHasSaturday] = useState<boolean>(false);
42-
const [maxConsecutiveHours, setMaxConsecutiveHours] = useState<number>(4);
4346
const [isOptimising, setIsOptimising] = useState<boolean>(false);
4447
const [error, setError] = useState<Error | null>(null);
4548

@@ -48,48 +51,23 @@ const OptimiserContent: React.FC = () => {
4851
return getLessonOptions(modules, activeSemester, colors);
4952
}, [timetable, modulesMap, activeSemester, colors]);
5053

51-
const recordedLessonOptions: LessonOption[] = useMemo(
52-
() => getRecordedLessonOptions(lessonOptions, selectedLessons),
53-
[lessonOptions, selectedLessons],
54-
);
55-
5654
const freeDayConflicts: FreeDayConflict[] = useMemo(() => {
5755
const modules = getSemesterModules(timetable, modulesMap);
58-
return getFreeDayConflicts(modules, activeSemester, selectedLessons, selectedFreeDays);
59-
}, [timetable, modulesMap, activeSemester, selectedLessons, selectedFreeDays]);
56+
return getFreeDayConflicts(modules, activeSemester, physicalLessonOptions, freeDays);
57+
}, [timetable, modulesMap, activeSemester, physicalLessonOptions, freeDays]);
58+
59+
const recordedLessonOptions: LessonOption[] = useMemo(
60+
() => getRecordedLessonOptions(lessonOptions, physicalLessonOptions),
61+
[lessonOptions, physicalLessonOptions],
62+
);
6063

6164
useEffect(() => {
6265
const availableKeys = new Set(lessonOptions.map((option) => option.lessonKey));
63-
setSelectedLessons((prev) => prev.filter((lesson) => availableKeys.has(lesson.lessonKey)));
66+
setPhysicalLessonOptions((prev) =>
67+
prev.filter((lesson) => availableKeys.has(lesson.lessonKey)),
68+
);
6469
setHasSaturday(isSaturdayInOptions(lessonOptions));
65-
}, [lessonOptions]);
66-
67-
const toggleLessonSelection = useCallback(
68-
(option: LessonOption) => {
69-
const isSelected = selectedLessons.some((lesson) => lesson.lessonKey === option.lessonKey);
70-
71-
if (isSelected) {
72-
setSelectedLessons((prev) =>
73-
prev.filter((lesson) => lesson.lessonKey !== option.lessonKey),
74-
);
75-
} else {
76-
setSelectedLessons((prev) => [...prev, option]);
77-
}
78-
},
79-
[selectedLessons],
80-
);
81-
82-
const toggleFreeDay = useCallback((day: DayText) => {
83-
setSelectedFreeDays((prev) => {
84-
const newSet = new Set(prev);
85-
if (newSet.has(day)) {
86-
newSet.delete(day);
87-
} else {
88-
newSet.add(day);
89-
}
90-
return newSet;
91-
});
92-
}, []);
70+
}, [lessonOptions, setHasSaturday, setPhysicalLessonOptions]);
9371

9472
const buttonOnClick = async () => {
9573
setShareableLink(''); // Reset shareable link
@@ -98,18 +76,17 @@ const OptimiserContent: React.FC = () => {
9876

9977
const modulesList = Object.keys(timetable);
10078
const acadYearFormatted = `${acadYear.split('/')[0]}-${acadYear.split('/')[1]}`;
101-
const recordings = recordedLessonOptions.map((lessonOption) => lessonOption.displayText);
10279

10380
const params: OptimiseRequest = {
10481
modules: modulesList,
10582
acadYear: acadYearFormatted,
10683
acadSem: activeSemester,
107-
freeDays: Array.from(selectedFreeDays),
108-
earliestTime,
109-
latestTime,
110-
recordings,
111-
lunchStart: earliestLunchTime,
112-
lunchEnd: latestLunchTime,
84+
freeDays: Array.from(freeDays),
85+
earliestTime: lessonTimeRange.earliest,
86+
latestTime: lessonTimeRange.latest,
87+
recordings: recordedLessonOptions.map((lessonOption) => lessonOption.displayText),
88+
lunchStart: lunchTimeRange.earliest,
89+
lunchEnd: lunchTimeRange.latest,
11390
maxConsecutiveHours,
11491
};
11592

@@ -127,42 +104,26 @@ const OptimiserContent: React.FC = () => {
127104
setShareableLink(link);
128105

129106
const unassignedLessonOptions = getUnassignedLessonOptions(lessonOptions, data);
130-
setUnAssignedLessons(unassignedLessonOptions);
107+
setUnassignedLessons(unassignedLessonOptions);
131108
};
132109

133110
return (
134111
<div className={styles.container}>
135112
<Title>Optimiser</Title>
136113

137-
{/* Optimiser header */}
138114
<OptimiserHeader />
139115

140-
{/* All the form elements */}
141116
<OptimiserForm
142117
lessonOptions={lessonOptions}
143-
selectedLessons={selectedLessons}
144-
selectedFreeDays={selectedFreeDays}
145-
earliestTime={earliestTime}
146-
latestTime={latestTime}
147-
earliestLunchTime={earliestLunchTime}
148-
latestLunchTime={latestLunchTime}
149118
freeDayConflicts={freeDayConflicts}
150119
hasSaturday={hasSaturday}
151-
maxConsecutiveHours={maxConsecutiveHours}
152-
onToggleLessonSelection={toggleLessonSelection}
153-
onToggleFreeDay={toggleFreeDay}
154-
onEarliestTimeChange={setEarliestTime}
155-
onLatestTimeChange={setLatestTime}
156-
onEarliestLunchTimeChange={setEarliestLunchTime}
157-
onLatestLunchTimeChange={setLatestLunchTime}
158-
onMaxConsecutiveHoursChange={setMaxConsecutiveHours}
120+
optimiserFormFields={optimiserFormFields}
159121
/>
160122

161-
{/* Optimiser button */}
162123
<OptimiserButton
163-
freeDayConflicts={freeDayConflicts}
164-
lessonOptions={lessonOptions}
165124
isOptimising={isOptimising}
125+
lessonOptions={lessonOptions}
126+
freeDayConflicts={freeDayConflicts}
166127
onClick={buttonOnClick}
167128
/>
168129

@@ -173,8 +134,7 @@ const OptimiserContent: React.FC = () => {
173134
/>
174135
)}
175136

176-
{/* Optimiser results */}
177-
<OptimiserResults shareableLink={shareableLink} unassignedLessons={unAssignedLessons} />
137+
<OptimiserResults shareableLink={shareableLink} unassignedLessons={unassignedLessons} />
178138
</div>
179139
);
180140
};

0 commit comments

Comments
 (0)