Skip to content

Commit ef5c3be

Browse files
committed
refactor: update optimiser form styles
1 parent 20f9988 commit ef5c3be

23 files changed

+391
-499
lines changed

website/src/apis/optimiser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import axios, { AxiosError } from 'axios';
22

3-
const api = '/api/optimiser/optimise';
3+
const api = 'https://nusmods.com/api/optimiser/optimise';
44

55
export interface OptimiseRequest {
66
modules: string[];

website/src/utils/optimiser.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ import {
66
get,
77
groupBy,
88
isEmpty,
9+
padStart,
910
range,
1011
uniq,
1112
values,
1213
} from 'lodash';
1314
import {
15+
AcadYear,
1416
Day,
1517
DayText,
18+
LessonTime,
1619
LessonType,
1720
Module,
1821
ModuleCode,
@@ -24,7 +27,13 @@ import { DisplayText, FreeDayConflict, LessonOption, LessonKey, TimeRange } from
2427
import { ColorMapping } from 'types/reducers';
2528
import { LessonSlot, OptimiseResponse } from 'apis/optimiser';
2629
import { getModuleTimetable } from './modules';
27-
import { convertIndexToTime, convertTimeToIndex, NUM_INTERVALS_PER_HOUR } from './timify';
30+
import {
31+
convertIndexToTime,
32+
convertTimeToIndex,
33+
getLessonTimeHours,
34+
getLessonTimeMinutes,
35+
NUM_INTERVALS_PER_HOUR,
36+
} from './timify';
2837

2938
export function getLessonKey(moduleCode: ModuleCode, lessonType: LessonType): LessonKey {
3039
return `${moduleCode}|${lessonType}`;
@@ -34,6 +43,17 @@ export function getDisplayText(moduleCode: ModuleCode, lessonType: LessonType):
3443
return `${moduleCode} ${lessonType}`;
3544
}
3645

46+
export function getOptimiserAcadYear(acadYear: AcadYear): string {
47+
const [from, to] = acadYear.split('/');
48+
return `${from}-${to}`;
49+
}
50+
51+
export function getOptimiserTime(time: LessonTime): string {
52+
const hh = padStart(`${getLessonTimeHours(time)}`, 2, '0');
53+
const mm = padStart(`${getLessonTimeMinutes(time)}`, 2, '0');
54+
return `${hh}:${mm}`;
55+
}
56+
3757
export function getLessonTypes(lessons: readonly RawLesson[]): LessonType[] {
3858
return uniq(lessons.map((lesson) => lesson.lessonType));
3959
}
@@ -123,11 +143,11 @@ export function getFreeDayConflicts(
123143
return isEmpty(conflictingDays)
124144
? null
125145
: {
126-
moduleCode,
127-
lessonType,
128-
displayText,
129-
days: conflictingDays,
130-
};
146+
moduleCode,
147+
lessonType,
148+
displayText,
149+
days: conflictingDays,
150+
};
131151
}),
132152
);
133153
});
@@ -153,6 +173,9 @@ export function isSaturdayInOptions(lessonOptions: LessonOption[]): boolean {
153173
.some((day) => day === 'Saturday');
154174
}
155175

