diff --git a/client/src/App.tsx b/client/src/App.tsx index c84dcd7e4..7aefdd069 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -37,8 +37,8 @@ import NetworkError from './interfaces/NetworkError'; import { Activity, ClassData, - CourseCode, CourseData, + CourseId, DisplayTimetablesMap, InInventory, SelectedClasses, @@ -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); @@ -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; }); }; @@ -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) ?? @@ -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; }), ), @@ -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); } }); @@ -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; } } @@ -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>; + type SavedClasses = Record>; /** * Populate selected courses, classes and created events with the data saved in local storage @@ -392,7 +392,7 @@ 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 = @@ -400,26 +400,26 @@ const App: React.FC = () => { 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) { @@ -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); diff --git a/client/src/api/getCourseInfo.ts b/client/src/api/getCourseInfo.ts index 06011ba0f..6751eeba4 100644 --- a/client/src/api/getCourseInfo.ts +++ b/client/src/api/getCourseInfo.ts @@ -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" } }) { @@ -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 => { try { const data: GraphQLCourse = await client.query({ query: GET_COURSE_INFO, - variables: { courseCode, term, year }, + variables: { courseId, term, year }, }); const json: DbCourse = graphQLCourseToDbCourse(data); diff --git a/client/src/api/getCoursesList.ts b/client/src/api/getCoursesList.ts index 7d12fec74..15faf3907 100644 --- a/client/src/api/getCoursesList.ts +++ b/client/src/api/getCoursesList.ts @@ -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, @@ -22,6 +23,7 @@ const GET_COURSE_LIST = gql` faculty modes school + course_id course_code course_name terms diff --git a/client/src/components/controls/CourseSelect.tsx b/client/src/components/controls/CourseSelect.tsx index 258be39d0..bb0776d9e 100644 --- a/client/src/components/controls/CourseSelect.tsx +++ b/client/src/components/controls/CourseSelect.tsx @@ -215,11 +215,10 @@ const CourseSelect: React.FC = ({ 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]); @@ -321,7 +320,7 @@ const CourseSelect: React.FC = ({ 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); @@ -421,12 +420,12 @@ const CourseSelect: React.FC = ({ 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 }) => ( -
  • +
  • - {selectedValue.find((course: CourseOverview) => course.code === option.code) ? ( + {selectedValue.find((course: CourseOverview) => course.id === option.id) ? ( ) : ( @@ -463,7 +462,7 @@ const CourseSelect: React.FC = ({ 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={{ @@ -495,8 +494,8 @@ const CourseSelect: React.FC = ({ assignedColors, handleSelec deleteIcon={} {...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); }} /> )) diff --git a/client/src/components/timetable/DroppedCards.tsx b/client/src/components/timetable/DroppedCards.tsx index e2a499115..2a6258cef 100644 --- a/client/src/components/timetable/DroppedCards.tsx +++ b/client/src/components/timetable/DroppedCards.tsx @@ -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'; @@ -34,15 +34,15 @@ const DroppedCards: React.FC = ({ const droppedCardsRef = useRef(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; @@ -53,7 +53,7 @@ const DroppedCards: React.FC = ({ }); } else { // The current period is in the inventory - const inventoryPeriod = getInventoryPeriod(courseCode, activity); + const inventoryPeriod = getInventoryPeriod(courseId, activity); if (inventoryPeriod) { classCards.push(inventoryPeriod); @@ -110,7 +110,6 @@ const DroppedCards: React.FC = ({ }, [days]); const clashes = findClashes(selectedClasses, createdEvents); - // Generate classes classCards.forEach((classCard) => { try { diff --git a/client/src/components/timetable/Dropzones.tsx b/client/src/components/timetable/Dropzones.tsx index 311fb8ce8..ae351755b 100644 --- a/client/src/components/timetable/Dropzones.tsx +++ b/client/src/components/timetable/Dropzones.tsx @@ -80,7 +80,7 @@ const Dropzones: React.FC = ({ assignedColors }) => { const dropzones = selectedCourses.map((course) => ( >; +export type SelectedClasses = Record>; export type CreatedEvents = Record; export type EventMetadata = EventData & EventTime; export interface CourseData { + id: CourseId; code: CourseCode; name: string; earliestStartTime: number; @@ -31,6 +33,7 @@ export interface TermData { export interface ClassData { id: string; classNo: string; + courseId: CourseId; courseCode: CourseCode; courseName: string; activity: Activity; @@ -111,6 +114,7 @@ export interface TimetableDTO { } export interface InventoryData { + courseId: CourseId; courseCode: CourseCode; activity: Activity; } @@ -126,6 +130,7 @@ export interface EventData { export interface ClassPeriod { type: 'class'; classId: string; + courseId: CourseId; courseCode: CourseCode; activity: Activity; subActivity: string; @@ -136,6 +141,7 @@ export interface ClassPeriod { export interface InventoryPeriod { type: 'inventory'; classId: null; + courseId: CourseId; courseCode: CourseCode; activity: Activity; } diff --git a/client/src/interfaces/PropTypes.ts b/client/src/interfaces/PropTypes.ts index f9f42c8e2..13da12f61 100644 --- a/client/src/interfaces/PropTypes.ts +++ b/client/src/interfaces/PropTypes.ts @@ -2,7 +2,7 @@ import { PopoverOrigin, SelectChangeEvent } from '@mui/material'; import { ReactNode } from 'react'; import { ClassCard } from '../utils/Drag'; -import { ClassData, ClassPeriod, CourseCode, CourseData, EventPeriod, InInventory, Location, Section } from './Periods'; +import { ClassData, ClassPeriod, CourseData, CourseId, EventPeriod, InInventory, Location, Section } from './Periods'; export interface AppContextProviderProps { children: ReactNode; @@ -34,14 +34,14 @@ export interface CustomModalProps { export interface CourseSelectProps { assignedColors: Record; handleSelect(data: string | string[], a?: boolean, callback?: (_selectedCourses: CourseData[]) => void): void; - handleRemove(courseCode: CourseCode): void; + handleRemove(courseId: CourseId): void; } export interface ControlsProps { assignedColors: Record; handleSelectClass(classData: ClassData): void; handleSelectCourse(data: string | string[], a?: boolean, callback?: (_selectedCourses: CourseData[]) => void): void; - handleRemoveCourse(courseCode: CourseCode): void; + handleRemoveCourse(courseId: CourseId): void; } export interface DropdownOptionProps { diff --git a/client/src/utils/DbCourse.ts b/client/src/utils/DbCourse.ts index a8bb30501..5f50b4b0f 100644 --- a/client/src/utils/DbCourse.ts +++ b/client/src/utils/DbCourse.ts @@ -119,6 +119,7 @@ const dbTimesToPeriod = (dbTimes: DbTimes, classData: ClassData, isConvertToLoca const classPeriod: ClassPeriod = { type: 'class', classId: classData.id, + courseId: classData.courseId, courseCode: classData.courseCode, activity: classData.activity, subActivity: subActivity, @@ -148,6 +149,7 @@ const dbTimesToPeriod = (dbTimes: DbTimes, classData: ClassData, isConvertToLoca */ export const dbCourseToCourseData = (dbCourse: DbCourse, isConvertToLocalTimezone: boolean): CourseData => { const courseData: CourseData = { + id: dbCourse.courseId, code: dbCourse.courseCode, name: dbCourse.name, activities: {}, @@ -159,6 +161,7 @@ export const dbCourseToCourseData = (dbCourse: DbCourse, isConvertToLocalTimezon dbCourse.classes.forEach((dbClass) => { const classData: ClassData = { id: uuidv4(), + courseId: dbCourse.courseId, courseCode: dbCourse.courseCode, courseName: dbCourse.name, activity: dbClass.activity, @@ -186,6 +189,7 @@ export const dbCourseToCourseData = (dbCourse: DbCourse, isConvertToLocalTimezon const newPeriod: ClassPeriod = { type: 'class', classId: period.classId, + courseId: period.courseId, courseCode: period.courseCode, activity: period.activity, subActivity: period.subActivity, @@ -246,6 +250,7 @@ export const dbCourseToCourseData = (dbCourse: DbCourse, isConvertToLocalTimezon courseData.inventoryData[activity] = { type: 'inventory', classId: null, + courseId: courseData.id, courseCode: courseData.code, activity: activity, }; diff --git a/client/src/utils/Drag.ts b/client/src/utils/Drag.ts index f703c7976..4137151dd 100644 --- a/client/src/utils/Drag.ts +++ b/client/src/utils/Drag.ts @@ -142,7 +142,7 @@ export const checkCanDrop = (a: ClassCard | null, b: ClassCard | null) => { // These last two || clauses are necessary because all periods for a given activity // may not be the same length (see JURD7251 22T2) thus the equalDur(a, b) condition will be false. return ( - a.courseCode === b.courseCode && + a.courseId === b.courseId && a.activity === b.activity && (!isScheduledPeriod(a) || !isScheduledPeriod(b) || @@ -249,7 +249,7 @@ const getIsElevated = (cardData: ClassCard | EventPeriod) => { const isMatchingClasses = isScheduledPeriod(cardData) && isScheduledPeriod(dragTarget) && - cardData.courseCode === dragTarget.courseCode && + cardData.courseId === dragTarget.courseId && cardData.activity === dragTarget.activity; return dragTarget !== null && (cardData === dragTarget || isMatchingClasses); diff --git a/client/src/utils/generateICS.ts b/client/src/utils/generateICS.ts index 27479a525..7eab7f844 100644 --- a/client/src/utils/generateICS.ts +++ b/client/src/utils/generateICS.ts @@ -85,8 +85,8 @@ const getClassEvents = (courses: CourseData[], classes: SelectedClasses): [Class // NOTE: this function may be useful in other applications, if so, move it to a more reasonably named file. const allClasses = courses.flatMap((course) => Object.keys(course.activities) - .filter((possibleActivity) => classes[course.code] !== null && classes[course.code][possibleActivity] !== null) - .map((activities) => classes[course.code][activities]), + .filter((possibleActivity) => classes[course.id] !== null && classes[course.id][possibleActivity] !== null) + .map((activities) => classes[course.id][activities]), ); return allClasses.flatMap((classTime) => diff --git a/client/src/utils/getClassCourse.ts b/client/src/utils/getClassCourse.ts index 87dd4606a..42af2a304 100644 --- a/client/src/utils/getClassCourse.ts +++ b/client/src/utils/getClassCourse.ts @@ -6,7 +6,7 @@ import { ClassData, ClassPeriod, CourseData, InventoryData, InventoryPeriod } fr * @returns The course data for the course associated with a particular class */ export const getCourseFromClassData = (selectedCourses: CourseData[], data: ClassData | InventoryData) => { - const course = selectedCourses.find((course) => course.code === data.courseCode); + const course = selectedCourses.find((course) => course.id === data.courseId); if (course) { return course; } else { @@ -20,7 +20,7 @@ export const getCourseFromClassData = (selectedCourses: CourseData[], data: Clas * @returns The class data for the class associated with a particular period */ export const getClassDataFromPeriod = (selectedCourses: CourseData[], period: ClassPeriod | InventoryPeriod) => { - const course = selectedCourses.find((course) => course.code === period.courseCode); + const course = selectedCourses.find((course) => course.id === period.courseId); if (!course) throw new Error(); const classData = course.activities[period.activity].find((classData) => classData.id === period.classId); diff --git a/client/src/utils/graphQLCourseToDbCourse.ts b/client/src/utils/graphQLCourseToDbCourse.ts index 0bb9855c1..1eea7ea83 100644 --- a/client/src/utils/graphQLCourseToDbCourse.ts +++ b/client/src/utils/graphQLCourseToDbCourse.ts @@ -22,6 +22,7 @@ export const graphQLCourseToDbCourse = (graphQLCourse: GraphQLCourse): DbCourse const course = graphQLCourse.data.courses[0]; return { + courseId: course.course_id, courseCode: course.course_code, name: course.course_name, classes: course.classes.map((classItem) => ({ diff --git a/client/src/utils/timetableHelpers.ts b/client/src/utils/timetableHelpers.ts index 4f7f2bcb5..976c79c23 100644 --- a/client/src/utils/timetableHelpers.ts +++ b/client/src/utils/timetableHelpers.ts @@ -22,7 +22,7 @@ export type ActionsPointer = Record; const duplicateClasses = (selectedClasses: SelectedClasses) => { const newClasses: SelectedClasses = {}; - Object.entries(selectedClasses).forEach(([courseCode, activities]) => { + Object.entries(selectedClasses).forEach(([courseId, activities]) => { const newActivityCopy: Record = {}; Object.entries(activities).forEach(([activity, classData]) => { @@ -34,7 +34,7 @@ const duplicateClasses = (selectedClasses: SelectedClasses) => { } // newActivityCopy[activity] = classData !== null ? { ...classData } : null; }); - newClasses[courseCode] = { ...newActivityCopy }; + newClasses[courseId] = { ...newActivityCopy }; }); return newClasses;