From b53fac176b98f0b2298a5916434f97993ed302db Mon Sep 17 00:00:00 2001 From: xavilien <“xavilien@gmail.com”> Date: Wed, 9 Oct 2024 12:18:43 -0400 Subject: [PATCH 01/39] Calendar MVP --- apps/frontend/package.json | 2 + apps/frontend/src/app/user.ts | 12 +- .../src/components/ScheduleCalendar.tsx | 207 ++++++++++++++++++ .../src/components/SectionSelector.tsx | 207 ++++++++++++++++++ apps/frontend/src/pages/schedules.tsx | 31 ++- 5 files changed, 448 insertions(+), 11 deletions(-) create mode 100644 apps/frontend/src/components/ScheduleCalendar.tsx create mode 100644 apps/frontend/src/components/SectionSelector.tsx diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 130658a..0d160ae 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -32,6 +32,7 @@ "passlink": "^1.1.0", "posthog-js": "^1.181.0", "react": "^18.2.0", + "react-big-calendar": "^1.15.0", "react-dom": "^18.2.0", "react-headless-pagination": "^0.1.0", "react-hot-toast": "^2.2.0", @@ -50,6 +51,7 @@ "@types/jest": "^27.0.1", "@types/node": "^16.9.1", "@types/react": "^17.0.21", + "@types/react-big-calendar": "^1.8.12", "@types/react-dom": "^17.0.9", "@types/react-redux": "^7.1.18", "autoprefixer": "^10.4.0", diff --git a/apps/frontend/src/app/user.ts b/apps/frontend/src/app/user.ts index 5d9d95b..3d16a5c 100644 --- a/apps/frontend/src/app/user.ts +++ b/apps/frontend/src/app/user.ts @@ -24,6 +24,8 @@ export interface UserState { }; selectedSchool: string; selectedTags: string[]; + selectedSemester: string; + selectedSessions: {}; } const initialState: UserState = { @@ -47,6 +49,8 @@ const initialState: UserState = { }, selectedSchool: "SCS", selectedTags: [], + selectedSemester: "", + selectedSessions: {}, }; export const userSlice = createSlice({ @@ -102,7 +106,13 @@ export const userSlice = createSlice({ }, setSelectedTags: (state, action: PayloadAction) => { state.selectedTags = action.payload; - } + }, + setSelectedSemester: (state, action: PayloadAction) => { + state.selectedSemester = action.payload; + }, + setSelectedSessions: (state, action: PayloadAction<{}>) => { + state.selectedSessions = action.payload + }, }, }); diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx new file mode 100644 index 0000000..90413bc --- /dev/null +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -0,0 +1,207 @@ +import React, { useMemo } from "react"; +import { Calendar, DateLocalizer, momentLocalizer } from "react-big-calendar"; +import PropTypes from 'prop-types' +import * as dates from 'date-arithmetic' +import TimeGrid from 'react-big-calendar/lib/TimeGrid' +import Toolbar from 'react-big-calendar/lib/Toolbar' +import style from "react-big-calendar/lib/css/react-big-calendar.css"; +import moment from "moment"; +import { useAppDispatch, useAppSelector } from "~/app/hooks"; +import useDeepCompareEffect from "use-deep-compare-effect"; +import { fetchCourseInfos } from "~/app/api/course"; +import { fetchFCEInfosByCourse } from "~/app/api/fce"; +import { selectCourseResults } from "~/app/cache"; +import { Course } from "~/app/types"; +import {sessionToString} from "~/app/utils"; + +const localizer = momentLocalizer(moment); + +function Week({ + date, + max = localizer.endOf(new Date(), 'day'), + min = localizer.startOf(new Date(), 'day'), + scrollToTime = localizer.startOf(new Date(), 'day'), + ...props +} : { date: Date, max: Date, min: Date, scrollToTime: Date }) { + const currRange = useMemo( + () => Week.range(date, { localizer }), + [date, localizer] + ) + + return ( + + ) +} + +Week.propTypes = { + date: PropTypes.instanceOf(Date).isRequired, + localizer: PropTypes.object, + max: PropTypes.instanceOf(Date), + min: PropTypes.instanceOf(Date), + scrollToTime: PropTypes.instanceOf(Date), +} + +Week.range = (date: Date, p: { localizer: DateLocalizer }) => { + const start = new Date(2024, 8, 30) + const end = dates.add(start, 4, 'day') + + let current = start + const range = [] + + while (localizer.lte(current, end, 'day')) { + range.push(current) + current = localizer.add(current, 1, 'day') + } + + return range +} + +Week.title = () => "Week" + +class CustomToolbar extends Toolbar { + render() { + return ""; + } +} + +const getTime = (day: number, time: string) => { + let [hour, minute] = time.split(":"); + if (hour && time.slice(-2) === "PM") { + hour = (parseInt(hour) + 12).toString(); + } + return new Date(2024, 8, 29 + day, parseInt(hour || "0"), parseInt(minute || "0")); +} + +interface courseSessions { + [courseID: string]: { + [sessionType: string]: string; + }; +} + +const getTimes = (courseID: string, sessionType: string, sessionTimes) => { + const times = []; + for (const sessionTime of sessionTimes || []) { + for (const day of sessionTime.days || []) { + times.push({ + title: `${courseID} ${sessionType}`, + start: getTime(day, sessionTime.begin || ""), + end: getTime(day, sessionTime.end || ""), + }); + } + + } + return times; +} + +interface Event { + title: string; + start: Date; + end: Date; +} + +const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSessions: courseSessions) => { + let events: Event[] = []; + + const filteredCourses = CourseDetails.filter((course) => { + const schedules = course.schedules; + if (schedules) { + return schedules.some(sched => sessionToString(sched) === selectedSemester); + } + }); + + const selectedLectures = filteredCourses.flatMap(course => { + const lecture = course.schedules?.find(sched => sessionToString(sched) === selectedSemester) + ?.lectures.find(lecture => lecture.name === selectedSessions[course.courseID]?.Lecture); + return { + courseID: course.courseID, + ...lecture, + } + }).filter(x => x !== undefined); + +events = events.concat(selectedLectures.flatMap(lecture => getTimes(lecture.courseID, lecture.name || "Lecture", lecture.times))); + + const selectedSections = filteredCourses.flatMap(course => { + const section = course.schedules?.find(sched => sessionToString(sched) === selectedSemester) + ?.sections.find(section => section.name === selectedSessions[course.courseID]?.Section); + return { + courseID: course.courseID, + ...section, + } + }).filter(x => x !== undefined); + + events = events.concat(selectedSections.flatMap(section => getTimes(section.courseID, `Section ${section.name || ""}`, section.times))); + + return events; +} + +interface Props { + courseIDs: string[]; +} + +const ScheduleCalendar = ({ courseIDs }: Props) =>{ + const loggedIn = useAppSelector((state) => state.user.loggedIn); + const selectedSemester = useAppSelector((state) => state.user.selectedSemester); + const selectedSessions = useAppSelector((state) => state.user.selectedSessions); + const dispatch = useAppDispatch(); + + useDeepCompareEffect(() => { + if (courseIDs) { + void dispatch(fetchCourseInfos(courseIDs)); + if (loggedIn) void dispatch(fetchFCEInfosByCourse({ courseIDs })); + } + }, [courseIDs]); + + const CourseDetails = useAppSelector(selectCourseResults(courseIDs)).filter(x => x !== undefined); + + const events = getEvents(CourseDetails, selectedSemester, selectedSessions); + + const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + + const { defaultDate, views, components, formats } = useMemo( + () => ({ + defaultDate: new Date(2015, 3, 1), + views: { + week: Week, + }, + components: { + toolbar: CustomToolbar, + header: ({ date } : { date: Date }) => {days[moment(date).day()]}, + }, + formats: { + eventTimeRangeFormat: () => { + return "" + }, + }, + }), + [] + ) + + return ( +
+ +
+ ) +} + +export default ScheduleCalendar; \ No newline at end of file diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx new file mode 100644 index 0000000..a8de1d4 --- /dev/null +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -0,0 +1,207 @@ +import React, { Dispatch, SetStateAction } from "react"; +import {useAppDispatch, useAppSelector} from "~/app/hooks"; +import { ChevronUpDownIcon } from "@heroicons/react/24/outline"; +import { Listbox } from "@headlessui/react"; +import {classNames, sessionToString} from "~/app/utils"; +import { CheckIcon } from "@heroicons/react/20/solid"; +import {selectCourseResults} from "~/app/cache"; +import { Lecture, Section } from "~/app/types"; +import { userSlice } from "~/app/user"; + +interface Props { + courseIDs: string[]; +} + +interface courseSessions { + [courseID: string]: { + [sessionType: string]: string; + }; +} + +const getTimes = (courseID: string, sessionType: string, sessions: Lecture[] | Section[], selectedSessions: courseSessions, dispatch: Dispatch>) => { + const selectedSession = selectedSessions[courseID]?.[sessionType] || ""; + + return ( + { + dispatch(userSlice.actions.setSelectedSessions({ + ...selectedSessions, + [courseID]: { + ...selectedSessions[courseID], + [sessionType]: payload + } + })); + }}> + + {sessionType} + + + + {selectedSession.length === 0 ? ( + Select Lecture + ) : ( + + {selectedSession} + + )} + + + + + +
+ + {sessions.map((lecture) => ( + { + return classNames( + "relative cursor-pointer select-none py-2 pl-3 pr-9 focus:outline-none ", + active ? "bg-indigo-600 text-gray-600" : "text-gray-900" + ); + }} + > + {({selected}) => ( + <> + + + {lecture.name} + + + {selected && ( + + + + )} + + )} + + ))} + +
+
+ ) +} + +const SectionSelector = ({ courseIDs }: Props) => { + const dispatch = useAppDispatch(); + const CourseDetails = useAppSelector(selectCourseResults(courseIDs)).filter(x => x !== undefined); + const selectedSemester = useAppSelector((state) => state.user.selectedSemester); + const selectedSessions = useAppSelector((state) => state.user.selectedSessions); + + const semesters = [...new Set(CourseDetails.flatMap(course => { + const schedules = course.schedules; + if (schedules) { + return schedules.map(schedule => sessionToString(schedule)); + } + }))]; + + return ( +
+
+
Schedule Calendar
+
+ +
+ { + dispatch(userSlice.actions.setSelectedSemester(payload)); + const courseIDs = CourseDetails.filter((course) => course.schedules?.some(sched => sessionToString(sched) === payload)).map(course => course.courseID); + dispatch(userSlice.actions.setSelectedSessions(courseIDs.reduce((acc: courseSessions, courseID) => { + acc[courseID] = {Lecture: "", Section: ""}; + return acc; + }, {}))); + }}> + + Semester + + + + {selectedSemester.length === 0 ? ( + Select Semester + ) : ( + + {selectedSemester} + + )} + + + + + +
+ + {semesters.map((semester) => ( + { + return classNames( + "relative cursor-pointer select-none py-2 pl-3 pr-9 focus:outline-none ", + active ? "bg-indigo-600 text-gray-600" : "text-gray-900" + ); + }} + > + {({selected}) => ( + <> + + + {semester} + + + {selected && ( + + + + )} + + )} + + ))} + +
+
+
+
+ { + CourseDetails.filter((course) => course.schedules?.some(sched => sessionToString(sched) === selectedSemester)).map((course) => { + const schedule = course.schedules?.find(sched => sessionToString(sched) === selectedSemester); + const courseID = course.courseID; + + console.log("Schedule", schedule) + console.log("Lectures", schedule?.lectures) + + return ( +
+
{courseID}
+ {schedule?.lectures && getTimes(courseID, "Lecture", schedule.lectures, selectedSessions, dispatch)} + {schedule?.sections && getTimes(courseID, "Section", schedule.sections, selectedSessions, dispatch)} +
+ ); + }) + } +
+
+ ); +}; + +export default SectionSelector; \ No newline at end of file diff --git a/apps/frontend/src/pages/schedules.tsx b/apps/frontend/src/pages/schedules.tsx index c50679a..0108b3a 100644 --- a/apps/frontend/src/pages/schedules.tsx +++ b/apps/frontend/src/pages/schedules.tsx @@ -9,10 +9,14 @@ import ScheduleData from "~/components/ScheduleData"; import { selectCoursesInActiveSchedule } from "~/app/userSchedules"; import { Page } from "~/components/Page"; import Loading from "~/components/Loading"; +import ScheduleCalendar from "~/components/ScheduleCalendar"; +import SectionSelector from "~/components/SectionSelector"; const SchedulePage: NextPage = () => { const scheduled = useAppSelector(selectCoursesInActiveSchedule); + const view = "cal"; + return ( { - - {/* This are the elements to show when we have no results to show. */} - {scheduled.length ? ( // We have things in our schedule, but have no results => still loading - + { + view === "sched" ? ( + + {/* This are the elements to show when we have no results to show. */} + {scheduled.length ? ( // We have things in our schedule, but have no results => still loading + + ) : ( + // We haven't added anything to the schedule yet +
+ Nothing in your schedule yet! +
+ )} +
) : ( - // We haven't added anything to the schedule yet -
- Nothing in your schedule yet! -
- )} -
+ + ) + } } sidebar={ <> + } /> From a78ee9d6b55d7170da1c7092b62ef9ba07a2dc87 Mon Sep 17 00:00:00 2001 From: xavilien <“xavilien@gmail.com”> Date: Wed, 9 Oct 2024 15:25:14 -0400 Subject: [PATCH 02/39] Add tabs for schedule view --- apps/frontend/src/app/constants.ts | 5 ++++- apps/frontend/src/app/user.ts | 5 +++++ .../frontend/src/components/ScheduleSearch.tsx | 18 ++++++++++++++++++ apps/frontend/src/pages/schedules.tsx | 14 +++++--------- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/apps/frontend/src/app/constants.ts b/apps/frontend/src/app/constants.ts index 8ecf806..2582e36 100644 --- a/apps/frontend/src/app/constants.ts +++ b/apps/frontend/src/app/constants.ts @@ -135,4 +135,7 @@ export const GENED_SOURCES = { Dietrich: "https://www.cmu.edu/dietrich/gened/fall-2021-and-beyond/course-options/index.html", }; -export const STALE_TIME = 1000 * 60 * 60 * 24; // 1 day \ No newline at end of file +export const STALE_TIME = 1000 * 60 * 60 * 24; // 1 day + +export const CAL_VIEW = "cal"; +export const SCHED_VIEW = "sched"; \ No newline at end of file diff --git a/apps/frontend/src/app/user.ts b/apps/frontend/src/app/user.ts index 3d16a5c..726d6ec 100644 --- a/apps/frontend/src/app/user.ts +++ b/apps/frontend/src/app/user.ts @@ -26,6 +26,7 @@ export interface UserState { selectedTags: string[]; selectedSemester: string; selectedSessions: {}; + scheduleView: string; } const initialState: UserState = { @@ -51,6 +52,7 @@ const initialState: UserState = { selectedTags: [], selectedSemester: "", selectedSessions: {}, + scheduleView: "cal", }; export const userSlice = createSlice({ @@ -113,6 +115,9 @@ export const userSlice = createSlice({ setSelectedSessions: (state, action: PayloadAction<{}>) => { state.selectedSessions = action.payload }, + setScheduleView: (state, action: PayloadAction) => { + state.scheduleView = action.payload; + }, }, }); diff --git a/apps/frontend/src/components/ScheduleSearch.tsx b/apps/frontend/src/components/ScheduleSearch.tsx index 964de32..b3a2a79 100644 --- a/apps/frontend/src/components/ScheduleSearch.tsx +++ b/apps/frontend/src/components/ScheduleSearch.tsx @@ -16,6 +16,8 @@ import { userSchedulesSlice, } from "~/app/userSchedules"; import { useFetchAllCourses } from "~/app/api/course"; +import { userSlice } from "~/app/user"; +import { CAL_VIEW, SCHED_VIEW } from "~/app/constants"; type selectedItem = { courseID: string; @@ -247,6 +249,7 @@ const ScheduleSearch = () => { const dispatch = useAppDispatch(); const savedSchedules = useAppSelector((state) => state.schedules.saved); const active = useAppSelector((state) => state.schedules.active); + const scheduleView = useAppSelector((state) => state.user.scheduleView); return (
@@ -269,6 +272,21 @@ const ScheduleSearch = () => {
)} +
+ + +
+ { dispatch( diff --git a/apps/frontend/src/pages/schedules.tsx b/apps/frontend/src/pages/schedules.tsx index 0108b3a..49a5ea6 100644 --- a/apps/frontend/src/pages/schedules.tsx +++ b/apps/frontend/src/pages/schedules.tsx @@ -11,12 +11,11 @@ import { Page } from "~/components/Page"; import Loading from "~/components/Loading"; import ScheduleCalendar from "~/components/ScheduleCalendar"; import SectionSelector from "~/components/SectionSelector"; +import {CAL_VIEW, SCHED_VIEW} from "~/app/constants"; const SchedulePage: NextPage = () => { const scheduled = useAppSelector(selectCoursesInActiveSchedule); - - const view = "cal"; - + const scheduleView = useAppSelector((state) => state.user.scheduleView); return ( { - { - view === "sched" ? ( + {scheduleView === SCHED_VIEW && ( {/* This are the elements to show when we have no results to show. */} {scheduled.length ? ( // We have things in our schedule, but have no results => still loading @@ -39,10 +37,8 @@ const SchedulePage: NextPage = () => { )} - ) : ( - - ) - } + )} + {scheduleView === CAL_VIEW && ()} } sidebar={ From 81d87d7647c6086ed7588729db4c00204086f0f9 Mon Sep 17 00:00:00 2001 From: xavilien <“xavilien@gmail.com”> Date: Wed, 9 Oct 2024 15:35:28 -0400 Subject: [PATCH 03/39] Fixed bug in getting time --- apps/frontend/src/components/ScheduleCalendar.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index 90413bc..84930f1 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -76,7 +76,7 @@ class CustomToolbar extends Toolbar { const getTime = (day: number, time: string) => { let [hour, minute] = time.split(":"); - if (hour && time.slice(-2) === "PM") { + if (hour && time.slice(-2) === "PM" && time.slice(0, 2) !== "12") { hour = (parseInt(hour) + 12).toString(); } return new Date(2024, 8, 29 + day, parseInt(hour || "0"), parseInt(minute || "0")); @@ -98,7 +98,6 @@ const getTimes = (courseID: string, sessionType: string, sessionTimes) => { end: getTime(day, sessionTime.end || ""), }); } - } return times; } @@ -128,7 +127,7 @@ const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSe } }).filter(x => x !== undefined); -events = events.concat(selectedLectures.flatMap(lecture => getTimes(lecture.courseID, lecture.name || "Lecture", lecture.times))); + events = events.concat(selectedLectures.flatMap(lecture => getTimes(lecture.courseID, lecture.name || "Lecture", lecture.times))); const selectedSections = filteredCourses.flatMap(course => { const section = course.schedules?.find(sched => sessionToString(sched) === selectedSemester) @@ -141,6 +140,8 @@ events = events.concat(selectedLectures.flatMap(lecture => getTimes(lecture.cour events = events.concat(selectedSections.flatMap(section => getTimes(section.courseID, `Section ${section.name || ""}`, section.times))); + console.log(events) + return events; } From fa9efb4e9b1dfe19d309fca31b842a373ef67fb8 Mon Sep 17 00:00:00 2001 From: xavilien <“xavilien@gmail.com”> Date: Wed, 9 Oct 2024 16:16:30 -0400 Subject: [PATCH 04/39] Temporary fix to scrolling --- apps/frontend/src/components/SectionSelector.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index a8de1d4..7b4e281 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -181,7 +181,7 @@ const SectionSelector = ({ courseIDs }: Props) => { -
+
{ CourseDetails.filter((course) => course.schedules?.some(sched => sessionToString(sched) === selectedSemester)).map((course) => { const schedule = course.schedules?.find(sched => sessionToString(sched) === selectedSemester); @@ -191,7 +191,7 @@ const SectionSelector = ({ courseIDs }: Props) => { console.log("Lectures", schedule?.lectures) return ( -
+
{courseID}
{schedule?.lectures && getTimes(courseID, "Lecture", schedule.lectures, selectedSessions, dispatch)} {schedule?.sections && getTimes(courseID, "Section", schedule.sections, selectedSessions, dispatch)} From 2c2a62c0b96b447a6460aa3d02be3f4e59d4fedf Mon Sep 17 00:00:00 2001 From: xavilien <“xavilien@gmail.com”> Date: Wed, 9 Oct 2024 16:45:36 -0400 Subject: [PATCH 05/39] Added colors --- apps/frontend/src/app/constants.ts | 14 +++++++- apps/frontend/src/app/types.ts | 16 +++++---- .../src/components/ScheduleCalendar.tsx | 34 +++++++++++++------ .../src/components/SectionSelector.tsx | 12 +++---- 4 files changed, 50 insertions(+), 26 deletions(-) diff --git a/apps/frontend/src/app/constants.ts b/apps/frontend/src/app/constants.ts index 2582e36..243a45d 100644 --- a/apps/frontend/src/app/constants.ts +++ b/apps/frontend/src/app/constants.ts @@ -138,4 +138,16 @@ export const GENED_SOURCES = { export const STALE_TIME = 1000 * 60 * 60 * 24; // 1 day export const CAL_VIEW = "cal"; -export const SCHED_VIEW = "sched"; \ No newline at end of file +export const SCHED_VIEW = "sched"; +const CALENDAR_COLORS = [ + "#FFB3BA", // Light Red + "#FFDFBA", // Light Orange + "#FFFFBA", // Light Yellow + "#BAFFC9", // Light Green + "#BAE1FF", // Light Blue + "#D4BAFF", // Light Purple + "#FFC4E1", // Light Pink + "#C4E1FF", // Light Sky Blue + "#E1FFC4", // Light Lime + "#FFF4BA" // Light Cream +];export const GET_CALENDAR_COLOR = (i: number) => CALENDAR_COLORS[i % CALENDAR_COLORS.length] || ""; \ No newline at end of file diff --git a/apps/frontend/src/app/types.ts b/apps/frontend/src/app/types.ts index 75096ec..5fc1ba2 100644 --- a/apps/frontend/src/app/types.ts +++ b/apps/frontend/src/app/types.ts @@ -32,17 +32,19 @@ export interface Course { fces?: FCE[]; } +export interface Time { + days: number[]; + begin: string; + end: string; + building: string; + room: string; +} + interface Lesson { instructors: string[]; name: string; location: string; - times: { - days: number[]; - begin: string; - end: string; - building: string; - room: string; - }[]; + times: Time[]; } export type Lecture = Lesson; diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index 84930f1..09214f4 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -11,7 +11,7 @@ import useDeepCompareEffect from "use-deep-compare-effect"; import { fetchCourseInfos } from "~/app/api/course"; import { fetchFCEInfosByCourse } from "~/app/api/fce"; import { selectCourseResults } from "~/app/cache"; -import { Course } from "~/app/types"; +import {Course, Time} from "~/app/types"; import {sessionToString} from "~/app/utils"; const localizer = momentLocalizer(moment); @@ -88,7 +88,7 @@ interface courseSessions { }; } -const getTimes = (courseID: string, sessionType: string, sessionTimes) => { +const getTimes = (courseID: string, sessionType: string, sessionTimes: Time[], color: string) => { const times = []; for (const sessionTime of sessionTimes || []) { for (const day of sessionTime.days || []) { @@ -96,6 +96,7 @@ const getTimes = (courseID: string, sessionType: string, sessionTimes) => { title: `${courseID} ${sessionType}`, start: getTime(day, sessionTime.begin || ""), end: getTime(day, sessionTime.end || ""), + color, }); } } @@ -106,6 +107,7 @@ interface Event { title: string; start: Date; end: Date; + color: string; } const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSessions: courseSessions) => { @@ -118,30 +120,35 @@ const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSe } }); + console.log(selectedSessions) + const selectedLectures = filteredCourses.flatMap(course => { const lecture = course.schedules?.find(sched => sessionToString(sched) === selectedSemester) ?.lectures.find(lecture => lecture.name === selectedSessions[course.courseID]?.Lecture); return { courseID: course.courseID, + color: selectedSessions[course.courseID]?.Color || "", ...lecture, } }).filter(x => x !== undefined); - events = events.concat(selectedLectures.flatMap(lecture => getTimes(lecture.courseID, lecture.name || "Lecture", lecture.times))); + events = events.concat(selectedLectures.flatMap(lecture => { + if (lecture.times) return getTimes(lecture.courseID, lecture.name || "Lecture", lecture.times, lecture.color); + }).filter(x => x !== undefined)); const selectedSections = filteredCourses.flatMap(course => { const section = course.schedules?.find(sched => sessionToString(sched) === selectedSemester) ?.sections.find(section => section.name === selectedSessions[course.courseID]?.Section); return { courseID: course.courseID, + color: selectedSessions[course.courseID]?.Color || "", ...section, } }).filter(x => x !== undefined); - events = events.concat(selectedSections.flatMap(section => getTimes(section.courseID, `Section ${section.name || ""}`, section.times))); - - console.log(events) - + events = events.concat(selectedSections.flatMap(section => { + if (section.times) return getTimes(section.courseID, `Section ${section.name || ""}`, section.times, section.color); + }).filter(x => x !== undefined)); return events; } @@ -168,7 +175,7 @@ const ScheduleCalendar = ({ courseIDs }: Props) =>{ const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - const { defaultDate, views, components, formats } = useMemo( + const { defaultDate, views, components, formats, eventPropGetter } = useMemo( () => ({ defaultDate: new Date(2015, 3, 1), views: { @@ -183,9 +190,13 @@ const ScheduleCalendar = ({ courseIDs }: Props) =>{ return "" }, }, - }), - [] - ) + eventPropGetter: (event: Event) => ({ + style: { + color: "#030712", + backgroundColor: event.color, + }, + }), + }), []); return (
@@ -198,6 +209,7 @@ const ScheduleCalendar = ({ courseIDs }: Props) =>{ views={views} components={components} formats={formats} + eventPropGetter={eventPropGetter} min={new Date(0, 0, 0, 8, 0)} max={new Date(0, 0, 0, 22, 0)} /> diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index 7b4e281..0283a9a 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -7,6 +7,7 @@ import { CheckIcon } from "@heroicons/react/20/solid"; import {selectCourseResults} from "~/app/cache"; import { Lecture, Section } from "~/app/types"; import { userSlice } from "~/app/user"; +import {GET_CALENDAR_COLOR} from "~/app/constants"; interface Props { courseIDs: string[]; @@ -35,7 +36,7 @@ const getTimes = (courseID: string, sessionType: string, sessions: Lecture[] | S {sessionType} + className="relative mt-2 w-full cursor-default rounded border py-1 pl-1 pr-10 text-left transition duration-150 ease-in-out border-black sm:text-sm sm:leading-5"> {selectedSession.length === 0 ? ( Select Lecture @@ -116,8 +117,8 @@ const SectionSelector = ({ courseIDs }: Props) => { { dispatch(userSlice.actions.setSelectedSemester(payload)); const courseIDs = CourseDetails.filter((course) => course.schedules?.some(sched => sessionToString(sched) === payload)).map(course => course.courseID); - dispatch(userSlice.actions.setSelectedSessions(courseIDs.reduce((acc: courseSessions, courseID) => { - acc[courseID] = {Lecture: "", Section: ""}; + dispatch(userSlice.actions.setSelectedSessions(courseIDs.reduce((acc: courseSessions, courseID, i: number) => { + acc[courseID] = {Lecture: "", Section: "", Color: GET_CALENDAR_COLOR(i)}; return acc; }, {}))); }}> @@ -187,11 +188,8 @@ const SectionSelector = ({ courseIDs }: Props) => { const schedule = course.schedules?.find(sched => sessionToString(sched) === selectedSemester); const courseID = course.courseID; - console.log("Schedule", schedule) - console.log("Lectures", schedule?.lectures) - return ( -
+
{courseID}
{schedule?.lectures && getTimes(courseID, "Lecture", schedule.lectures, selectedSessions, dispatch)} {schedule?.sections && getTimes(courseID, "Section", schedule.sections, selectedSessions, dispatch)} From da02d8860f3b810051dfe68207211458e1191d9c Mon Sep 17 00:00:00 2001 From: xavilien <“xavilien@gmail.com”> Date: Wed, 9 Oct 2024 16:49:24 -0400 Subject: [PATCH 06/39] Added colors --- apps/frontend/src/app/user.ts | 7 ++++++- apps/frontend/src/components/SectionSelector.tsx | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/app/user.ts b/apps/frontend/src/app/user.ts index 726d6ec..daba962 100644 --- a/apps/frontend/src/app/user.ts +++ b/apps/frontend/src/app/user.ts @@ -25,7 +25,12 @@ export interface UserState { selectedSchool: string; selectedTags: string[]; selectedSemester: string; - selectedSessions: {}; + selectedSessions: { + [courseID: string]: { + [sessionType: string]: string; + Color: string; + }; + }; scheduleView: string; } diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index 0283a9a..0fbcc8d 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -189,7 +189,7 @@ const SectionSelector = ({ courseIDs }: Props) => { const courseID = course.courseID; return ( -
+
{courseID}
{schedule?.lectures && getTimes(courseID, "Lecture", schedule.lectures, selectedSessions, dispatch)} {schedule?.sections && getTimes(courseID, "Section", schedule.sections, selectedSessions, dispatch)} From 4341fa82c3933eeff4885d1805657e78b2946188 Mon Sep 17 00:00:00 2001 From: xavilien <“xavilien@gmail.com”> Date: Thu, 10 Oct 2024 23:36:03 -0400 Subject: [PATCH 07/39] Shifted position of tabs --- .../src/components/ScheduleSearch.tsx | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/apps/frontend/src/components/ScheduleSearch.tsx b/apps/frontend/src/components/ScheduleSearch.tsx index b3a2a79..bd594ca 100644 --- a/apps/frontend/src/components/ScheduleSearch.tsx +++ b/apps/frontend/src/components/ScheduleSearch.tsx @@ -256,9 +256,9 @@ const ScheduleSearch = () => {
{active !== null && (
- + dispatch( @@ -269,24 +269,21 @@ const ScheduleSearch = () => { } placeholder="Schedule Name" /> + +
)} -
- - -
- { dispatch( From c3da9e8932318827cf8edcfdcaf0ae181013acf8 Mon Sep 17 00:00:00 2001 From: xavilien <“xavilien@gmail.com”> Date: Fri, 11 Oct 2024 01:05:56 -0400 Subject: [PATCH 08/39] Refactored handling of selecting sessions --- apps/frontend/src/app/constants.ts | 4 +- apps/frontend/src/app/types.ts | 2 +- apps/frontend/src/app/user.ts | 15 ---- apps/frontend/src/app/userSchedules.ts | 82 ++++++++++++++----- apps/frontend/src/app/utils.tsx | 32 ++++++++ .../src/components/ScheduleCalendar.tsx | 9 +- .../src/components/ScheduleSearch.tsx | 3 +- .../src/components/SectionSelector.tsx | 58 ++++++------- 8 files changed, 128 insertions(+), 77 deletions(-) diff --git a/apps/frontend/src/app/constants.ts b/apps/frontend/src/app/constants.ts index 243a45d..7481f7e 100644 --- a/apps/frontend/src/app/constants.ts +++ b/apps/frontend/src/app/constants.ts @@ -150,4 +150,6 @@ const CALENDAR_COLORS = [ "#C4E1FF", // Light Sky Blue "#E1FFC4", // Light Lime "#FFF4BA" // Light Cream -];export const GET_CALENDAR_COLOR = (i: number) => CALENDAR_COLORS[i % CALENDAR_COLORS.length] || ""; \ No newline at end of file +]; + +export const GET_CALENDAR_COLOR = (i: number) => CALENDAR_COLORS[i % CALENDAR_COLORS.length] || ""; \ No newline at end of file diff --git a/apps/frontend/src/app/types.ts b/apps/frontend/src/app/types.ts index 5fc1ba2..ae24d43 100644 --- a/apps/frontend/src/app/types.ts +++ b/apps/frontend/src/app/types.ts @@ -1,4 +1,4 @@ -export type Semester = "fall" | "spring" | "summer"; +export type Semester = "fall" | "spring" | "summer" | ""; export type SummerSession = | "summer one" | "summer two" diff --git a/apps/frontend/src/app/user.ts b/apps/frontend/src/app/user.ts index daba962..362dbcb 100644 --- a/apps/frontend/src/app/user.ts +++ b/apps/frontend/src/app/user.ts @@ -24,13 +24,6 @@ export interface UserState { }; selectedSchool: string; selectedTags: string[]; - selectedSemester: string; - selectedSessions: { - [courseID: string]: { - [sessionType: string]: string; - Color: string; - }; - }; scheduleView: string; } @@ -55,8 +48,6 @@ const initialState: UserState = { }, selectedSchool: "SCS", selectedTags: [], - selectedSemester: "", - selectedSessions: {}, scheduleView: "cal", }; @@ -114,12 +105,6 @@ export const userSlice = createSlice({ setSelectedTags: (state, action: PayloadAction) => { state.selectedTags = action.payload; }, - setSelectedSemester: (state, action: PayloadAction) => { - state.selectedSemester = action.payload; - }, - setSelectedSessions: (state, action: PayloadAction<{}>) => { - state.selectedSessions = action.payload - }, setScheduleView: (state, action: PayloadAction) => { state.scheduleView = action.payload; }, diff --git a/apps/frontend/src/app/userSchedules.ts b/apps/frontend/src/app/userSchedules.ts index 78449cc..386c1cb 100644 --- a/apps/frontend/src/app/userSchedules.ts +++ b/apps/frontend/src/app/userSchedules.ts @@ -1,15 +1,24 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { addToSet, removeFromSet } from "./utils"; +import { addToSet, removeFromSet, sessionToString } from "./utils"; import { Session } from "./types"; import { v4 as uuidv4 } from "uuid"; import { RootState } from "./store"; +import { GET_CALENDAR_COLOR } from "~/app/constants"; + +export interface CourseSessions { + [courseID: string]: { + [sessionType: string]: string; + Color: string; + }; +} export interface UserSchedule { name: string; courses: string[]; selected: string[]; id: string; - session?: Session; + session: Session; + courseSessions: CourseSessions; } export interface UserSchedulesState { @@ -22,6 +31,23 @@ const initialState: UserSchedulesState = { saved: {}, }; +const getNewUserSchedule = (courseIDs: string[]) : UserSchedule => { + return { + name: "My Schedule", + courses: courseIDs, + selected: courseIDs, + id: uuidv4(), + session: { + year: "", + semester: "", + }, + courseSessions: courseIDs.reduce((acc: CourseSessions, courseID, i: number) => { + acc[courseID] = {Lecture: "", Section: "", Color: GET_CALENDAR_COLOR(i)}; + return acc; + }, {}), + }; +} + export const userSchedulesSlice = createSlice({ name: "userSchedules", initialState, @@ -32,12 +58,7 @@ export const userSchedulesSlice = createSlice({ addCourseToActiveSchedule: (state, action: PayloadAction) => { if (state.active === null) { const newId = uuidv4(); - state.saved[newId] = { - name: "My Schedule", - courses: [], - selected: [], - id: newId, - }; + state.saved[newId] = getNewUserSchedule([]); state.active = newId; } state.saved[state.active].courses = addToSet( @@ -48,6 +69,7 @@ export const userSchedulesSlice = createSlice({ state.saved[state.active].selected, action.payload ); + state.saved[state.active].courseSessions[action.payload] = {Lecture: "", Section: "", Color: GET_CALENDAR_COLOR(state.saved[state.active].selected.length)}; }, removeCourseFromActiveSchedule: (state, action: PayloadAction) => { if (state.active === null) return; @@ -59,6 +81,10 @@ export const userSchedulesSlice = createSlice({ state.saved[state.active].selected, action.payload ); + + console.log("Here") + + delete state.saved[state.active].courseSessions[action.payload]; }, selectCourseInActiveSchedule: (state, action: PayloadAction) => { if (state.active === null) return; @@ -88,22 +114,12 @@ export const userSchedulesSlice = createSlice({ }, createEmptySchedule: (state) => { const newId = uuidv4(); - state.saved[newId] = { - name: "My Schedule", - selected: [], - courses: [], - id: newId, - }; + state.saved[newId] = getNewUserSchedule([]); state.active = newId; }, createSharedSchedule: (state, action: PayloadAction) => { const newId = uuidv4(); - state.saved[newId] = { - name: "Shared Schedule", - selected: action.payload, - courses: action.payload, - id: newId, - }; + state.saved[newId] = getNewUserSchedule(action.payload); state.active = newId; }, deleteSchedule: (state, action: PayloadAction) => { @@ -123,6 +139,16 @@ export const userSchedulesSlice = createSlice({ state.saved[state.active].name = action.payload; } }, + updateActiveScheduleSession: (state, action: PayloadAction) => { + if (state.active !== null) { + state.saved[state.active].session = action.payload; + } + }, + updateActiveScheduleCourseSession: (state, action: PayloadAction<{ courseID: string, sessionType: string, session: string }>) => { + if (state.active !== null) { + state.saved[state.active].courseSessions[action.payload.courseID][action.payload.sessionType] = action.payload.session + } + }, }, }); @@ -138,4 +164,20 @@ export const selectSelectedCoursesInActiveSchedule = ( return state.schedules.saved[state.schedules.active].selected; }; +export const selectSessionInActiveSchedule = ( + state: RootState +): string => { + if (state.schedules.active === null) return ""; + const session = state.schedules.saved[state.schedules.active].session; + if (session.semester === "") return ""; + return sessionToString(session); +}; + +export const selectCourseSessionsInActiveSchedule = ( + state: RootState +): CourseSessions => { + if (state.schedules.active === null) return {}; + return state.schedules.saved[state.schedules.active].courseSessions; +}; + export const reducer = userSchedulesSlice.reducer; diff --git a/apps/frontend/src/app/utils.tsx b/apps/frontend/src/app/utils.tsx index cd4d6c5..98f1487 100644 --- a/apps/frontend/src/app/utils.tsx +++ b/apps/frontend/src/app/utils.tsx @@ -65,6 +65,38 @@ export const sessionToShortString = (sessionInfo: Session | FCE | Schedule) => { } }; +export const stringToSession = (sessionString: string): Session => { + const sessionStringSplit = sessionString.split(" "); + + if (sessionStringSplit.length === 2) { + const [semester, year] = sessionStringSplit; + return { + semester: semester?.toLowerCase() as any, + year: year as string, + }; + } else if (sessionStringSplit.length === 3) { + const [semester, session, year] = sessionStringSplit; + + if (semester?.includes("Q")) { + return { + semester: "summer", + year: year as string, + session: "qatar summer", + }; + } + return { + semester: semester?.toLowerCase() as any, + year: year as string, + session: `${semester} ${session}`.toLowerCase() as any, + }; + } + + return { + semester: "", + year: "", + }; +} + export const compareSessions = ( session1: Session | FCE, session2: Session | FCE diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index 09214f4..d30960a 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -12,7 +12,8 @@ import { fetchCourseInfos } from "~/app/api/course"; import { fetchFCEInfosByCourse } from "~/app/api/fce"; import { selectCourseResults } from "~/app/cache"; import {Course, Time} from "~/app/types"; -import {sessionToString} from "~/app/utils"; +import { sessionToString } from "~/app/utils"; +import { selectCourseSessionsInActiveSchedule, selectSessionInActiveSchedule } from "~/app/userSchedules"; const localizer = momentLocalizer(moment); @@ -158,8 +159,8 @@ interface Props { const ScheduleCalendar = ({ courseIDs }: Props) =>{ const loggedIn = useAppSelector((state) => state.user.loggedIn); - const selectedSemester = useAppSelector((state) => state.user.selectedSemester); - const selectedSessions = useAppSelector((state) => state.user.selectedSessions); + const selectedSession = useAppSelector(selectSessionInActiveSchedule); + const selectedCourseSessions = useAppSelector(selectCourseSessionsInActiveSchedule); const dispatch = useAppDispatch(); useDeepCompareEffect(() => { @@ -171,7 +172,7 @@ const ScheduleCalendar = ({ courseIDs }: Props) =>{ const CourseDetails = useAppSelector(selectCourseResults(courseIDs)).filter(x => x !== undefined); - const events = getEvents(CourseDetails, selectedSemester, selectedSessions); + const events = getEvents(CourseDetails, selectedSession, selectedCourseSessions); const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; diff --git a/apps/frontend/src/components/ScheduleSearch.tsx b/apps/frontend/src/components/ScheduleSearch.tsx index bd594ca..40e45f3 100644 --- a/apps/frontend/src/components/ScheduleSearch.tsx +++ b/apps/frontend/src/components/ScheduleSearch.tsx @@ -159,6 +159,7 @@ const CourseCombobox = ({ onClick={(e) => { e.stopPropagation(); removeSelectedItem(selectedItem); + dispatch(userSchedulesSlice.actions.removeCourseFromActiveSchedule(selectedItem.courseID)); }} > ✕ @@ -224,7 +225,7 @@ const CourseCombobox = ({ }, })} > - + {course.courseID} diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index 0fbcc8d..afcaadc 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -1,36 +1,28 @@ import React, { Dispatch, SetStateAction } from "react"; -import {useAppDispatch, useAppSelector} from "~/app/hooks"; +import { useAppDispatch, useAppSelector } from "~/app/hooks"; import { ChevronUpDownIcon } from "@heroicons/react/24/outline"; import { Listbox } from "@headlessui/react"; -import {classNames, sessionToString} from "~/app/utils"; +import { classNames, sessionToString, stringToSession } from "~/app/utils"; import { CheckIcon } from "@heroicons/react/20/solid"; import {selectCourseResults} from "~/app/cache"; import { Lecture, Section } from "~/app/types"; -import { userSlice } from "~/app/user"; -import {GET_CALENDAR_COLOR} from "~/app/constants"; +import { + CourseSessions, + selectCourseSessionsInActiveSchedule, + selectSessionInActiveSchedule, + userSchedulesSlice +} from "~/app/userSchedules"; interface Props { courseIDs: string[]; } -interface courseSessions { - [courseID: string]: { - [sessionType: string]: string; - }; -} - -const getTimes = (courseID: string, sessionType: string, sessions: Lecture[] | Section[], selectedSessions: courseSessions, dispatch: Dispatch>) => { +const getTimes = (courseID: string, sessionType: string, sessions: Lecture[] | Section[], selectedSessions: CourseSessions, dispatch: Dispatch>) => { const selectedSession = selectedSessions[courseID]?.[sessionType] || ""; return ( { - dispatch(userSlice.actions.setSelectedSessions({ - ...selectedSessions, - [courseID]: { - ...selectedSessions[courseID], - [sessionType]: payload - } - })); + dispatch(userSchedulesSlice.actions.updateActiveScheduleCourseSession({ courseID, sessionType, session: payload as string })); }}> {sessionType} @@ -97,8 +89,9 @@ const getTimes = (courseID: string, sessionType: string, sessions: Lecture[] | S const SectionSelector = ({ courseIDs }: Props) => { const dispatch = useAppDispatch(); const CourseDetails = useAppSelector(selectCourseResults(courseIDs)).filter(x => x !== undefined); - const selectedSemester = useAppSelector((state) => state.user.selectedSemester); - const selectedSessions = useAppSelector((state) => state.user.selectedSessions); + + const selectedSession = useAppSelector(selectSessionInActiveSchedule); + const selectedCourseSessions = useAppSelector(selectCourseSessionsInActiveSchedule); const semesters = [...new Set(CourseDetails.flatMap(course => { const schedules = course.schedules; @@ -114,13 +107,8 @@ const SectionSelector = ({ courseIDs }: Props) => {
- { - dispatch(userSlice.actions.setSelectedSemester(payload)); - const courseIDs = CourseDetails.filter((course) => course.schedules?.some(sched => sessionToString(sched) === payload)).map(course => course.courseID); - dispatch(userSlice.actions.setSelectedSessions(courseIDs.reduce((acc: courseSessions, courseID, i: number) => { - acc[courseID] = {Lecture: "", Section: "", Color: GET_CALENDAR_COLOR(i)}; - return acc; - }, {}))); + { + dispatch(userSchedulesSlice.actions.updateActiveScheduleSession(stringToSession(payload))); }}> Semester @@ -128,14 +116,14 @@ const SectionSelector = ({ courseIDs }: Props) => { - {selectedSemester.length === 0 ? ( + {selectedSession.length === 0 ? ( Select Semester ) : ( - {selectedSemester} + {selectedSession} )} @@ -184,15 +172,15 @@ const SectionSelector = ({ courseIDs }: Props) => {
{ - CourseDetails.filter((course) => course.schedules?.some(sched => sessionToString(sched) === selectedSemester)).map((course) => { - const schedule = course.schedules?.find(sched => sessionToString(sched) === selectedSemester); + CourseDetails.filter((course) => course.schedules?.some(sched => sessionToString(sched) === selectedSession)).map((course) => { + const schedule = course.schedules?.find(sched => sessionToString(sched) === selectedSession); const courseID = course.courseID; return ( -
+
{courseID}
- {schedule?.lectures && getTimes(courseID, "Lecture", schedule.lectures, selectedSessions, dispatch)} - {schedule?.sections && getTimes(courseID, "Section", schedule.sections, selectedSessions, dispatch)} + {schedule?.lectures && getTimes(courseID, "Lecture", schedule.lectures, selectedCourseSessions, dispatch)} + {schedule?.sections && getTimes(courseID, "Section", schedule.sections, selectedCourseSessions, dispatch)}
); }) From 14221efd0cbc36b22473815caf98702ef6328514 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Fri, 18 Oct 2024 21:16:06 -0500 Subject: [PATCH 09/39] Removed line-clamp which is now included by default --- apps/frontend/tailwind.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/tailwind.config.ts b/apps/frontend/tailwind.config.ts index a3d0c1a..2c72172 100644 --- a/apps/frontend/tailwind.config.ts +++ b/apps/frontend/tailwind.config.ts @@ -143,5 +143,5 @@ module.exports = { }, }, }, - plugins: [require("nightwind"), require("@tailwindcss/line-clamp")], + plugins: [require("nightwind")], }; From fb0bea12b53c7ce0336bc596022f0d6be6deca33 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Fri, 18 Oct 2024 22:19:42 -0500 Subject: [PATCH 10/39] Fix CSS --- .../src/components/SectionSelector.tsx | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index afcaadc..5e06c15 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -21,14 +21,15 @@ const getTimes = (courseID: string, sessionType: string, sessions: Lecture[] | S const selectedSession = selectedSessions[courseID]?.[sessionType] || ""; return ( - { +
+ { dispatch(userSchedulesSlice.actions.updateActiveScheduleCourseSession({ courseID, sessionType, session: payload as string })); }}> - - {sessionType} - - + + {sessionType} + + {selectedSession.length === 0 ? ( Select Lecture @@ -41,26 +42,26 @@ const getTimes = (courseID: string, sessionType: string, sessions: Lecture[] | S )} - - - - -
- - {sessions.map((lecture) => ( - { - return classNames( - "relative cursor-pointer select-none py-2 pl-3 pr-9 focus:outline-none ", - active ? "bg-indigo-600 text-gray-600" : "text-gray-900" - ); - }} - > - {({selected}) => ( - <> + + + + +
+ + {sessions.map((lecture) => ( + { + return classNames( + "relative cursor-pointer select-none py-2 pl-3 pr-9 focus:outline-none ", + active ? "bg-indigo-600 text-gray-600" : "text-gray-900" + ); + }} + > + {({selected}) => ( + <> - {selected && ( - + {selected && ( + - )} - - )} - - ))} - -
- + )} + + )} +
+ ))} +
+
+
+
) } From 10cc9189be4c9d15462f96d9809bc3d84db99477 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Fri, 18 Oct 2024 22:29:22 -0500 Subject: [PATCH 11/39] Added nullish check, removed console.log --- apps/frontend/src/components/ScheduleCalendar.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index d30960a..d09f056 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -121,8 +121,6 @@ const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSe } }); - console.log(selectedSessions) - const selectedLectures = filteredCourses.flatMap(course => { const lecture = course.schedules?.find(sched => sessionToString(sched) === selectedSemester) ?.lectures.find(lecture => lecture.name === selectedSessions[course.courseID]?.Lecture); From 5a56e6e7bb6dddd0fef57f92bc8488c47fedb28c Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Fri, 18 Oct 2024 22:32:57 -0500 Subject: [PATCH 12/39] Updated margins --- apps/frontend/src/components/CourseList.tsx | 2 +- apps/frontend/src/components/ScheduleCalendar.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/components/CourseList.tsx b/apps/frontend/src/components/CourseList.tsx index 18bd619..68b4225 100644 --- a/apps/frontend/src/components/CourseList.tsx +++ b/apps/frontend/src/components/CourseList.tsx @@ -12,7 +12,7 @@ const CourseList = ({ courseIDs, children }: Props) => { const results = useFetchCourseInfos(courseIDs); return ( -
+
{results.length > 0 ? ( <>
diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index d09f056..f45f8c5 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -198,7 +198,7 @@ const ScheduleCalendar = ({ courseIDs }: Props) =>{ }), []); return ( -
+
Date: Fri, 18 Oct 2024 22:37:50 -0500 Subject: [PATCH 13/39] Added nothing in your schedule yet --- apps/frontend/src/pages/schedules.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/pages/schedules.tsx b/apps/frontend/src/pages/schedules.tsx index 49a5ea6..b8e257a 100644 --- a/apps/frontend/src/pages/schedules.tsx +++ b/apps/frontend/src/pages/schedules.tsx @@ -38,13 +38,21 @@ const SchedulePage: NextPage = () => { )} )} - {scheduleView === CAL_VIEW && ()} + {scheduleView === CAL_VIEW && ( + scheduled.length ? ( + + ) : ( +
+ Nothing in your schedule yet! +
+ ) + )} } sidebar={ <> - - + + } From 2eb5a9910dfb01f4c33e90162678612d428b161f Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Fri, 18 Oct 2024 22:39:51 -0500 Subject: [PATCH 14/39] Added no lectures --- apps/frontend/src/components/SectionSelector.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index 5e06c15..92d684d 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -81,6 +81,8 @@ const getTimes = (courseID: string, sessionType: string, sessions: Lecture[] | S )} ))} + {sessions.length === 0 && + No {sessionType}s}
From e16bd2f2d92bc3b2aca0257957f5aa2557ffde71 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Fri, 18 Oct 2024 23:42:49 -0500 Subject: [PATCH 15/39] Added dropdown to add automatically to schedule --- apps/frontend/src/app/userSchedules.ts | 12 ++--- .../src/components/BookmarkButton.tsx | 51 ++++++++++++++++--- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/apps/frontend/src/app/userSchedules.ts b/apps/frontend/src/app/userSchedules.ts index 386c1cb..dc8484e 100644 --- a/apps/frontend/src/app/userSchedules.ts +++ b/apps/frontend/src/app/userSchedules.ts @@ -31,12 +31,12 @@ const initialState: UserSchedulesState = { saved: {}, }; -const getNewUserSchedule = (courseIDs: string[]) : UserSchedule => { +const getNewUserSchedule = (courseIDs: string[], id: string) : UserSchedule => { return { name: "My Schedule", courses: courseIDs, selected: courseIDs, - id: uuidv4(), + id: id, session: { year: "", semester: "", @@ -58,7 +58,7 @@ export const userSchedulesSlice = createSlice({ addCourseToActiveSchedule: (state, action: PayloadAction) => { if (state.active === null) { const newId = uuidv4(); - state.saved[newId] = getNewUserSchedule([]); + state.saved[newId] = getNewUserSchedule([], newId); state.active = newId; } state.saved[state.active].courses = addToSet( @@ -82,8 +82,6 @@ export const userSchedulesSlice = createSlice({ action.payload ); - console.log("Here") - delete state.saved[state.active].courseSessions[action.payload]; }, selectCourseInActiveSchedule: (state, action: PayloadAction) => { @@ -114,12 +112,12 @@ export const userSchedulesSlice = createSlice({ }, createEmptySchedule: (state) => { const newId = uuidv4(); - state.saved[newId] = getNewUserSchedule([]); + state.saved[newId] = getNewUserSchedule([], newId); state.active = newId; }, createSharedSchedule: (state, action: PayloadAction) => { const newId = uuidv4(); - state.saved[newId] = getNewUserSchedule(action.payload); + state.saved[newId] = getNewUserSchedule(action.payload, newId); state.active = newId; }, deleteSchedule: (state, action: PayloadAction) => { diff --git a/apps/frontend/src/components/BookmarkButton.tsx b/apps/frontend/src/components/BookmarkButton.tsx index 25c09da..9c60c91 100644 --- a/apps/frontend/src/components/BookmarkButton.tsx +++ b/apps/frontend/src/components/BookmarkButton.tsx @@ -1,13 +1,15 @@ -import React from "react"; -import { StarIcon as OutlineStar } from "@heroicons/react/24/outline"; +import React, { useState } from "react"; +import { PlusIcon } from "@heroicons/react/24/solid"; import { useAppDispatch, useAppSelector } from "~/app/hooks"; import { userSlice } from "~/app/user"; +import { CheckIcon } from "@heroicons/react/20/solid"; +import { UserSchedule, userSchedulesSlice } from "~/app/userSchedules"; interface Props { courseID: string; } -const BookmarkButton = ({ courseID }: Props) => { +const BookmarkButton = ({courseID}: Props) => { const dispatch = useAppDispatch(); const bookmarks = useAppSelector((state) => state.user.bookmarked); const bookmarked = bookmarks.indexOf(courseID) !== -1; @@ -17,12 +19,47 @@ const BookmarkButton = ({ courseID }: Props) => { else dispatch(userSlice.actions.addBookmark(courseID)); }; + const [dropdownVisible, setDropdownVisible] = useState(false); + const saved = useAppSelector((state) => state.schedules.saved); + + const toggleCourseInSchedule = (schedule: UserSchedule) => { + dispatch(userSchedulesSlice.actions.changeActiveSchedule(schedule.id)); + if (schedule.courses.includes(courseID)) { + dispatch(userSchedulesSlice.actions.removeCourseFromActiveSchedule(courseID)); + } else { + dispatch(userSchedulesSlice.actions.addCourseToActiveSchedule(courseID)); + } + }; + return ( -
- {bookmarked ? ( - +
setDropdownVisible(true)} onMouseLeave={() => setDropdownVisible(false)}> + {dropdownVisible ? ( +
+
    +
  • + Saved + {bookmarked && ( + + + + )} +
  • + {Object.values(saved).map((schedule) => ( +
  • toggleCourseInSchedule(schedule)}> + {schedule.name} + {schedule.courses.includes(courseID) && ( + + + + )} +
  • + ))} +
+
) : ( - + )}
); From 5e2ad500506a954e81093122ff3c42eed10fa3c7 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Fri, 18 Oct 2024 23:46:08 -0500 Subject: [PATCH 16/39] Added dropdown to add automatically to schedule --- apps/frontend/src/components/BookmarkButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/components/BookmarkButton.tsx b/apps/frontend/src/components/BookmarkButton.tsx index 9c60c91..0a81415 100644 --- a/apps/frontend/src/components/BookmarkButton.tsx +++ b/apps/frontend/src/components/BookmarkButton.tsx @@ -34,7 +34,7 @@ const BookmarkButton = ({courseID}: Props) => { return (
setDropdownVisible(true)} onMouseLeave={() => setDropdownVisible(false)}> {dropdownVisible ? ( -
+
  • From 3294da44edfeff2661bbd520f919bb12d90d5407 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Fri, 18 Oct 2024 23:57:06 -0500 Subject: [PATCH 17/39] Sort semesters --- apps/frontend/src/components/SectionSelector.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index 92d684d..8a720f6 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -2,7 +2,7 @@ import React, { Dispatch, SetStateAction } from "react"; import { useAppDispatch, useAppSelector } from "~/app/hooks"; import { ChevronUpDownIcon } from "@heroicons/react/24/outline"; import { Listbox } from "@headlessui/react"; -import { classNames, sessionToString, stringToSession } from "~/app/utils"; +import {classNames, compareSessions, sessionToString, stringToSession} from "~/app/utils"; import { CheckIcon } from "@heroicons/react/20/solid"; import {selectCourseResults} from "~/app/cache"; import { Lecture, Section } from "~/app/types"; @@ -102,7 +102,9 @@ const SectionSelector = ({ courseIDs }: Props) => { if (schedules) { return schedules.map(schedule => sessionToString(schedule)); } - }))]; + }))].sort((a, b) => { + return compareSessions(stringToSession(a || ""), stringToSession(b || "")); + }); return (
    From f58b14b7060549583a50f76a428c2495eb45cb33 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sat, 19 Oct 2024 00:02:21 -0500 Subject: [PATCH 18/39] Fix color issue --- apps/frontend/src/app/userSchedules.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/app/userSchedules.ts b/apps/frontend/src/app/userSchedules.ts index dc8484e..3a2c258 100644 --- a/apps/frontend/src/app/userSchedules.ts +++ b/apps/frontend/src/app/userSchedules.ts @@ -19,6 +19,7 @@ export interface UserSchedule { id: string; session: Session; courseSessions: CourseSessions; + numColors: number; } export interface UserSchedulesState { @@ -45,6 +46,7 @@ const getNewUserSchedule = (courseIDs: string[], id: string) : UserSchedule => { acc[courseID] = {Lecture: "", Section: "", Color: GET_CALENDAR_COLOR(i)}; return acc; }, {}), + numColors: courseIDs.length, }; } @@ -69,7 +71,8 @@ export const userSchedulesSlice = createSlice({ state.saved[state.active].selected, action.payload ); - state.saved[state.active].courseSessions[action.payload] = {Lecture: "", Section: "", Color: GET_CALENDAR_COLOR(state.saved[state.active].selected.length)}; + state.saved[state.active].courseSessions[action.payload] = {Lecture: "", Section: "", Color: GET_CALENDAR_COLOR(state.saved[state.active].numColors)}; + state.saved[state.active].numColors += 1; }, removeCourseFromActiveSchedule: (state, action: PayloadAction) => { if (state.active === null) return; From 0e5c9687ff8c3318264d724db801a5e79f293603 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sat, 30 Nov 2024 00:01:10 -0500 Subject: [PATCH 19/39] Fixed compatibility issues --- apps/frontend/src/app/userSchedules.ts | 2 +- apps/frontend/src/app/utils.tsx | 12 +++++++---- .../src/components/ScheduleCalendar.tsx | 20 ++++--------------- .../src/components/SectionSelector.tsx | 12 +++++------ 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/apps/frontend/src/app/userSchedules.ts b/apps/frontend/src/app/userSchedules.ts index 3a2c258..a4fcfe1 100644 --- a/apps/frontend/src/app/userSchedules.ts +++ b/apps/frontend/src/app/userSchedules.ts @@ -170,7 +170,7 @@ export const selectSessionInActiveSchedule = ( ): string => { if (state.schedules.active === null) return ""; const session = state.schedules.saved[state.schedules.active].session; - if (session.semester === "") return ""; + if (session?.semester === "") return ""; return sessionToString(session); }; diff --git a/apps/frontend/src/app/utils.tsx b/apps/frontend/src/app/utils.tsx index 98f1487..bf3c666 100644 --- a/apps/frontend/src/app/utils.tsx +++ b/apps/frontend/src/app/utils.tsx @@ -20,7 +20,9 @@ export const standardizeIdsInString = (str: string) => { }; export const sessionToString = (sessionInfo: Session | FCE | Schedule) => { - const semester = sessionInfo.semester; + if (!sessionInfo) return ""; + + const semester = sessionInfo?.semester || ""; const sessionStrings = { "summer one": "Summer One", @@ -38,12 +40,14 @@ export const sessionToString = (sessionInfo: Session | FCE | Schedule) => { if (semester === "summer" && sessionInfo.session) { return `${sessionStrings[sessionInfo.session]} ${sessionInfo.year}`; } else { - return `${semesterStrings[sessionInfo.semester]} ${sessionInfo.year}`; + return `${semesterStrings[semester]} ${sessionInfo.year}`; } }; export const sessionToShortString = (sessionInfo: Session | FCE | Schedule) => { - const semester = sessionInfo.semester; + if (!sessionInfo) return ""; + + const semester = sessionInfo?.semester || ""; const sessionStrings = { "summer one": "M1", @@ -61,7 +65,7 @@ export const sessionToShortString = (sessionInfo: Session | FCE | Schedule) => { if (semester === "summer" && sessionInfo.session) { return `${sessionStrings[sessionInfo.session]} ${sessionInfo.year}`; } else { - return `${semesterStrings[sessionInfo.semester]} ${sessionInfo.year}`; + return `${semesterStrings[semester]} ${sessionInfo.year}`; } }; diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index f45f8c5..0b5a5ee 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -6,14 +6,11 @@ import TimeGrid from 'react-big-calendar/lib/TimeGrid' import Toolbar from 'react-big-calendar/lib/Toolbar' import style from "react-big-calendar/lib/css/react-big-calendar.css"; import moment from "moment"; -import { useAppDispatch, useAppSelector } from "~/app/hooks"; -import useDeepCompareEffect from "use-deep-compare-effect"; -import { fetchCourseInfos } from "~/app/api/course"; -import { fetchFCEInfosByCourse } from "~/app/api/fce"; -import { selectCourseResults } from "~/app/cache"; -import {Course, Time} from "~/app/types"; +import { useAppSelector } from "~/app/hooks"; +import { Course, Time } from "~/app/types"; import { sessionToString } from "~/app/utils"; import { selectCourseSessionsInActiveSchedule, selectSessionInActiveSchedule } from "~/app/userSchedules"; +import { useFetchCourseInfos } from "~/app/api/course"; const localizer = momentLocalizer(moment); @@ -156,19 +153,10 @@ interface Props { } const ScheduleCalendar = ({ courseIDs }: Props) =>{ - const loggedIn = useAppSelector((state) => state.user.loggedIn); const selectedSession = useAppSelector(selectSessionInActiveSchedule); const selectedCourseSessions = useAppSelector(selectCourseSessionsInActiveSchedule); - const dispatch = useAppDispatch(); - useDeepCompareEffect(() => { - if (courseIDs) { - void dispatch(fetchCourseInfos(courseIDs)); - if (loggedIn) void dispatch(fetchFCEInfosByCourse({ courseIDs })); - } - }, [courseIDs]); - - const CourseDetails = useAppSelector(selectCourseResults(courseIDs)).filter(x => x !== undefined); + const CourseDetails = useFetchCourseInfos(courseIDs); const events = getEvents(CourseDetails, selectedSession, selectedCourseSessions); diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index 8a720f6..111747f 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -4,14 +4,14 @@ import { ChevronUpDownIcon } from "@heroicons/react/24/outline"; import { Listbox } from "@headlessui/react"; import {classNames, compareSessions, sessionToString, stringToSession} from "~/app/utils"; import { CheckIcon } from "@heroicons/react/20/solid"; -import {selectCourseResults} from "~/app/cache"; -import { Lecture, Section } from "~/app/types"; +import { Lecture, Section, Schedule } from "~/app/types"; import { CourseSessions, selectCourseSessionsInActiveSchedule, selectSessionInActiveSchedule, userSchedulesSlice } from "~/app/userSchedules"; +import { useFetchCourseInfos } from "~/app/api/course"; interface Props { courseIDs: string[]; @@ -92,13 +92,13 @@ const getTimes = (courseID: string, sessionType: string, sessions: Lecture[] | S const SectionSelector = ({ courseIDs }: Props) => { const dispatch = useAppDispatch(); - const CourseDetails = useAppSelector(selectCourseResults(courseIDs)).filter(x => x !== undefined); + const CourseDetails = useFetchCourseInfos(courseIDs); const selectedSession = useAppSelector(selectSessionInActiveSchedule); const selectedCourseSessions = useAppSelector(selectCourseSessionsInActiveSchedule); const semesters = [...new Set(CourseDetails.flatMap(course => { - const schedules = course.schedules; + const schedules: Schedule[] = course.schedules; if (schedules) { return schedules.map(schedule => sessionToString(schedule)); } @@ -178,8 +178,8 @@ const SectionSelector = ({ courseIDs }: Props) => {
    { - CourseDetails.filter((course) => course.schedules?.some(sched => sessionToString(sched) === selectedSession)).map((course) => { - const schedule = course.schedules?.find(sched => sessionToString(sched) === selectedSession); + CourseDetails.filter((course) => course.schedules?.some((sched: Schedule) => sessionToString(sched) === selectedSession)).map((course) => { + const schedule = course.schedules?.find((sched: Schedule) => sessionToString(sched) === selectedSession); const courseID = course.courseID; return ( From a668c86f3aeaeb38321c898f96ee69704119912a Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sat, 30 Nov 2024 18:09:40 -0500 Subject: [PATCH 20/39] Change location of show/hide calendar --- apps/frontend/src/app/user.ts | 6 +++--- .../src/components/ScheduleSearch.tsx | 19 ++----------------- .../src/components/SectionSelector.tsx | 11 ++++++++++- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/apps/frontend/src/app/user.ts b/apps/frontend/src/app/user.ts index 362dbcb..ee440b4 100644 --- a/apps/frontend/src/app/user.ts +++ b/apps/frontend/src/app/user.ts @@ -1,6 +1,6 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { addToSet, removeFromSet } from "./utils"; -import { SEMESTERS_COUNTED } from "./constants"; +import {CAL_VIEW, SCHED_VIEW, SEMESTERS_COUNTED} from "./constants"; import { Semester } from "./types"; export interface UserState { @@ -105,8 +105,8 @@ export const userSlice = createSlice({ setSelectedTags: (state, action: PayloadAction) => { state.selectedTags = action.payload; }, - setScheduleView: (state, action: PayloadAction) => { - state.scheduleView = action.payload; + toggleScheduleView: (state) => { + state.scheduleView = state.scheduleView === CAL_VIEW ? SCHED_VIEW : CAL_VIEW; }, }, }); diff --git a/apps/frontend/src/components/ScheduleSearch.tsx b/apps/frontend/src/components/ScheduleSearch.tsx index 40e45f3..a629789 100644 --- a/apps/frontend/src/components/ScheduleSearch.tsx +++ b/apps/frontend/src/components/ScheduleSearch.tsx @@ -16,8 +16,6 @@ import { userSchedulesSlice, } from "~/app/userSchedules"; import { useFetchAllCourses } from "~/app/api/course"; -import { userSlice } from "~/app/user"; -import { CAL_VIEW, SCHED_VIEW } from "~/app/constants"; type selectedItem = { courseID: string; @@ -250,16 +248,15 @@ const ScheduleSearch = () => { const dispatch = useAppDispatch(); const savedSchedules = useAppSelector((state) => state.schedules.saved); const active = useAppSelector((state) => state.schedules.active); - const scheduleView = useAppSelector((state) => state.user.scheduleView); return (
    {active !== null && (
    - + dispatch( @@ -270,18 +267,6 @@ const ScheduleSearch = () => { } placeholder="Schedule Name" /> - -
    )} diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index 111747f..f2ff065 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -12,6 +12,8 @@ import { userSchedulesSlice } from "~/app/userSchedules"; import { useFetchCourseInfos } from "~/app/api/course"; +import { userSlice } from "~/app/user"; +import { SCHED_VIEW } from "~/app/constants"; interface Props { courseIDs: string[]; @@ -96,6 +98,7 @@ const SectionSelector = ({ courseIDs }: Props) => { const selectedSession = useAppSelector(selectSessionInActiveSchedule); const selectedCourseSessions = useAppSelector(selectCourseSessionsInActiveSchedule); + const scheduleView = useAppSelector((state) => state.user.scheduleView); const semesters = [...new Set(CourseDetails.flatMap(course => { const schedules: Schedule[] = course.schedules; @@ -108,8 +111,14 @@ const SectionSelector = ({ courseIDs }: Props) => { return (
    -
    +
    Schedule Calendar
    +
    From 0a31c174aa2a518f4c460365779f547b306e0c2b Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sat, 30 Nov 2024 18:22:34 -0500 Subject: [PATCH 21/39] Make show button more consistent --- apps/frontend/src/components/ScheduleData.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/components/ScheduleData.tsx b/apps/frontend/src/components/ScheduleData.tsx index 131c434..28a3390 100644 --- a/apps/frontend/src/components/ScheduleData.tsx +++ b/apps/frontend/src/components/ScheduleData.tsx @@ -98,7 +98,7 @@ const ScheduleData = ({ scheduled }: ScheduleDataProps) => { return ( <>
    -
    +
    Total Workload{" "} @@ -115,7 +115,7 @@ const ScheduleData = ({ scheduled }: ScheduleDataProps) => { dispatch(uiSlice.actions.toggleSchedulesTopbarOpen()) } > -
    +
    {open ? ( <>
    Hide
    From 0cd844367ea02dab325aaa0ad0db0fa0374ec78d Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sat, 30 Nov 2024 18:29:36 -0500 Subject: [PATCH 22/39] Changed schedules course search bar to make sense in small screens --- apps/frontend/src/components/ScheduleSearch.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/components/ScheduleSearch.tsx b/apps/frontend/src/components/ScheduleSearch.tsx index a629789..c9e2740 100644 --- a/apps/frontend/src/components/ScheduleSearch.tsx +++ b/apps/frontend/src/components/ScheduleSearch.tsx @@ -141,8 +141,8 @@ const CourseCombobox = ({
    -
    -
    +
    +
    {selectedItems.map((selectedItem, index) => (
    Date: Sat, 30 Nov 2024 18:34:19 -0500 Subject: [PATCH 23/39] Made sidebar smaller when in small screen --- apps/frontend/src/components/Sidebar.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/components/Sidebar.tsx b/apps/frontend/src/components/Sidebar.tsx index ff9ef68..6fe6057 100644 --- a/apps/frontend/src/components/Sidebar.tsx +++ b/apps/frontend/src/components/Sidebar.tsx @@ -14,7 +14,7 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => { const dispatch = useAppDispatch(); return open ? ( -
    +
    -
    +
    Hide
    @@ -34,7 +34,7 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => {
    ) : ( -
    +
    -
    +
    { CourseDetails.filter((course) => course.schedules?.some((sched: Schedule) => sessionToString(sched) === selectedSession)).map((course) => { const schedule = course.schedules?.find((sched: Schedule) => sessionToString(sched) === selectedSession); diff --git a/apps/frontend/src/components/Sidebar.tsx b/apps/frontend/src/components/Sidebar.tsx index 6fe6057..9bee19c 100644 --- a/apps/frontend/src/components/Sidebar.tsx +++ b/apps/frontend/src/components/Sidebar.tsx @@ -14,7 +14,7 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => { const dispatch = useAppDispatch(); return open ? ( -
    +
    + dispatch(userSlice.actions.toggleScheduleView())}> + {scheduleView === SCHED_VIEW ? "Show" : "Hide"} +
    -
    { dispatch(userSchedulesSlice.actions.updateActiveScheduleSession(stringToSession(payload))); From becd671ea4ddbad16c0de609fb86d16704a32cb5 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sun, 1 Dec 2024 15:01:45 -0500 Subject: [PATCH 31/39] Remove unused imports --- apps/frontend/src/components/SectionSelector.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index f90d76d..94c42f2 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from "~/app/hooks"; import { ChevronUpDownIcon } from "@heroicons/react/24/outline"; import { Listbox, RadioGroup } from "@headlessui/react"; import { classNames, compareSessions, sessionToString, stringToSession } from "~/app/utils"; -import {CheckIcon, ChevronDownIcon, ChevronUpIcon} from "@heroicons/react/20/solid"; +import { CheckIcon } from "@heroicons/react/20/solid"; import { Schedule } from "~/app/types"; import { CourseSessions, @@ -14,8 +14,7 @@ import { import { useFetchCourseInfos } from "~/app/api/course"; import { userSlice } from "~/app/user"; import { SCHED_VIEW } from "~/app/constants"; -import {FlushedButton} from "~/components/Buttons"; -import {uiSlice} from "~/app/ui"; +import { FlushedButton } from "~/components/Buttons"; interface Props { courseIDs: string[]; From 673200c317d40e328afb380fa988bd6b55e90561 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sun, 1 Dec 2024 15:09:08 -0500 Subject: [PATCH 32/39] Refactored some code --- .../src/components/SectionSelector.tsx | 106 +++++++++--------- 1 file changed, 51 insertions(+), 55 deletions(-) diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index 94c42f2..9d4f7a4 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, SetStateAction } from "react"; +import React from "react"; import { useAppDispatch, useAppSelector } from "~/app/hooks"; import { ChevronUpDownIcon } from "@heroicons/react/24/outline"; import { Listbox, RadioGroup } from "@headlessui/react"; @@ -6,7 +6,6 @@ import { classNames, compareSessions, sessionToString, stringToSession } from "~ import { CheckIcon } from "@heroicons/react/20/solid"; import { Schedule } from "~/app/types"; import { - CourseSessions, selectCourseSessionsInActiveSchedule, selectSessionInActiveSchedule, userSchedulesSlice @@ -16,55 +15,7 @@ import { userSlice } from "~/app/user"; import { SCHED_VIEW } from "~/app/constants"; import { FlushedButton } from "~/components/Buttons"; -interface Props { - courseIDs: string[]; -} - -const getTimes = (courseID: string, schedule: Schedule, selectedSessions: CourseSessions, dispatch: Dispatch>) => { - const sessionType = schedule.sections ? "Section" : "Lecture"; - const sessions = schedule.sections || schedule.lectures - - const selectedSession = selectedSessions[courseID]?.[sessionType] || ""; - - return ( -
    - { - if (sessionType === "Section") { - const section = schedule.sections.find((section) => section.name === payload); - const lecture = schedule.lectures.find((lecture) => lecture.name === section?.lecture); - if (lecture) - dispatch(userSchedulesSlice.actions.updateActiveScheduleCourseSession({ courseID, sessionType: "Lecture", session: lecture.name })); - } - dispatch(userSchedulesSlice.actions.updateActiveScheduleCourseSession({ courseID, sessionType, session: payload as string })); - }}> - {sessions.map((lecture) => ( - { - return classNames( - "flex relative justify-center cursor-pointer select-none focus:outline-none", - active ? "bg-indigo-600 text-gray-600" : "text-gray-900" - ); - }} - > - {({checked}) => ( - - - {lecture.name} - - - )} - - ))} - -
    - ) -} - -const SectionSelector = ({ courseIDs }: Props) => { +const SectionSelector = ({ courseIDs }: { courseIDs: string[] }) => { const dispatch = useAppDispatch(); const CourseDetails = useFetchCourseInfos(courseIDs); @@ -156,13 +107,17 @@ const SectionSelector = ({ courseIDs }: Props) => {
    { CourseDetails.filter((course) => course.schedules?.some((sched: Schedule) => sessionToString(sched) === selectedSession)).map((course) => { - const schedule = course.schedules?.find((sched: Schedule) => sessionToString(sched) === selectedSession); + const schedule: Schedule = course.schedules?.find((sched: Schedule) => sessionToString(sched) === selectedSession); const courseID = course.courseID; const sessionType = schedule.sections ? "Section" : "Lecture"; + const sessions = schedule.sections || schedule.lectures + + const selectedCourseSession = selectedCourseSessions[courseID]?.[sessionType] || ""; return ( -
    +
    {courseID} (Select {sessionType}) { ✕
    - {getTimes(courseID, schedule, selectedCourseSessions, dispatch)} +
    + { + if (sessionType === "Section") { + const section = schedule.sections.find((section) => section.name === payload); + const lecture = schedule.lectures.find((lecture) => lecture.name === section?.lecture); + if (lecture) + dispatch(userSchedulesSlice.actions.updateActiveScheduleCourseSession({ + courseID, + sessionType: "Lecture", + session: lecture.name + })); + } + dispatch(userSchedulesSlice.actions.updateActiveScheduleCourseSession({ + courseID, + sessionType, + session: payload as string + })); + }}> + {sessions.map((lecture) => ( + { + return classNames( + "flex relative justify-center cursor-pointer select-none focus:outline-none", + active ? "bg-indigo-600 text-gray-600" : "text-gray-900" + ); + }} + > + {({checked}) => ( + + + {lecture.name} + + + )} + + ))} + +
    ); }) }
    -
    +
    ); }; From 9f883a2d635e3b48ceea5aa8cdbc55bf57244d3e Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sun, 1 Dec 2024 15:25:52 -0500 Subject: [PATCH 33/39] Made sure text sizes are ok --- apps/frontend/src/components/SectionSelector.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index 9d4f7a4..5e617be 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -132,7 +132,7 @@ const SectionSelector = ({ courseIDs }: { courseIDs: string[] }) => {
    { if (sessionType === "Section") { const section = schedule.sections.find((section) => section.name === payload); @@ -150,13 +150,16 @@ const SectionSelector = ({ courseIDs }: { courseIDs: string[] }) => { session: payload as string })); }}> - {sessions.map((lecture) => ( + {sessions.map((lecture, i) => ( { return classNames( "flex relative justify-center cursor-pointer select-none focus:outline-none", + "hover:bg-gray-50 py-1", + i === 0 ? "rounded-l-md pl-1" : "", + i === sessions.length - 1 ? "rounded-r-md pr-1" : "", active ? "bg-indigo-600 text-gray-600" : "text-gray-900" ); }} From e8d043c928b14ce4cd68ee34597433acc9e9b83f Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sun, 1 Dec 2024 15:40:18 -0500 Subject: [PATCH 34/39] Cosmetic change --- apps/frontend/src/components/ScheduleCalendar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index 0b5a5ee..4494928 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -170,7 +170,7 @@ const ScheduleCalendar = ({ courseIDs }: Props) =>{ }, components: { toolbar: CustomToolbar, - header: ({ date } : { date: Date }) => {days[moment(date).day()]}, + header: ({ date } : { date: Date }) =>
    {days[moment(date).day()]}
    , }, formats: { eventTimeRangeFormat: () => { From 27e684a78f0e3ee38cbf101ab7a4b3b516417e6e Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sun, 1 Dec 2024 15:42:36 -0500 Subject: [PATCH 35/39] Cosmetic change --- apps/frontend/src/components/ScheduleCalendar.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index 4494928..81067a4 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -9,7 +9,7 @@ import moment from "moment"; import { useAppSelector } from "~/app/hooks"; import { Course, Time } from "~/app/types"; import { sessionToString } from "~/app/utils"; -import { selectCourseSessionsInActiveSchedule, selectSessionInActiveSchedule } from "~/app/userSchedules"; +import { CourseSessions, selectCourseSessionsInActiveSchedule, selectSessionInActiveSchedule } from "~/app/userSchedules"; import { useFetchCourseInfos } from "~/app/api/course"; const localizer = momentLocalizer(moment); @@ -80,12 +80,6 @@ const getTime = (day: number, time: string) => { return new Date(2024, 8, 29 + day, parseInt(hour || "0"), parseInt(minute || "0")); } -interface courseSessions { - [courseID: string]: { - [sessionType: string]: string; - }; -} - const getTimes = (courseID: string, sessionType: string, sessionTimes: Time[], color: string) => { const times = []; for (const sessionTime of sessionTimes || []) { @@ -108,7 +102,7 @@ interface Event { color: string; } -const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSessions: courseSessions) => { +const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSessions: CourseSessions) => { let events: Event[] = []; const filteredCourses = CourseDetails.filter((course) => { From facf42d48bbf82a55ae94069f6e7f32dbb8bc609 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sun, 1 Dec 2024 16:17:58 -0500 Subject: [PATCH 36/39] Show possible on hover --- apps/frontend/src/app/userSchedules.ts | 23 +++++++ .../src/components/ScheduleCalendar.tsx | 31 +++++++++- .../src/components/SectionSelector.tsx | 62 ++++++++++++------- 3 files changed, 92 insertions(+), 24 deletions(-) diff --git a/apps/frontend/src/app/userSchedules.ts b/apps/frontend/src/app/userSchedules.ts index a4fcfe1..bcd04f9 100644 --- a/apps/frontend/src/app/userSchedules.ts +++ b/apps/frontend/src/app/userSchedules.ts @@ -12,6 +12,11 @@ export interface CourseSessions { }; } +export interface HoverSession { + courseID: string; + [sessionType: string]: string; +} + export interface UserSchedule { name: string; courses: string[]; @@ -20,6 +25,7 @@ export interface UserSchedule { session: Session; courseSessions: CourseSessions; numColors: number; + hoverSession?: HoverSession; } export interface UserSchedulesState { @@ -150,6 +156,16 @@ export const userSchedulesSlice = createSlice({ state.saved[state.active].courseSessions[action.payload.courseID][action.payload.sessionType] = action.payload.session } }, + setHoverSession: (state, action: PayloadAction<{ courseID: string, [sessionType: string]: string }>) => { + if (state.active !== null) { + state.saved[state.active].hoverSession = action.payload; + } + }, + clearHoverSession: (state) => { + if (state.active !== null) { + state.saved[state.active].hoverSession = undefined; + } + }, }, }); @@ -181,4 +197,11 @@ export const selectCourseSessionsInActiveSchedule = ( return state.schedules.saved[state.schedules.active].courseSessions; }; +export const selectHoverSessionInActiveSchedule = ( + state: RootState +): { courseID: string, [sessionType: string]: string } | undefined => { + if (state.schedules.active === null) return undefined; + return state.schedules.saved[state.schedules.active].hoverSession; +}; + export const reducer = userSchedulesSlice.reducer; diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index 81067a4..17b1183 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -9,7 +9,12 @@ import moment from "moment"; import { useAppSelector } from "~/app/hooks"; import { Course, Time } from "~/app/types"; import { sessionToString } from "~/app/utils"; -import { CourseSessions, selectCourseSessionsInActiveSchedule, selectSessionInActiveSchedule } from "~/app/userSchedules"; +import { + CourseSessions, HoverSession, + selectCourseSessionsInActiveSchedule, + selectHoverSessionInActiveSchedule, + selectSessionInActiveSchedule +} from "~/app/userSchedules"; import { useFetchCourseInfos } from "~/app/api/course"; const localizer = momentLocalizer(moment); @@ -102,7 +107,7 @@ interface Event { color: string; } -const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSessions: CourseSessions) => { +const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSessions: CourseSessions, hoverSession?: HoverSession) => { let events: Event[] = []; const filteredCourses = CourseDetails.filter((course) => { @@ -139,6 +144,25 @@ const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSe events = events.concat(selectedSections.flatMap(section => { if (section.times) return getTimes(section.courseID, `Section ${section.name || ""}`, section.times, section.color); }).filter(x => x !== undefined)); + + if (hoverSession) { + const courseID = hoverSession.courseID; + const selectedCourse = filteredCourses.find(course => course.courseID === courseID); + + const hoverLecture = selectedCourse?.schedules?.find(sched => sessionToString(sched) === selectedSemester) + ?.lectures.find(lecture => lecture.name === hoverSession["Lecture"]); + + const hoverSection = selectedCourse?.schedules?.find(sched => sessionToString(sched) === selectedSemester) + ?.sections.find(section => section.name === hoverSession["Section"]); + + const hoverColor = "#9CA3AF"; + if (hoverLecture) + events.push(...getTimes(courseID, hoverLecture.name || "Lecture", hoverLecture.times, hoverColor)); + + if (hoverSection) + events.push(...getTimes(courseID, `Section ${hoverSection?.name || ""}`, hoverSection?.times, hoverColor)); + } + return events; } @@ -149,10 +173,11 @@ interface Props { const ScheduleCalendar = ({ courseIDs }: Props) =>{ const selectedSession = useAppSelector(selectSessionInActiveSchedule); const selectedCourseSessions = useAppSelector(selectCourseSessionsInActiveSchedule); + const hoverSession = useAppSelector(selectHoverSessionInActiveSchedule); const CourseDetails = useFetchCourseInfos(courseIDs); - const events = getEvents(CourseDetails, selectedSession, selectedCourseSessions); + const events = getEvents(CourseDetails, selectedSession, selectedCourseSessions, hoverSession); const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index 5e617be..bbcb439 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -133,27 +133,33 @@ const SectionSelector = ({ courseIDs }: { courseIDs: string[] }) => {
    { - if (sessionType === "Section") { - const section = schedule.sections.find((section) => section.name === payload); - const lecture = schedule.lectures.find((lecture) => lecture.name === section?.lecture); - if (lecture) - dispatch(userSchedulesSlice.actions.updateActiveScheduleCourseSession({ - courseID, - sessionType: "Lecture", - session: lecture.name - })); - } - dispatch(userSchedulesSlice.actions.updateActiveScheduleCourseSession({ - courseID, - sessionType, - session: payload as string - })); - }}> - {sessions.map((lecture, i) => ( + value={selectedCourseSession} + onChange={(payload) => { + if (sessionType === "Section") { + const section = schedule.sections.find((section) => section.name === payload); + const lecture = schedule.lectures.find((lecture) => lecture.name === section?.lecture); + if (lecture) + dispatch(userSchedulesSlice.actions.updateActiveScheduleCourseSession({ + courseID, + sessionType: "Lecture", + session: lecture.name + })); + } + dispatch(userSchedulesSlice.actions.updateActiveScheduleCourseSession({ + courseID, + sessionType, + session: payload as string + })); + dispatch(userSchedulesSlice.actions.clearHoverSession()); + }} + onMouseLeave={() => { + dispatch(userSchedulesSlice.actions.clearHoverSession()); + }} + > + {sessions.map((session, i) => ( { return classNames( "flex relative justify-center cursor-pointer select-none focus:outline-none", @@ -163,11 +169,25 @@ const SectionSelector = ({ courseIDs }: { courseIDs: string[] }) => { active ? "bg-indigo-600 text-gray-600" : "text-gray-900" ); }} + onMouseEnter={() => { + const payload = { courseID, "Lecture": "", "Section": "" }; + if (sessionType === "Lecture") { + payload["Lecture"] = session.name; + } + if (sessionType === "Section") { + payload["Section"] = session.name; + const section = schedule.sections.find((section) => section.name === session.name); + const lecture = schedule.lectures.find((lecture) => lecture.name === section?.lecture); + if (lecture) + payload["Lecture"] = lecture.name; + } + dispatch(userSchedulesSlice.actions.setHoverSession(payload)); + }} > {({checked}) => ( - {lecture.name} + {session.name} )} From 8d3e48ec8da99386c07c2f0e70aeb85126d8db2f Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sun, 1 Dec 2024 16:35:38 -0500 Subject: [PATCH 37/39] Light colors for hover --- apps/frontend/src/app/constants.ts | 36 ++++++++++++------- apps/frontend/src/app/userSchedules.ts | 7 ++-- apps/frontend/src/app/utils.tsx | 9 ++++- .../src/components/ScheduleCalendar.tsx | 4 +-- .../src/components/SectionSelector.tsx | 2 +- 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/apps/frontend/src/app/constants.ts b/apps/frontend/src/app/constants.ts index 7481f7e..48241ae 100644 --- a/apps/frontend/src/app/constants.ts +++ b/apps/frontend/src/app/constants.ts @@ -139,17 +139,29 @@ export const STALE_TIME = 1000 * 60 * 60 * 24; // 1 day export const CAL_VIEW = "cal"; export const SCHED_VIEW = "sched"; -const CALENDAR_COLORS = [ - "#FFB3BA", // Light Red - "#FFDFBA", // Light Orange - "#FFFFBA", // Light Yellow - "#BAFFC9", // Light Green - "#BAE1FF", // Light Blue - "#D4BAFF", // Light Purple - "#FFC4E1", // Light Pink - "#C4E1FF", // Light Sky Blue - "#E1FFC4", // Light Lime - "#FFF4BA" // Light Cream + +export const CALENDAR_COLORS = [ + "#FFB3BA", // Red + "#FFDFBA", // Orange + "#FFFFBA", // Yellow + "#BAFFC9", // Green + "#BAE1FF", // Blue + "#D4BAFF", // Purple + "#FFC4E1", // Pink + "#C4E1FF", // Sky Blue + "#E1FFC4", // Lime + "#FFF4BA" // Cream ]; -export const GET_CALENDAR_COLOR = (i: number) => CALENDAR_COLORS[i % CALENDAR_COLORS.length] || ""; \ No newline at end of file +export const CALENDAR_COLORS_LIGHT = [ + "#FFE1E3", // Light Red + "#FFF2E3", // Light Orange + "#FFFFE3", // Light Yellow + "#E3FFE9", // Light Green + "#E3F3FF", // Light Blue + "#EEE3FF", // Light Purple + "#FFE7F3", // Light Pink + "#E7F3FF", // Light Sky Blue + "#F3FFE7", // Light Lime + "#FFFBE3" // Light Cream +]; \ No newline at end of file diff --git a/apps/frontend/src/app/userSchedules.ts b/apps/frontend/src/app/userSchedules.ts index bcd04f9..34db77e 100644 --- a/apps/frontend/src/app/userSchedules.ts +++ b/apps/frontend/src/app/userSchedules.ts @@ -1,9 +1,8 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { addToSet, removeFromSet, sessionToString } from "./utils"; +import { addToSet, getCalendarColor, removeFromSet, sessionToString } from "./utils"; import { Session } from "./types"; import { v4 as uuidv4 } from "uuid"; import { RootState } from "./store"; -import { GET_CALENDAR_COLOR } from "~/app/constants"; export interface CourseSessions { [courseID: string]: { @@ -49,7 +48,7 @@ const getNewUserSchedule = (courseIDs: string[], id: string) : UserSchedule => { semester: "", }, courseSessions: courseIDs.reduce((acc: CourseSessions, courseID, i: number) => { - acc[courseID] = {Lecture: "", Section: "", Color: GET_CALENDAR_COLOR(i)}; + acc[courseID] = {Lecture: "", Section: "", Color: getCalendarColor(i)}; return acc; }, {}), numColors: courseIDs.length, @@ -77,7 +76,7 @@ export const userSchedulesSlice = createSlice({ state.saved[state.active].selected, action.payload ); - state.saved[state.active].courseSessions[action.payload] = {Lecture: "", Section: "", Color: GET_CALENDAR_COLOR(state.saved[state.active].numColors)}; + state.saved[state.active].courseSessions[action.payload] = {Lecture: "", Section: "", Color: getCalendarColor(state.saved[state.active].numColors)}; state.saved[state.active].numColors += 1; }, removeCourseFromActiveSchedule: (state, action: PayloadAction) => { diff --git a/apps/frontend/src/app/utils.tsx b/apps/frontend/src/app/utils.tsx index bf3c666..e6a93b9 100644 --- a/apps/frontend/src/app/utils.tsx +++ b/apps/frontend/src/app/utils.tsx @@ -2,7 +2,7 @@ import reactStringReplace from "react-string-replace"; import Link from "~/components/Link"; import { FCE, Schedule, Session, Time } from "./types"; import { AggregateFCEsOptions, filterFCEs } from "./fce"; -import { DEPARTMENT_MAP_NAME, DEPARTMENT_MAP_SHORTNAME } from "./constants"; +import { DEPARTMENT_MAP_NAME, DEPARTMENT_MAP_SHORTNAME, CALENDAR_COLORS, CALENDAR_COLORS_LIGHT } from "./constants"; import namecase from "namecase"; export const courseIdRegex = /([0-9]{2}-?[0-9]{3})/g; @@ -272,3 +272,10 @@ export function parseUnits(units: string): number { } return 0.0; } + +export const getCalendarColor = (i: number) => CALENDAR_COLORS[i % CALENDAR_COLORS.length] || ""; + +export const getCalendarColorLight = (color: string) => { + const index = CALENDAR_COLORS.indexOf(color); + return CALENDAR_COLORS_LIGHT[index]; +} \ No newline at end of file diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index 17b1183..eb744d8 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -8,7 +8,7 @@ import style from "react-big-calendar/lib/css/react-big-calendar.css"; import moment from "moment"; import { useAppSelector } from "~/app/hooks"; import { Course, Time } from "~/app/types"; -import { sessionToString } from "~/app/utils"; +import {getCalendarColorLight, sessionToString} from "~/app/utils"; import { CourseSessions, HoverSession, selectCourseSessionsInActiveSchedule, @@ -155,7 +155,7 @@ const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSe const hoverSection = selectedCourse?.schedules?.find(sched => sessionToString(sched) === selectedSemester) ?.sections.find(section => section.name === hoverSession["Section"]); - const hoverColor = "#9CA3AF"; + const hoverColor = getCalendarColorLight(`${selectedSessions[courseID]?.Color}`) || ""; if (hoverLecture) events.push(...getTimes(courseID, hoverLecture.name || "Lecture", hoverLecture.times, hoverColor)); diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index bbcb439..c9d3069 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -163,7 +163,7 @@ const SectionSelector = ({ courseIDs }: { courseIDs: string[] }) => { className={({active}) => { return classNames( "flex relative justify-center cursor-pointer select-none focus:outline-none", - "hover:bg-gray-50 py-1", + "hover:bg-gray-200 py-1", i === 0 ? "rounded-l-md pl-1" : "", i === sessions.length - 1 ? "rounded-r-md pr-1" : "", active ? "bg-indigo-600 text-gray-600" : "text-gray-900" From e46b92ba67cec40d12bb8ef6061dd0b1a54fea31 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sun, 1 Dec 2024 16:57:13 -0500 Subject: [PATCH 38/39] Show message when course not in selected semester --- .../src/components/SectionSelector.tsx | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/frontend/src/components/SectionSelector.tsx b/apps/frontend/src/components/SectionSelector.tsx index c9d3069..077a2b5 100644 --- a/apps/frontend/src/components/SectionSelector.tsx +++ b/apps/frontend/src/components/SectionSelector.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { useAppDispatch, useAppSelector } from "~/app/hooks"; import { ChevronUpDownIcon } from "@heroicons/react/24/outline"; import { Listbox, RadioGroup } from "@headlessui/react"; @@ -17,13 +17,13 @@ import { FlushedButton } from "~/components/Buttons"; const SectionSelector = ({ courseIDs }: { courseIDs: string[] }) => { const dispatch = useAppDispatch(); - const CourseDetails = useFetchCourseInfos(courseIDs); + const courseDetails = useFetchCourseInfos(courseIDs); const selectedSession = useAppSelector(selectSessionInActiveSchedule); const selectedCourseSessions = useAppSelector(selectCourseSessionsInActiveSchedule); const scheduleView = useAppSelector((state) => state.user.scheduleView); - const semesters = [...new Set(CourseDetails.flatMap(course => { + const semesters = [...new Set(courseDetails.flatMap(course => { const schedules: Schedule[] = course.schedules; if (schedules) { return schedules.map(schedule => sessionToString(schedule)); @@ -32,6 +32,19 @@ const SectionSelector = ({ courseIDs }: { courseIDs: string[] }) => { return compareSessions(stringToSession(a || ""), stringToSession(b || "")); }); + const coursesNotInSemester = courseIDs.filter((courseID) => { + const course = courseDetails.find(course => course.courseID === courseID); + const schedules: Schedule[] = course?.schedules || []; + return !schedules.some(schedule => sessionToString(schedule) === selectedSession); + }); + + useEffect(() => { + // Check if selected session is in the list of semesters + if (selectedSession.length > 0 && !semesters.includes(selectedSession)) { + dispatch(userSchedulesSlice.actions.updateActiveScheduleSession(stringToSession(""))); + } + }, [selectedSession, semesters]) + return (
    @@ -50,7 +63,9 @@ const SectionSelector = ({ courseIDs }: { courseIDs: string[] }) => { - {selectedSession.length === 0 ? ( + {semesters.length === 0 ? ( + Add courses + ) : selectedSession.length === 0 ? ( Select Semester ) : ( {
    +
    + {selectedSession.length > 0 && coursesNotInSemester.length > 0 && + `The following courses are not offered in the selected semester: ${coursesNotInSemester.join(", ")}.`} +
    { - CourseDetails.filter((course) => course.schedules?.some((sched: Schedule) => sessionToString(sched) === selectedSession)).map((course) => { + courseDetails.filter((course) => course.schedules?.some((sched: Schedule) => sessionToString(sched) === selectedSession)).map((course) => { const schedule: Schedule = course.schedules?.find((sched: Schedule) => sessionToString(sched) === selectedSession); const courseID = course.courseID; From 9b18bd61e709f309d5796bb8f1d7268d5b4a2fe1 Mon Sep 17 00:00:00 2001 From: Xavier Lien Date: Sun, 1 Dec 2024 17:01:55 -0500 Subject: [PATCH 39/39] Remove existing lecture in calendar on hover --- apps/frontend/src/components/ScheduleCalendar.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/frontend/src/components/ScheduleCalendar.tsx b/apps/frontend/src/components/ScheduleCalendar.tsx index eb744d8..8b16305 100644 --- a/apps/frontend/src/components/ScheduleCalendar.tsx +++ b/apps/frontend/src/components/ScheduleCalendar.tsx @@ -155,6 +155,8 @@ const getEvents = (CourseDetails: Course[], selectedSemester: string, selectedSe const hoverSection = selectedCourse?.schedules?.find(sched => sessionToString(sched) === selectedSemester) ?.sections.find(section => section.name === hoverSession["Section"]); + events = events.filter(event => event.title.slice(0, courseID.length) !== courseID); + const hoverColor = getCalendarColorLight(`${selectedSessions[courseID]?.Color}`) || ""; if (hoverLecture) events.push(...getTimes(courseID, hoverLecture.name || "Lecture", hoverLecture.times, hoverColor));