(callback: (socket: Socket) => T): T {
+ if (!this.socket) {
+ throw new Error('Socket is not connected');
+ }
+ return callback(this.socket);
+ }
+}
+
+class SessionsSocket {
+ private url: string;
+ private socket: OptionalSocket;
+
+ constructor(url: string) {
+ this.url = url;
+ this.socket = new OptionalSocket();
+ }
+
+ connect() {
+ const newSocket = io(this.url, {
+ auth: {
+ token: 'dummy', // TODO: Replace with actual federated authentication token
+ }
+ });
+ this.socket.set(newSocket);
+ }
+
+ disconnect() {
+ this.socket.use(socket => socket.disconnect());
+ this.socket.unset();
+ }
+
+ on(event: string, callback: (...args: any[]) => void) {
+ this.socket.use(socket => socket.on(event, callback));
+ }
+
+ off(event: string, callback?: (...args: any[]) => void) {
+ this.socket.use(socket => socket.off(event, callback));
+ }
+
+ emit(event: string, ...args: any[]) {
+ this.socket.use(socket => socket.emit(event, args));
+ }
+}
+
+const sessionsSocket = new SessionsSocket(SOCKET_URL);
+
+export {
+ sessionsSocket,
+ SOCKET_URL,
+};
diff --git a/src/components/planner/sidebar/CoursesController.tsx b/src/components/planner/sidebar/CoursesController.tsx
index 5f3c87f5..6b9322a0 100644
--- a/src/components/planner/sidebar/CoursesController.tsx
+++ b/src/components/planner/sidebar/CoursesController.tsx
@@ -1,14 +1,36 @@
import { useContext } from 'react'
import ClassSelector from './CoursesController/ClassSelector'
import CourseContext from '../../../contexts/CourseContext'
+import MultipleOptionsContext from '../../../contexts/MultipleOptionsContext'
+import { TrashIcon } from "@heroicons/react/24/outline"
import { NoMajorSelectedSVG } from '../../svgs'
import { Button } from '../../ui/button'
const CoursesController = () => {
const { pickedCourses, setUcsModalOpen } = useContext(CourseContext);
+ const { multipleOptions, selectedOption, setMultipleOptions } = useContext(MultipleOptionsContext);
const noCoursesPicked = pickedCourses.length === 0;
+
+ const eraseClasses = () => {
+ const currentOption = multipleOptions[selectedOption];
+
+ const updatedCourseOptions = currentOption.course_options.map(courseOption => ({
+ ...courseOption,
+ picked_class_id: null,
+ locked: false,
+ }));
+
+ const updatedMultipleOptions = [...multipleOptions];
+ updatedMultipleOptions[selectedOption] = {
+ ...currentOption,
+ course_options: updatedCourseOptions,
+ };
+
+ setMultipleOptions(updatedMultipleOptions);
+ };
+
return (
{noCoursesPicked ? (
@@ -28,6 +50,20 @@ const CoursesController = () => {
))
)}
+
+ {!noCoursesPicked && (
+
+
+
+ )}
+
)
}
diff --git a/src/components/planner/sidebar/CoursesController/ClassItem.tsx b/src/components/planner/sidebar/CoursesController/ClassItem.tsx
index 13769b51..b8bc0332 100644
--- a/src/components/planner/sidebar/CoursesController/ClassItem.tsx
+++ b/src/components/planner/sidebar/CoursesController/ClassItem.tsx
@@ -1,8 +1,8 @@
-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'
@@ -10,7 +10,6 @@ import CourseContext from '../../../../contexts/CourseContext'
type Props = {
course_id: number,
classInfo: ClassInfo
- conflict?: boolean
onSelect?: () => void
onMouseEnter?: () => void
onMouseLeave?: () => void
@@ -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 (
(
{slot.lesson_type}
- {/* {convertWeekday(slot.day)} */}
- {/* {getLessonBoxTime(slot)} */}
{slot.location}
{slot.professors.map((professor) => professor.acronym).join(', ')}
@@ -72,7 +69,7 @@ const ClassItem = ({ course_id, classInfo, onSelect, onMouseEnter, onMouseLeave
))}
-
+ 0 ? 'block' : 'hidden'} ${conflictSeverity() == 2 ? 'text-red-600' : 'text-amber-500'}`} aria-hidden="true" />
)
}
diff --git a/src/components/planner/sidebar/CoursesController/ClassSelectorDropdownController.tsx b/src/components/planner/sidebar/CoursesController/ClassSelectorDropdownController.tsx
index a5d42203..22893f8e 100644
--- a/src/components/planner/sidebar/CoursesController/ClassSelectorDropdownController.tsx
+++ b/src/components/planner/sidebar/CoursesController/ClassSelectorDropdownController.tsx
@@ -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";
@@ -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 <>
{course.classes === null ? (
@@ -217,16 +210,17 @@ const ClassSelectorDropdownController = ({
{!course.classes || course.classes.length === 0
?
: <>
+ {selectedClassId && (
deleteOption()}>
Remover Seleção
+ )}
{course.classes &&
getOptions().map((classInfo) => (
{
setSelectedClassId(classInfo.id)
setPreview(null)
@@ -260,7 +254,6 @@ const ClassSelectorDropdownController = ({
key={`schedule-${classInfo.name}`}
course_id={course.id}
classInfo={classInfo}
- conflict={timesCollideWithSelected(classInfo)}
onSelect={() => {
setSelectedClassId(classInfo.id)
setPreview(null)
diff --git a/src/components/planner/sidebar/sessionController/course-picker/MajorSearchCombobox.tsx b/src/components/planner/sidebar/sessionController/course-picker/MajorSearchCombobox.tsx
index 16cf8dd0..8bf0473f 100644
--- a/src/components/planner/sidebar/sessionController/course-picker/MajorSearchCombobox.tsx
+++ b/src/components/planner/sidebar/sessionController/course-picker/MajorSearchCombobox.tsx
@@ -71,9 +71,11 @@ const MajorSearchCombobox = ({ selectedMajor, setSelectedMajor }: Props) => {
// handle that event and actually be scrollable with the mouse wheel
onWheel={(e) => e.stopPropagation()}
>
+ {selectedMajor && (
setSelectedMajor(null)}>
Remover Seleção
+ )}
{majors &&
majors.map((major) => (
{
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;
+}
+
const schedulesConflict = (first: SlotInfo, second: SlotInfo) => {
if (first.day !== second.day) return false
@@ -395,5 +409,6 @@ export {
uniqueTeachersFromCourseInfo,
teacherIdsFromCourseInfo,
scrollToTop,
+ classesConflictSeverity,
plausible
}