Skip to content
Merged
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
84 changes: 47 additions & 37 deletions src/components/generator/Calendar/CalendarComponent.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { useState, useEffect, useContext, useCallback } from "react";
import React, {
useState,
useEffect,
useContext,
useCallback,
useMemo,
} from "react";
import FullCalendar from "@fullcalendar/react";
import { useSnackbar } from "notistack";
import CalendarNavBar from "./CalendarNavBar";
Expand Down Expand Up @@ -30,14 +36,12 @@ import { useEventBusHandlers } from "./hooks/useEventBusHandlers.js";
import { prepareCoursesForTimeline } from "./utils/courseTimelineUtils.js";
import {
calculateNavigationDate,
handleDurationChange,
getCalendarViewNotificationMessage,
getVisibleTimetables,
} from "./utils/calendarViewUtils.js";
import { getFullCalendarConfig } from "./utils/calendarConfigUtils.js";
import MultiLineSnackbar from "@/components/sitewide/MultiLineSnackbar";
import { useIsMobile } from "@/lib/utils/screenSizeUtils";
let previousDuration = null;
let aprioriDurationTimetable = null;
export default function CalendarComponent({
timetables,
setTimetables,
Expand All @@ -53,6 +57,7 @@ export default function CalendarComponent({
const courseColorsRef = React.useRef({});
const [events, setEvents] = useState([]);
const [currentTimetableIndex, setCurrentTimetableIndex] = useState(0);
const [viewRange, setViewRange] = useState(null);
const { setCourseDetails } = useContext(CourseDetailsContext);
const { courseColors, setCalendarUpdateHandler, getDefaultColorForCourse } =
useContext(CourseColorsContext);
Expand All @@ -67,6 +72,11 @@ export default function CalendarComponent({
const [renameAnchorPosition, setRenameAnchorPosition] = useState(null);
const [coursesForTimeline, setCoursesForTimeline] = useState([]);

const visibleTimetables = useMemo(
() => getVisibleTimetables(timetables, viewRange),
[timetables, viewRange],
);

// Screen size detection
const isMobile = useIsMobile();

Expand All @@ -81,21 +91,28 @@ export default function CalendarComponent({
});

useEffect(() => {
timetablesRef.current = timetables;
}, [timetables]);
timetablesRef.current = visibleTimetables;
}, [visibleTimetables]);

useEffect(() => {
currentTimetableIndexRef.current = currentTimetableIndex;
}, [currentTimetableIndex]);

useEffect(() => {
if (visibleTimetables.length === 0) return;
if (currentTimetableIndex >= visibleTimetables.length) {
setCurrentTimetableIndex(0);
}
}, [currentTimetableIndex, visibleTimetables.length]);

useEffect(() => {
courseColorsRef.current = courseColors;
updateCalendarEvents();
}, [courseColors]);

useEffect(() => {
updateCalendarEvents();
}, [currentTimetableIndex, timetables]);
}, [currentTimetableIndex, visibleTimetables]);

useEffect(() => {
if (selectedDuration) {
Expand All @@ -108,8 +125,9 @@ export default function CalendarComponent({
}, []);

const handleLast = useCallback(() => {
setCurrentTimetableIndex(timetables.length - 1);
}, [timetables.length]);
if (visibleTimetables.length === 0) return;
setCurrentTimetableIndex(visibleTimetables.length - 1);
}, [visibleTimetables.length]);

const updateCalendarEvents = useCallback(() => {
const currentTimetables = timetablesRef.current;
Expand Down Expand Up @@ -146,13 +164,6 @@ export default function CalendarComponent({
const hasWeekendClasses = checkForWeekendClasses(timetable);
setShowWeekends(hasWeekendClasses);

if (
previousDuration === selectedDuration.split("-")[2] &&
JSON.stringify(timetable)
) {
aprioriDurationTimetable = JSON.parse(JSON.stringify(timetable));
}

const newEvents = createCalendarEvents(
timetable,
getDaysOfWeek,
Expand All @@ -177,8 +188,6 @@ export default function CalendarComponent({
setNoTimetablesGenerated(false);
} else {
setNoCourses(true);
previousDuration = null;
aprioriDurationTimetable = null;
const newEvents = createCalendarEvents(
null,
getDaysOfWeek,
Expand All @@ -204,7 +213,7 @@ export default function CalendarComponent({
const calendarApi = calendarRef.current?.getApi();
if (!calendarApi) return;

const [startUnix, endUnix, duration] = durationLabel.split("-");
const [startUnix] = durationLabel.split("-");

const startDate = new Date(parseInt(startUnix) * 1000);
const navigationDate = calculateNavigationDate(startDate);
Expand All @@ -214,19 +223,7 @@ export default function CalendarComponent({
calendarApi.gotoDate(navigationDate);
});

if (previousDuration == null) {
previousDuration = duration;
} else {
handleDurationChange(
previousDuration,
duration,
aprioriDurationTimetable,
setCurrentTimetableIndex,
setTimetables,
sortOption,
);
previousDuration = duration;
}
setCurrentTimetableIndex(0);

setSelectedDuration(durationLabel);

Expand All @@ -237,6 +234,13 @@ export default function CalendarComponent({
});
};

const handleDatesSet = useCallback((dateInfo) => {
setViewRange({
start: dateInfo.start,
end: dateInfo.end,
});
}, []);

const [shiftHeld, setShiftHeld] = useState(false);
const [hoveredElement, setHoveredElement] = useState(null);

Expand Down Expand Up @@ -362,7 +366,10 @@ export default function CalendarComponent({
};

const handleNext = () => {
setCurrentTimetableIndex((currentTimetableIndex + 1) % timetables.length);
if (visibleTimetables.length === 0) return;
setCurrentTimetableIndex(
(currentTimetableIndex + 1) % visibleTimetables.length,
);
};

const handleRenameDialogClose = () => {
Expand Down Expand Up @@ -398,8 +405,10 @@ export default function CalendarComponent({
};

const handlePrevious = () => {
if (visibleTimetables.length === 0) return;
setCurrentTimetableIndex(
(currentTimetableIndex - 1 + timetables.length) % timetables.length,
(currentTimetableIndex - 1 + visibleTimetables.length) %
visibleTimetables.length,
);
};

Expand Down Expand Up @@ -445,11 +454,11 @@ export default function CalendarComponent({
// Prepare courses for timeline
useEffect(() => {
const courses = prepareCoursesForTimeline(
timetables,
visibleTimetables,
currentTimetableIndex,
);
setCoursesForTimeline(courses);
}, [timetables, currentTimetableIndex]);
}, [visibleTimetables, currentTimetableIndex]);

return (
<div id="Calendar">
Expand All @@ -472,7 +481,7 @@ export default function CalendarComponent({
handleNext={handleNext}
handleLast={handleLast}
currentTimetableIndex={currentTimetableIndex}
timetables={timetables}
timetables={visibleTimetables}
selectedDuration={selectedDuration}
setSelectedDuration={setSelectedDuration}
durations={durations}
Expand All @@ -485,6 +494,7 @@ export default function CalendarComponent({
calendarRef,
showWeekends,
events,
handleDatesSet,
handleEventClick,
handleSelect,
handleSelectAllow,
Expand Down
30 changes: 18 additions & 12 deletions src/components/generator/Calendar/CourseTimelineComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,15 @@ export default function CourseTimelineComponent({
course.string ||
`${course.code} ${course.section || ""} (${course.duration || ""})`;

let startDate = course.startDate
? parseStartDate(course.startDate)
: null;
let endDate = course.endDate ? parseEndDate(course.endDate) : null;
let startDate = course.startDate;
let endDate = course.endDate;

if (startDate && !(startDate instanceof Date)) {
startDate = parseStartDate(startDate);
}
if (endDate && !(endDate instanceof Date)) {
endDate = parseEndDate(endDate);
}

if (
!startDate ||
Expand All @@ -268,6 +273,7 @@ export default function CourseTimelineComponent({
}

return {
id: `${courseName}-${course.duration || "nodur"}-${startDate.getTime()}`,
code: courseName,
fullName: courseString,
startDate,
Expand Down Expand Up @@ -525,7 +531,7 @@ export default function CourseTimelineComponent({

{/* Course bars with labels */}
{coursesWithDates.map((course, index) => (
<React.Fragment key={course.code}>
<React.Fragment key={course.id}>
{/* Course label */}
<Tooltip
title={`${course.fullName}: ${formatDate(
Expand All @@ -539,7 +545,7 @@ export default function CourseTimelineComponent({
<Typography
variant="caption"
onClick={(event) => handleCourseClick(course, event)}
onMouseEnter={() => setHoveredCourse(course.code)}
onMouseEnter={() => setHoveredCourse(course.id)}
onMouseLeave={() => setHoveredCourse(null)}
sx={{
position: "absolute",
Expand All @@ -566,7 +572,7 @@ export default function CourseTimelineComponent({
cursor: "pointer",
transition: "all 0.15s cubic-bezier(0.4, 0, 0.2, 1)",
boxShadow:
hoveredCourse === course.code
hoveredCourse === course.id
? (theme) =>
`0 2px 4px ${alpha(
course.color,
Expand Down Expand Up @@ -597,7 +603,7 @@ export default function CourseTimelineComponent({
TransitionProps={{ timeout: 300 }}
>
<Box
onMouseEnter={() => setHoveredCourse(course.code)}
onMouseEnter={() => setHoveredCourse(course.id)}
onMouseLeave={() => setHoveredCourse(null)}
onClick={(event) => handleCourseClick(course, event)}
sx={{
Expand All @@ -607,14 +613,14 @@ export default function CourseTimelineComponent({
width: `${course.widthPercent}%`,
height: "12px",
backgroundColor: course.color,
opacity: hoveredCourse === course.code ? 1 : 0.85,
opacity: hoveredCourse === course.id ? 1 : 0.85,
borderRadius: "6px",
cursor: "pointer",
transition: "all 0.15s cubic-bezier(0.4, 0, 0.2, 1)",
transform:
hoveredCourse === course.code ? "translateY(-1px)" : "none",
hoveredCourse === course.id ? "translateY(-1px)" : "none",
boxShadow:
hoveredCourse === course.code
hoveredCourse === course.id
? (theme) =>
`0 2px 4px ${alpha(
course.color,
Expand All @@ -629,7 +635,7 @@ export default function CourseTimelineComponent({
transform: "translateY(0px) scale(0.98)",
transition: "all 0.1s cubic-bezier(0.4, 0, 0.2, 1)",
},
zIndex: hoveredCourse === course.code ? 3 : 2,
zIndex: hoveredCourse === course.id ? 3 : 2,
}}
/>
</Tooltip>
Expand Down
Loading