From 2ddfde26420a57b987900be61a697124113b8a37 Mon Sep 17 00:00:00 2001 From: Sriram Hariharan Date: Wed, 22 Mar 2023 22:16:51 -0500 Subject: [PATCH] course conflict highlighting and calculations --- src/shared/storage/UserScheduleStore.ts | 1 + src/shared/types/Course.ts | 20 ++++++++++++++++++- src/shared/types/CourseMeeting.ts | 16 +++++++++++++++ src/views/components/CourseCatalogMain.tsx | 2 +- .../GradeDistribution/GradeDistribution.tsx | 2 -- .../injected/TableRow/TableRow.module.scss | 3 ++- .../components/injected/TableRow/TableRow.tsx | 18 ++++++++++++++--- 7 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/shared/storage/UserScheduleStore.ts b/src/shared/storage/UserScheduleStore.ts index cf8830bd..3e857b3d 100644 --- a/src/shared/storage/UserScheduleStore.ts +++ b/src/shared/storage/UserScheduleStore.ts @@ -20,4 +20,5 @@ export const userScheduleStore = createLocalStore({ ], }); + debugStore({ userScheduleStore }); diff --git a/src/shared/types/Course.ts b/src/shared/types/Course.ts index 3e0e46f6..70a16ccb 100644 --- a/src/shared/types/Course.ts +++ b/src/shared/types/Course.ts @@ -1,6 +1,6 @@ /* eslint-disable max-classes-per-file */ import { Serialized } from 'chrome-extension-toolkit'; -import { capitalize } from '../util/string'; +import { CourseMeeting } from './CourseMeeting'; import { CourseSchedule } from './CourseSchedule'; import Instructor from './Instructor'; @@ -74,6 +74,24 @@ export class Course { this.schedule = new CourseSchedule(course.schedule); this.instructors = course.instructors.map(i => new Instructor(i)); } + + /** + * Gets a list of all the conflicts between this course and another course (i.e. if they have a meeting at the same time) + * @param other another course to compare this course to + * @returns a list of all the conflicts between this course and the other course as a tuple of the two conflicting meetings + */ + getConflicts(other: Course): [CourseMeeting, CourseMeeting][] { + const conflicts: [CourseMeeting, CourseMeeting][] = []; + for (const meeting of this.schedule.meetings) { + for (const otherMeeting of other.schedule.meetings) { + if (meeting.isConflicting(otherMeeting)) { + conflicts.push([meeting, otherMeeting]); + } + } + } + + return conflicts; + } } /** diff --git a/src/shared/types/CourseMeeting.ts b/src/shared/types/CourseMeeting.ts index 04872c81..2e8339f8 100644 --- a/src/shared/types/CourseMeeting.ts +++ b/src/shared/types/CourseMeeting.ts @@ -41,6 +41,22 @@ export class CourseMeeting { Object.assign(this, meeting); } + /** + * Whether or not this meeting conflicts with another meeting + * MWF 10:00 am - 11:00 am conflicts with a F 10:00 am - 10:30 am + * @param meeting the meeting to check for conflicts with + * @returns true if the given meeting conflicts with this meeting, false otherwise + */ + isConflicting(meeting: CourseMeeting): boolean { + const { days, startTime, endTime } = this; + const { days: otherDays, startTime: otherStartTime, endTime: otherEndTime } = meeting; + + const hasDayConflict = days.some(day => otherDays.includes(day)); + const hasTimeConflict = startTime < otherEndTime && endTime > otherStartTime; + + return hasDayConflict && hasTimeConflict; + } + /** * Return the string representation of the days of the week that this meeting is taught * @param options options for the string representation diff --git a/src/views/components/CourseCatalogMain.tsx b/src/views/components/CourseCatalogMain.tsx index d33882f9..03aef3fa 100644 --- a/src/views/components/CourseCatalogMain.tsx +++ b/src/views/components/CourseCatalogMain.tsx @@ -71,7 +71,7 @@ export default function CourseCatalogMain({ support }: Props) { key={row.course.uniqueId} row={row} isSelected={row.course.uniqueId === selectedCourse?.uniqueId} - isInActiveSchedule={Boolean(activeSchedule?.containsCourse(row.course))} + activeSchedule={activeSchedule} onClick={handleRowButtonClick(row.course)} /> ); diff --git a/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.tsx b/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.tsx index b190e735..579f8752 100644 --- a/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.tsx +++ b/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.tsx @@ -14,7 +14,6 @@ import { queryAggregateDistribution, querySemesterDistribution, } from 'src/views/lib/database/queryDistribution'; -import { bMessenger } from 'src/shared/messages'; import styles from './GradeDistribution.module.scss'; enum DataStatus { @@ -137,7 +136,6 @@ export default function GradeDistribution({ course }: Props) { useEffect(() => { queryAggregateDistribution(course) .then(([distribution, semesters]) => { - console.log('.then -> distribution, semesters:', distribution, semesters); setSemesters(semesters); updateChart(distribution); setStatus(DataStatus.FOUND); diff --git a/src/views/components/injected/TableRow/TableRow.module.scss b/src/views/components/injected/TableRow/TableRow.module.scss index 61038f71..44486740 100644 --- a/src/views/components/injected/TableRow/TableRow.module.scss +++ b/src/views/components/injected/TableRow/TableRow.module.scss @@ -21,7 +21,8 @@ .isConflict { * { - color: $speedway_brick !important; + color: $speedway_brick; text-decoration: line-through !important; + font-weight: normal !important; } } diff --git a/src/views/components/injected/TableRow/TableRow.tsx b/src/views/components/injected/TableRow/TableRow.tsx index 5b5c2e3e..b70db8c2 100644 --- a/src/views/components/injected/TableRow/TableRow.tsx +++ b/src/views/components/injected/TableRow/TableRow.tsx @@ -50,9 +50,21 @@ export default function TableRow({ row, isSelected, activeSchedule, onClick }: P }, [activeSchedule, element.classList]); useEffect(() => { - // if (!activeSchedule || !course) return; - // TODO: handle conflicts here - }, []); + if (!activeSchedule || !course) { + return; + } + + let hasConflicts = activeSchedule.courses.find(c => { + let conflicts = course.getConflicts(c); + return conflicts.length > 0; + }); + + element.classList[hasConflicts ? 'add' : 'remove'](styles.isConflict); + + return () => { + element.classList.remove(styles.isConflict); + }; + }, [activeSchedule, course]); if (!container) { return null;