Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: class item conflict indicator correct severity #330

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
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
41 changes: 19 additions & 22 deletions src/components/planner/sidebar/CoursesController/ClassItem.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { useContext, useMemo } from 'react'
import { useContext } from 'react'
import { ClassInfo } from '../../../../@types/index'
import { DropdownMenuCheckboxItem } from '../../../ui/dropdown-menu'
import { ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { conflictsSeverity, schedulesConflict } from '../../../../utils'
import { classesConflictSeverity } from '../../../../utils'
import MultipleOptionsContext from '../../../../contexts/MultipleOptionsContext'
import CourseContext from '../../../../contexts/CourseContext'


type Props = {
course_id: number,
classInfo: ClassInfo
conflict?: boolean
onSelect?: () => void
onMouseEnter?: () => void
onMouseLeave?: () => void
Expand All @@ -29,26 +28,26 @@ const ClassItem = ({ course_id, classInfo, onSelect, onMouseEnter, onMouseLeave
onSelect();
}

const conflict: number = useMemo(() => {
const classes: ClassInfo[] = []
const conflictSeverity = () => {
const chosenCourses = multipleOptions[selectedOption].course_options.filter(
(option) => option.course_id !== course_id
);

for (const course_option of multipleOptions[selectedOption].course_options) {
if (course_option.picked_class_id && course_option.course_id !== course_id) {
const pickedCourse = pickedCourses.find(co => co.id === course_option.course_id);
// retrieve class with the picked class id of the course option
const pickedClass = pickedCourse.classes.find(c => c.id === course_option.picked_class_id);
const otherClasses = [];
chosenCourses.forEach((option) => {
const courseInfo = pickedCourses.find((course) => course.id === option.course_id);
const pickedClass = courseInfo.classes.find((classInfo) => classInfo.id === option.picked_class_id);

classes.push(pickedClass);
}
if (pickedClass) otherClasses.push(pickedClass);
});

let maxSeverity = 0;
for (const otherClass of otherClasses) {
maxSeverity = Math.max(maxSeverity, classesConflictSeverity(classInfo, otherClass));
}

for (const pickedClass of classes)
for (const slot1 of pickedClass.slots)
for (const slot2 of classInfo.slots)
if (schedulesConflict(slot1, slot2)) {
return conflictsSeverity(slot1, slot2);
}
}, []);
return maxSeverity;
}

return (
<DropdownMenuCheckboxItem
Expand All @@ -62,8 +61,6 @@ const ClassItem = ({ course_id, classInfo, onSelect, onMouseEnter, onMouseLeave
{classInfo.slots.map((slot, idx) => (
<div key={`${classInfo.name}-${idx}`} className="flex items-center space-x-2">
<span className="text-xs text-gray-500">{slot.lesson_type}</span>
{/* <span className="text-xs text-gray-500">{convertWeekday(slot.day)}</span> */}
{/* <span className="text-xs text-gray-500">{getLessonBoxTime(slot)}</span> */}
<span className="text-xs text-gray-500">{slot.location}</span>
<span className="text-xs text-gray-500">
{slot.professors.map((professor) => professor.acronym).join(', ')}
Expand All @@ -72,7 +69,7 @@ const ClassItem = ({ course_id, classInfo, onSelect, onMouseEnter, onMouseLeave
))}
</div>
</div>
<ExclamationTriangleIcon className={`h-5 w-5 ${conflict ? 'block' : 'hidden'} ${conflict == 2 ? 'text-red-600' : 'text-amber-500'}`} aria-hidden="true" />
<ExclamationTriangleIcon className={`h-5 w-5 ${conflictSeverity() > 0 ? 'block' : 'hidden'} ${conflictSeverity() == 2 ? 'text-red-600' : 'text-amber-500'}`} aria-hidden="true" />
</DropdownMenuCheckboxItem>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ClassInfo, CourseInfo, CourseOption, ProfessorInfo } from "../../../../
import StorageAPI from "../../../../api/storage";
import CourseContext from "../../../../contexts/CourseContext";
import MultipleOptionsContext from "../../../../contexts/MultipleOptionsContext";
import { getAllPickedSlots, schedulesConflict, teacherIdsFromCourseInfo, uniqueTeachersFromCourseInfo } from "../../../../utils";
import { teacherIdsFromCourseInfo, uniqueTeachersFromCourseInfo } from "../../../../utils";
import { Desert } from "../../../svgs";
import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from "../../../ui/dropdown-menu";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../../ui/tabs";
Expand Down Expand Up @@ -157,13 +157,6 @@ const ClassSelectorDropdownController = ({
setMultipleOptions(newMultipleOptions)
}

// Checks if any of the selected classes have time conflicts with the classInfo
// This is used to display a warning icon in each class of the dropdown in case of conflicts
const timesCollideWithSelected = (classInfo: ClassInfo) => {
const pickedSlots = getAllPickedSlots(pickedCourses, multipleOptions[selectedOption])
return pickedSlots.some((slot) => classInfo.slots.some((currentSlot) => schedulesConflict(slot, currentSlot)))
}

return <>
<div>
{course.classes === null ? (
Expand Down Expand Up @@ -226,7 +219,6 @@ const ClassSelectorDropdownController = ({
key={`schedule-${classInfo.name}`}
course_id={course.id}
classInfo={classInfo}
conflict={timesCollideWithSelected(classInfo)}
onSelect={() => {
setSelectedClassId(classInfo.id)
setPreview(null)
Expand Down Expand Up @@ -260,7 +252,6 @@ const ClassSelectorDropdownController = ({
key={`schedule-${classInfo.name}`}
course_id={course.id}
classInfo={classInfo}
conflict={timesCollideWithSelected(classInfo)}
onSelect={() => {
setSelectedClassId(classInfo.id)
setPreview(null)
Expand Down
17 changes: 16 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import config from '../config/prod.json'
import dev_config from '../config/local.json'
import { CourseInfo, CourseOption, SlotInfo, MultipleOptions, Option, PickedCourses, ProfessorInfo } from '../@types'
import { CourseInfo, CourseOption, SlotInfo, MultipleOptions, Option, PickedCourses, ProfessorInfo, ClassInfo } from '../@types'
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import Plausible from 'plausible-tracker'
Expand Down Expand Up @@ -77,6 +77,20 @@ const conflictsSeverity = (first: SlotInfo, second: SlotInfo): number => {
return (isMandatory(first) && isMandatory(second)) ? 2 : 1;
}

const classesConflictSeverity = (first: ClassInfo, second: ClassInfo): number => {
let maxSeverity = 0;

for (const slot of first.slots) {
for (const otherSlot of second.slots) {
if (schedulesConflict(slot, otherSlot)) {
maxSeverity = Math.max(maxSeverity, conflictsSeverity(slot, otherSlot));
}
}
}

return maxSeverity;
}
Comment on lines +80 to +92
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const classesConflictSeverity = (first: ClassInfo, second: ClassInfo): number => {
let maxSeverity = 0;
for (const slot of first.slots) {
for (const otherSlot of second.slots) {
if (schedulesConflict(slot, otherSlot)) {
maxSeverity = Math.max(maxSeverity, conflictsSeverity(slot, otherSlot));
}
}
}
return maxSeverity;
}
const classesConflictSeverity = (first: ClassInfo, second: ClassInfo): number =>
first.slots.flatMap(slot =>
second.slots
.filter(otherSlot => schedulesConflict(slot, otherSlot))
.map(otherSlot => conflictsSeverity(slot, otherSlot))
).reduce((maxSeverity, severity) => Math.max(maxSeverity, severity), 0);

Because who needs friends when you have pure functions?

Copy link
Member Author

@tomaspalma tomaspalma Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that the code you presented using a more functional paradigm has less lines but it becomes less readable and harder to debug because if we wanted to console log something inside one of the functions it would be a lot harder.

It is also harder because the majority of the people doesn't know well the workings of reduce.


const schedulesConflict = (first: SlotInfo, second: SlotInfo) => {
if (first.day !== second.day) return false

Expand Down Expand Up @@ -395,5 +409,6 @@ export {
uniqueTeachersFromCourseInfo,
teacherIdsFromCourseInfo,
scrollToTop,
classesConflictSeverity,
plausible
}
Loading