176+
// TOOD: check styles
177+
178+
// TODO: add unit tests
156179
export function getTimeValues(timeRange: TimeRange) {
157180
const earliestIndex = convertTimeToIndex(timeRange.earliest);
158181
const latestIndex = convertTimeToIndex(timeRange.latest) + 1;

website/src/views/hooks/useOptimiserForm.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ const defaultLunchTimeRange: TimeRange = {
1515
const defaultMaxConsecutiveHours = 4;
1616

1717
export type OptimiserFormFields = {
18-
physicalLessonOptions: LessonOption[];
19-
setPhysicalLessonOptions: Dispatch<SetStateAction<LessonOption[]>>;
18+
liveLessonOptions: LessonOption[];
19+
setLiveLessonOptions: Dispatch<SetStateAction<LessonOption[]>>;
2020
freeDays: Set<DayText>;
2121
setFreeDays: Dispatch<SetStateAction<Set<DayText>>>;
2222
lessonTimeRange: TimeRange;
@@ -28,15 +28,15 @@ export type OptimiserFormFields = {
2828
};
2929

3030
export default function useOptimiserForm(): OptimiserFormFields {
31-
const [physicalLessonOptions, setPhysicalLessonOptions] = useState<LessonOption[]>([]);
31+
const [liveLessonOptions, setLiveLessonOptions] = useState<LessonOption[]>([]);
3232
const [freeDays, setFreeDays] = useState<Set<DayText>>(new Set());
3333
const [lessonTimeRange, setLessonTimeRange] = useState(defaultLessonTimeRange);
3434
const [maxConsecutiveHours, setMaxConsecutiveHours] = useState(defaultMaxConsecutiveHours);
3535
const [lunchTimeRange, setLunchTimeRange] = useState(defaultLunchTimeRange);
3636

3737
return {
38-
physicalLessonOptions,
39-
setPhysicalLessonOptions,
38+
liveLessonOptions,
39+
setLiveLessonOptions,
4040
freeDays,
4141
setFreeDays,
4242
lessonTimeRange,
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
@import '~styles/utils/modules-entry.scss';
22

3-
// Main container
43
.container {
54
display: flex;
65
flex-direction: column;
6+
padding: 0 1rem;
77
margin-top: 1rem;
8-
9-
@include media-breakpoint-down(md) {
10-
padding: 0 1rem;
11-
}
128
}

website/src/views/optimiser/OptimiserContent.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ const OptimiserContent: React.FC = () => {
3232

3333
const optimiserFormFields = useOptimiserForm();
3434
const {
35-
physicalLessonOptions,
36-
setPhysicalLessonOptions,
35+
liveLessonOptions: physicalLessonOptions,
36+
setLiveLessonOptions: setPhysicalLessonOptions,
3737
freeDays,
3838
lessonTimeRange,
3939
lunchTimeRange,
@@ -74,11 +74,10 @@ const OptimiserContent: React.FC = () => {
7474
setIsOptimising(true);
7575
setError(null);
7676

77-
const modulesList = Object.keys(timetable);
7877
const acadYearFormatted = `${acadYear.split('/')[0]}-${acadYear.split('/')[1]}`;
7978

8079
const params: OptimiseRequest = {
81-
modules: modulesList,
80+
modules: Object.keys(timetable),
8281
acadYear: acadYearFormatted,
8382
acadSem: activeSemester,
8483
freeDays: Array.from(freeDays),
Lines changed: 43 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,12 @@
11
@import '~styles/utils/modules-entry.scss';
2+
@import '../common.scss';
23

3-
.lessonTag {
4-
display: inline-flex;
5-
align-items: center;
6-
padding: 0.25rem 0.5rem;
7-
border-bottom: 3px solid;
8-
border-radius: 0.25rem;
9-
font-weight: 500;
10-
font-size: 0.8rem;
11-
transition: transform 0.15s ease, filter 0.15s ease;
12-
gap: 0.25rem;
13-
14-
&:hover {
15-
filter: brightness(0.8);
16-
}
17-
}
18-
19-
.tag {
20-
cursor: pointer;
21-
}
22-
23-
// Main content section
24-
.mainContent {
4+
.optimiserForm {
255
display: flex;
266
flex-direction: column;
27-
margin-top: 2rem;
28-
}
29-
30-
// Section headers
31-
.sectionHeader {
32-
display: flex;
33-
flex-direction: row;
34-
flex-wrap: wrap;
35-
align-items: center;
36-
margin-bottom: 1rem;
37-
font-size: 1rem;
38-
gap: 0.5rem;
39-
}
40-
41-
.timetableLink {
42-
color: inherit;
43-
&:hover {
44-
text-decoration: none;
45-
}
7+
margin-top: $form-fields-gap;
468
}
479

48-
// Icon styling
49-
.infoIcon {
50-
color: #69707a;
51-
}
52-
53-
// Priority notice section
5410
.priorityNotice {
5511
padding-top: 1rem;
5612
margin-top: 2rem;
@@ -71,3 +27,43 @@
7127
text-underline-offset: 2px;
7228
}
7329
}
30+
31+
.optimiserDescription {
32+
display: flex;
33+
flex-direction: row;
34+
margin-bottom: 0.5rem;
35+
36+
h4 {
37+
margin: 0;
38+
font-size: medium;
39+
}
40+
41+
@include media-breakpoint-down(xs) {
42+
h4 {
43+
font-size: small;
44+
}
45+
}
46+
}
47+
48+
.optimiserDropdown {
49+
width: 7.1rem;
50+
padding: 0.25rem 0.75rem;
51+
padding-left: 0.9rem;
52+
border: 1px solid var(--gray-lighter);
53+
border-radius: 0.25rem;
54+
outline: none;
55+
font-size: 1.3rem;
56+
font-family: monospace;
57+
color: inherit;
58+
background-image: url("data:image/svg+xml;charset=US-ASCII,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'><path fill='%23666' d='M2 0L0 2h4zm0 5L0 3h4z'/></svg>");
59+
background-position: right 0.7rem center;
60+
background-size: 0.85rem;
61+
background-repeat: no-repeat;
62+
background-color: transparent;
63+
appearance: none;
64+
cursor: pointer;
65+
66+
@include night-mode {
67+
background-color: var(--gray-lightest);
68+
}
69+
}

website/src/views/optimiser/OptimiserForm/OptimiserForm.tsx

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { Info } from 'react-feather';
2-
import Tooltip from 'views/components/Tooltip';
31
import { FreeDayConflict, LessonOption } from 'types/optimiser';
42
import { OptimiserFormFields } from 'views/hooks/useOptimiserForm';
53
import styles from './OptimiserForm.scss';
@@ -25,23 +23,7 @@ const OptimiserFormComponent: React.FC<OptimiserFormProps> = ({
2523
hasSaturday,
2624
optimiserFormFields,
2725
}) => (
28-
<div className={styles.mainContent}>
29-
<div className={styles.sectionHeader}>
30-
<div>
31-
Select lessons you plan to attend live (in person, online, or other format)
32-
<Tooltip
33-
content="Chosen lessons will only be allocated on your school days"
34-
placement="right"
35-
>
36-
<Info
37-
className={`${styles.tag} ${styles.infoIcon}`}
38-
style={{ marginLeft: '0.5rem' }}
39-
size={15}
40-
/>
41-
</Tooltip>
42-
</div>
43-
</div>
44-
26+
<form className={styles.optimiserForm}>
4527
<OptimiserLessonOptionSelect
4628
lessonOptions={lessonOptions}
4729
optimiserFormFields={optimiserFormFields}
@@ -54,14 +36,14 @@ const OptimiserFormComponent: React.FC<OptimiserFormProps> = ({
5436
<OptimiserLessonTimeRangeSelect optimiserFormFields={optimiserFormFields} />
5537

5638
<div className={styles.priorityNotice}>
57-
Following preferences will be <strong className={styles.prioritised}>prioritised</strong>
58-
but <strong className={styles.notGuaranteed}>not guaranteed</strong> :
39+
Following preferences will be <strong className={styles.prioritised}>prioritised</strong> but{' '}
40+
<strong className={styles.notGuaranteed}>not guaranteed</strong> :
5941
</div>
6042

6143
<OptimiserMaxConsecutiveHoursSelect optimiserFormFields={optimiserFormFields} />
6244

6345
<OptimiserLunchTimeRangeSelect optimiserFormFields={optimiserFormFields} />
64-
</div>
46+
</form>
6547
);
6648

6749
export default OptimiserFormComponent;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@import '~styles/utils/modules-entry.scss';
2+
3+
.optimiserTooltipIcon {
4+
composes: svg svg-small from global;
5+
margin: auto;
6+
margin-left: 0.5rem;
7+
color: #69707a;
8+
cursor: pointer;
9+
10+
@include media-breakpoint-down(xs) {
11+
width: 0.8rem;
12+
margin-top: -0.2rem;
13+
}
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Info } from 'react-feather';
2+
import Tooltip from 'views/components/Tooltip';
3+
4+
import styles from './OptimiserFormTooltip.scss';
5+
6+
type Props = {
7+
content: string;
8+
};
9+
10+
const OptimiserFormTooltip: React.FC<Props> = ({ content }) => (
11+
<Tooltip content={content} placement="right">
12+
<Info className={styles.optimiserTooltipIcon} />
13+
</Tooltip>
14+
);
15+
16+
export default OptimiserFormTooltip;

0 commit comments

Comments
 (0)