Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import NetworkError from './interfaces/NetworkError';
import {
Activity,
ClassData,
CourseCode,
CourseData,
CourseId,
DisplayTimetablesMap,
InInventory,
SelectedClasses,
Expand Down Expand Up @@ -242,7 +242,7 @@ const App: React.FC = () => {
prev = { ...prev };

try {
prev[classData.courseCode][classData.activity] = classData;
prev[classData.courseId][classData.activity] = classData;
} catch (err) {
setAlertMsg(unknownErrorMessage);
setErrorVisibility(true);
Expand All @@ -260,7 +260,7 @@ const App: React.FC = () => {
const handleRemoveClass = (classData: ClassData) => {
setSelectedClasses((prev) => {
prev = { ...prev };
prev[classData.courseCode][classData.activity] = null;
prev[classData.id][classData.activity] = null;
return prev;
});
};
Expand All @@ -276,11 +276,11 @@ const App: React.FC = () => {
setSelectedClasses((prevRef) => {
const prev = { ...prevRef };

prev[course.code] = {};
prev[course.id] = {};

// null means a class is unscheduled
Object.keys(course.activities).forEach((activity) => {
prev[course.code][activity] = isDefaultUnscheduled
prev[course.id][activity] = isDefaultUnscheduled
? null
: (course.activities[activity].find((x) => x.enrolments !== x.capacity && x.periods.length) ??
course.activities[activity].find((x) => x.periods.length) ??
Expand All @@ -303,10 +303,10 @@ const App: React.FC = () => {
noInit?: boolean,
callback?: (_selectedCourses: CourseData[]) => void,
) => {
const codes: string[] = Array.isArray(data) ? data : [data];
const courseIds: string[] = Array.isArray(data) ? data : [data];
Promise.all(
codes.map((code) =>
getCourseInfo(term!.substring(0, 2), code, term!.substring(2), isConvertToLocalTimezone).catch((err) => {
courseIds.map((id) =>
getCourseInfo(term!.substring(0, 2), id, term!.substring(2), isConvertToLocalTimezone).catch((err) => {
return err;
}),
),
Expand All @@ -318,16 +318,16 @@ const App: React.FC = () => {

// Update the existing courses with the new data (for changing timezone).
addedCourses.forEach((addedCourse) => {
if (newSelectedCourses.find((x) => x.code === addedCourse.code)) {
const index = newSelectedCourses.findIndex((x) => x.code === addedCourse.code);
if (newSelectedCourses.find((x) => x.id === addedCourse.id)) {
const index = newSelectedCourses.findIndex((x) => x.id === addedCourse.id);
newSelectedCourses[index] = addedCourse;
if (!courseData.map.find((i) => i.code === addedCourse.code)) {
if (!courseData.map.find((i) => i.id === addedCourse.id)) {
newCourseData.map.push(addedCourse);
}
} else {
newSelectedCourses.push(addedCourse);
}
if (!courseData.map.find((i) => i.code === addedCourse.code)) {
if (!courseData.map.find((i) => i.id === addedCourse.id)) {
newCourseData.map.push(addedCourse);
}
});
Expand All @@ -350,16 +350,16 @@ const App: React.FC = () => {
/**
* Handles removing a course from the currently selected courses
*
* @param courseCode The course code of the course which was removed
* @param courseId The course id of the course which was removed
*/
const handleRemoveCourse = (courseCode: CourseCode) => {
const newSelectedCourses = selectedCourses.filter((course) => course.code !== courseCode);
const handleRemoveCourse = (courseId: CourseId) => {
const newSelectedCourses = selectedCourses.filter((course) => course.id !== courseId);
setSelectedCourses(newSelectedCourses);
const newCourseData = courseData;
newCourseData.map = courseData.map.filter(() => {
for (const timetable of displayTimetables[term]) {
for (const course of timetable.selectedCourses) {
if (course.code.localeCompare(courseCode)) {
if (course.id.localeCompare(courseId)) {
return true;
}
}
Expand All @@ -370,13 +370,13 @@ const App: React.FC = () => {

setSelectedClasses((prev) => {
prev = { ...prev };
delete prev[courseCode];
delete prev[courseId];
return prev;
});
};

type ClassId = string;
type SavedClasses = Record<CourseCode, Record<Activity, ClassId | InInventory>>;
type SavedClasses = Record<CourseId, Record<Activity, ClassId | InInventory>>;

/**
* Populate selected courses, classes and created events with the data saved in local storage
Expand All @@ -392,34 +392,34 @@ const App: React.FC = () => {

if (!storage.get('timetables') || !storage.get('timetables')[term][selectedTimetable]) return;
handleSelectCourse(
storage.get('timetables')[term][selectedTimetable].selectedCourses.map((course: CourseData) => course.code),
storage.get('timetables')[term][selectedTimetable].selectedCourses.map((course: CourseData) => course.id),
true,
(newSelectedCourses) => {
const timetableSelectedClasses: SelectedClasses =
storage.get('timetables')[term][selectedTimetable].selectedClasses;

const savedClasses: SavedClasses = {};

Object.keys(timetableSelectedClasses).forEach((courseCode) => {
savedClasses[courseCode] = {};
Object.keys(timetableSelectedClasses[courseCode]).forEach((activity) => {
const classData = timetableSelectedClasses[courseCode][activity];
savedClasses[courseCode][activity] = classData ? classData.section : null;
Object.keys(timetableSelectedClasses).forEach((courseId) => {
savedClasses[courseId] = {};
Object.keys(timetableSelectedClasses[courseId]).forEach((activity) => {
const classData = timetableSelectedClasses[courseId][activity];
savedClasses[courseId][activity] = classData ? classData.section : null;
});
});

const newSelectedClasses: SelectedClasses = {};

Object.keys(savedClasses).forEach((courseCode) => {
newSelectedClasses[courseCode] = {};
Object.keys(savedClasses[courseCode]).forEach((activity) => {
const classId = savedClasses[courseCode][activity];
Object.keys(savedClasses).forEach((courseId) => {
newSelectedClasses[courseId] = {};
Object.keys(savedClasses[courseId]).forEach((activity) => {
const classId = savedClasses[courseId][activity];
let classData: ClassData | null = null;

if (classId) {
try {
const result = newSelectedCourses
.find((x) => x.code === courseCode)
.find((x) => x.id === courseId)
?.activities[activity].find((x) => x.section === classId);
if (result) classData = result;
} catch (err) {
Expand All @@ -429,7 +429,7 @@ const App: React.FC = () => {
}

// classData being null means the activity is unscheduled
newSelectedClasses[courseCode][activity] = classData;
newSelectedClasses[courseId][activity] = classData;
});
});
setSelectedClasses(newSelectedClasses);
Expand Down
15 changes: 8 additions & 7 deletions client/src/api/getCourseInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { client } from '../api/config';
import { DbCourse, DbTimes } from '../interfaces/Database';
import { GraphQLCourse } from '../interfaces/GraphQLCourseInfo';
import NetworkError from '../interfaces/NetworkError';
import { CourseCode, CourseData } from '../interfaces/Periods';
import { CourseData, CourseId } from '../interfaces/Periods';
import { dbCourseToCourseData } from '../utils/DbCourse';
import { graphQLCourseToDbCourse } from '../utils/graphQLCourseToDbCourse';

const GET_COURSE_INFO = gql`
query GetCourseInfo($courseCode: String!, $term: String!, $year: String!) {
courses(where: { course_code: { _eq: $courseCode } }) {
query GetCourseInfo($courseId: String!, $term: String!, $year: String!) {
courses(where: { course_id: { _eq: $courseId } }) {
course_id
course_code
course_name
classes(where: { term: { _eq: $term }, year: { _eq: $year }, activity: { _neq: "Course Enrolment" } }) {
Expand Down Expand Up @@ -94,24 +95,24 @@ const sortUnique = (arr: number[]): number[] => {
* Fetches the information of a specified course
*
* @param term The term that the course is offered in
* @param courseCode The code of the course to fetch
* @param courseId The id of the course to fetch
* @param isConvertToLocalTimezone Whether the user wants to convert the course periods into their local timezone
* @return A promise containing the information of the course that is offered in the
* current year and term
*
* @example
* const selectedCourseClasses = await getCourseInfo('T1', 'COMP1511', true)
* const selectedCourseClasses = await getCourseInfo('T1', 'COMP1511Undergraduate', true)
*/
const getCourseInfo = async (
term: string,
courseCode: CourseCode,
courseId: CourseId,
year: string,
isConvertToLocalTimezone: boolean,
): Promise<CourseData> => {
try {
const data: GraphQLCourse = await client.query({
query: GET_COURSE_INFO,
variables: { courseCode, term, year },
variables: { courseId, term, year },
});

const json: DbCourse = graphQLCourseToDbCourse(data);
Expand Down
2 changes: 2 additions & 0 deletions client/src/api/getCoursesList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import NetworkError from '../interfaces/NetworkError';

const toCoursesList = (data: FetchedCourse[]): CoursesList =>
data.map((course) => ({
id: course.course_id,
code: course.course_code,
name: course.course_name,
online: course.online,
Expand All @@ -22,6 +23,7 @@ const GET_COURSE_LIST = gql`
faculty
modes
school
course_id
course_code
course_name
terms
Expand Down
19 changes: 9 additions & 10 deletions client/src/components/controls/CourseSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,10 @@ const CourseSelect: React.FC<CourseSelectProps> = ({ assignedColors, handleSelec
setSelectedValue([]);
return;
}

setSelectedValue(
selectedCourses
.map((x) => x.code) // Get the course code of each course
.map((code) => coursesList.find((course) => course.code === code)) // Get the corresponding CourseOverview for each CourseData object
.map((x) => x.id) // Get the course code of each course
.map((id) => coursesList.find((course) => course.id === id)) // Get the corresponding CourseOverview for each CourseData object
.filter((overview): overview is CourseOverview => overview !== undefined),
);
}, [selectedCourses, coursesList]);
Expand Down Expand Up @@ -321,7 +320,7 @@ const CourseSelect: React.FC<CourseSelectProps> = ({ assignedColors, handleSelec

const onChange = (_: any, value: CoursesList) => {
if (value.length > selectedValue.length) {
handleSelect(value[value.length - 1].code);
handleSelect(value[value.length - 1].id);
setSelectedValue([...value]);
}
setOptions(defaultOptions);
Expand Down Expand Up @@ -421,12 +420,12 @@ const CourseSelect: React.FC<CourseSelectProps> = ({ assignedColors, handleSelec
// Prevent built-in option filtering
filterOptions={(o) => o}
ListboxComponent={ListboxComponent}
isOptionEqualToValue={(option, value) => option.code === value.code && option.career === value.career}
isOptionEqualToValue={(option, value) => option.id === value.id}
renderOption={(props, option, { selected }) => (
<li {...props}>
<li key={option.id} {...props}>
<StyledOption>
<StyledIcon>
{selectedValue.find((course: CourseOverview) => course.code === option.code) ? (
{selectedValue.find((course: CourseOverview) => course.id === option.id) ? (
<CheckRounded />
) : (
<AddRounded />
Expand Down Expand Up @@ -463,7 +462,7 @@ const CourseSelect: React.FC<CourseSelectProps> = ({ assignedColors, handleSelec
if (event.key === 'Backspace' && inputValue === '' && selectedValue.length > 0) {
event.stopPropagation();
setSelectedValue(selectedValue.slice(selectedValue.length - 1));
handleRemove(selectedValue[selectedValue.length - 1].code);
handleRemove(selectedValue[selectedValue.length - 1].id);
}
}}
InputLabelProps={{
Expand Down Expand Up @@ -495,8 +494,8 @@ const CourseSelect: React.FC<CourseSelectProps> = ({ assignedColors, handleSelec
deleteIcon={<CloseRounded />}
{...getTagProps({ index })}
onDelete={() => {
setSelectedValue(selectedValue.filter((course) => course.code !== option.code));
handleRemove(option.code);
setSelectedValue(selectedValue.filter((course) => course.id !== option.id));
handleRemove(option.id);
}}
/>
))
Expand Down
13 changes: 6 additions & 7 deletions client/src/components/timetable/DroppedCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useContext, useLayoutEffect, useRef, useState } from 'react';
import { unknownErrorMessage } from '../../constants/timetable';
import { AppContext } from '../../context/AppContext';
import { CourseContext } from '../../context/CourseContext';
import { Activity, CourseCode } from '../../interfaces/Periods';
import { Activity, CourseId } from '../../interfaces/Periods';
import { DroppedCardsProps } from '../../interfaces/PropTypes';
import { findClashes, getClashInfo } from '../../utils/clashes';
import { ClassCard, morphCards } from '../../utils/Drag';
Expand Down Expand Up @@ -34,15 +34,15 @@ const DroppedCards: React.FC<DroppedCardsProps> = ({
const droppedCardsRef = useRef<HTMLDivElement>(null);

/**
* @param courseCode The course code of the activity
* @param courseId The course id of the activity
* @param activity The activity
* @returns The inventory period corresponding to that activity
*/
const getInventoryPeriod = (courseCode: CourseCode, activity: Activity) =>
selectedCourses.find((course) => course.code === courseCode)?.inventoryData[activity];
const getInventoryPeriod = (courseId: CourseId, activity: Activity) =>
selectedCourses.find((course) => course.id === courseId)?.inventoryData[activity];

// Get all scheduled and unscheduled periods
Object.entries(selectedClasses).forEach(([courseCode, activities]) => {
Object.entries(selectedClasses).forEach(([courseId, activities]) => {
Object.entries(activities).forEach(([activity, classData]) => {
if (isHideExamClasses && activity === 'Exam') return;

Expand All @@ -53,7 +53,7 @@ const DroppedCards: React.FC<DroppedCardsProps> = ({
});
} else {
// The current period is in the inventory
const inventoryPeriod = getInventoryPeriod(courseCode, activity);
const inventoryPeriod = getInventoryPeriod(courseId, activity);
if (inventoryPeriod) {
classCards.push(inventoryPeriod);

Expand Down Expand Up @@ -110,7 +110,6 @@ const DroppedCards: React.FC<DroppedCardsProps> = ({
}, [days]);

const clashes = findClashes(selectedClasses, createdEvents);

// Generate classes
classCards.forEach((classCard) => {
try {
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/timetable/Dropzones.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const Dropzones: React.FC<DropzonesProps> = ({ assignedColors }) => {

const dropzones = selectedCourses.map((course) => (
<DropzoneGroup
key={course.code}
key={course.id}
course={course}
color={assignedColors[course.code]}
earliestStartTime={earliestStartTime}
Expand Down
2 changes: 2 additions & 0 deletions client/src/interfaces/Courses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CourseCode } from './Periods';

export type CoursesList = CourseOverview[];
export interface CourseOverview {
id: string;
code: string;
name: string;
online: boolean;
Expand All @@ -15,6 +16,7 @@ export interface CoursesListWithDate {
}

export interface FetchedCourse {
course_id: string;
course_code: CourseCode;
course_name: string;
online: boolean;
Expand Down
3 changes: 2 additions & 1 deletion client/src/interfaces/Database.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Activity, CourseCode, Section, Status } from './Periods';
import { Activity, CourseCode, CourseId, Section, Status } from './Periods';

export interface DbCourse {
courseId: CourseId;
courseCode: CourseCode;
name: string;
classes: DbClass[];
Expand Down
1 change: 1 addition & 0 deletions client/src/interfaces/GraphQLCourseInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface Class {

interface Course {
__typename: 'courses';
course_id: string;
course_code: string;
course_name: string;
classes: Class[];
Expand Down
Loading
Loading