From 38d229e5388c8750430e612db2a641beafcfa948 Mon Sep 17 00:00:00 2001 From: Dylan Hojnoski Date: Wed, 11 Sep 2024 15:13:51 -0400 Subject: [PATCH 1/9] Added classroom schedule to navbar and inputs for building and room on the page --- src/components/navbar.tsx | 4 ++ src/pages/classroom-schedules.tsx | 87 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/pages/classroom-schedules.tsx diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 9e78009..27c3d88 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -20,6 +20,10 @@ const PAGES = [ label: 'Transfer Courses', href: '/transfer-courses', }, + { + label: 'Classroom Schedules', + href: '/classroom-schedules', + }, { label: 'About', href: '/about', diff --git a/src/pages/classroom-schedules.tsx b/src/pages/classroom-schedules.tsx new file mode 100644 index 0000000..c1751d2 --- /dev/null +++ b/src/pages/classroom-schedules.tsx @@ -0,0 +1,87 @@ +import React, {useCallback, useRef, useState, useEffect} from 'react'; +import {Select, Box, Divider} from '@chakra-ui/react'; +import Head from 'next/head'; +import {observer} from 'mobx-react-lite'; +import {NextSeo} from 'next-seo'; +import CoursesTable from 'src/components/courses-table'; +import ErrorToaster from 'src/components/error-toaster'; +import useStore from 'src/lib/state/context'; +import Basket from 'src/components/basket'; +import ScrollTopDetector from 'src/components/scroll-top-detector'; +import CoursesSearchBar from 'src/components/search-bar/courses'; +import WeekView from '../components/basket/calendar/views/week'; + +const isFirstRender = typeof window === 'undefined'; + +const ClassroomSchedules = observer(() => { + const courseTableContainerRef = useRef(null); + const {apiState} = useStore(); + + const [rooms, setRooms] = useState([]); + + const handleScrollToTop = useCallback(() => { + if (courseTableContainerRef.current) { + courseTableContainerRef.current.scrollTop = 0; + } + }, []); + + const buildings = apiState.buildings; + let selectedBuilding: string; + let sections; + let selectedRoom; + + const handleBuildingSelect = useCallback(async (event: React.ChangeEvent) => { + selectedBuilding = event.target.value; + sections = apiState.sections.filter(section => section.buildingName === selectedBuilding); + + const availableRooms: string[] = []; + for (const section of sections) { + if (section.room !== null && !availableRooms.includes(section.room)) { + availableRooms.push(section.room); + } + } + + rooms.sort(); + setRooms(availableRooms); + }, []); + + const handleRoomSelect = useCallback(async (event: React.ChangeEvent) => { + selectedRoom = event.target.value; + }, []); + + return ( + <> + + + + + + + + ); +}); + +export default ClassroomSchedules; From d628ef51c1cfc3cec7165589cc25b4f97378827a Mon Sep 17 00:00:00 2001 From: Dylan Hojnoski Date: Wed, 18 Sep 2024 20:42:16 -0400 Subject: [PATCH 2/9] Have sections showing on calendar for the selected room --- src/components/navbar.tsx | 2 +- src/pages/classroom-schedules.tsx | 115 ++++++++++++++++++++++++++---- 2 files changed, 104 insertions(+), 13 deletions(-) diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 27c3d88..4294847 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -38,7 +38,7 @@ const getTermDisplayName = (term: IPotentialFutureTerm) => { return `${SEMESTER_DISPLAY_MAPPING[term.semester]} ${term.year}`; }; -const PATHS_THAT_REQUIRE_TERM_SELECTOR = new Set(['/', '/help/registration-script']); +const PATHS_THAT_REQUIRE_TERM_SELECTOR = new Set(['/', '/help/registration-script', '/classroom-schedules']); const Navbar = observer(() => { const router = useRouter(); diff --git a/src/pages/classroom-schedules.tsx b/src/pages/classroom-schedules.tsx index c1751d2..d0c4da2 100644 --- a/src/pages/classroom-schedules.tsx +++ b/src/pages/classroom-schedules.tsx @@ -1,15 +1,17 @@ -import React, {useCallback, useRef, useState, useEffect} from 'react'; -import {Select, Box, Divider} from '@chakra-ui/react'; +import React, {useCallback, useRef, useState, useEffect, useContext, useMemo} from 'react'; +import {Select, Box, Divider, useDisclosure, Skeleton, Table} from '@chakra-ui/react'; +import {format, add} from 'date-fns'; import Head from 'next/head'; import {observer} from 'mobx-react-lite'; import {NextSeo} from 'next-seo'; -import CoursesTable from 'src/components/courses-table'; -import ErrorToaster from 'src/components/error-toaster'; +import useCalendar, {CalendarViewType} from '@veccu/react-calendar'; import useStore from 'src/lib/state/context'; -import Basket from 'src/components/basket'; -import ScrollTopDetector from 'src/components/scroll-top-detector'; -import CoursesSearchBar from 'src/components/search-bar/courses'; +import CalendarToolbar from 'src/components/basket/calendar/toolbar'; +import MonthView from 'src/components/basket/calendar/views/month'; +import {type ICourseFromAPI, ISectionFromAPI, type ISectionFromAPIWithSchedule} from 'src/lib/api-types'; +import occurrenceGeneratorCache from 'src/lib/occurrence-generator-cache'; import WeekView from '../components/basket/calendar/views/week'; +import styles from '../components/basket/calendar/styles/calendar.module.scss'; const isFirstRender = typeof window === 'undefined'; @@ -17,7 +19,12 @@ const ClassroomSchedules = observer(() => { const courseTableContainerRef = useRef(null); const {apiState} = useStore(); + const calendar = useCalendar({defaultViewType: CalendarViewType.Week}); + const [rooms, setRooms] = useState([]); + const [sectionsInRoom, setSectionsInRoom] = useState>([]); + + let sectionsInBuilding: ISectionFromAPIWithSchedule[] = []; const handleScrollToTop = useCallback(() => { if (courseTableContainerRef.current) { @@ -27,28 +34,65 @@ const ClassroomSchedules = observer(() => { const buildings = apiState.buildings; let selectedBuilding: string; - let sections; - let selectedRoom; + let selectedRoom: string; const handleBuildingSelect = useCallback(async (event: React.ChangeEvent) => { selectedBuilding = event.target.value; - sections = apiState.sections.filter(section => section.buildingName === selectedBuilding); + sectionsInBuilding = apiState.sectionsWithParsedSchedules.filter(section => section.buildingName === selectedBuilding); const availableRooms: string[] = []; - for (const section of sections) { + for (const section of sectionsInBuilding) { if (section.room !== null && !availableRooms.includes(section.room)) { availableRooms.push(section.room); } } - rooms.sort(); + availableRooms.sort(); setRooms(availableRooms); }, []); const handleRoomSelect = useCallback(async (event: React.ChangeEvent) => { selectedRoom = event.target.value; + + const sections = sectionsInBuilding.filter(section => section.room === selectedRoom) + .map(section => ({...section, course: apiState.courseById.get(section.courseId)!})); + setSectionsInRoom(sections); }, []); + const bodyWithEvents = useMemo(() => ({ + ...calendar.body, + value: calendar.body.value.map(week => ({ + ...week, + value: week.value.map(day => { + const events = []; + + const start = day.value; + const end = add(day.value, {days: 1}); + + for (const section of sectionsInRoom ?? []) { + if (section.parsedTime) { + for (const occurrence of occurrenceGeneratorCache(JSON.stringify(section.time), start, end, section.parsedTime)) { + events.push({ + section, + start: occurrence.date as Date, + end: occurrence.end as Date ?? new Date(), + }); + } + } + } + + return { + ...day, + events: events.sort((a, b) => a.start.getTime() - b.start.getTime()).map(event => ({ + ...event, + key: `${event.section.id}-${event.start.toISOString()}-${event.end.toISOString()}`, + label: `${event.section.course.title} ${event.section.section} (${event.section.course.subject}${event.section.course.crse})`, + })), + }; + }), + })), + }), [calendar.body, sectionsInRoom]); + return ( <> { description='A listing of when classrooms have classes scheduled in them' /> + + {isFirstRender && ( + <> + + + + + + )} + + + + + + + { + calendar.view.isMonthView && ( + { + console.log('hello'); + }}/> + ) + } + + { + calendar.view.isWeekView && ( + { + console.log('hello'); + }}/> + ) + } +
+
+ ); }); From b961d4cdb21b91e2b69638dd52405312acdea285 Mon Sep 17 00:00:00 2001 From: Dylan Hojnoski Date: Wed, 18 Sep 2024 21:11:09 -0400 Subject: [PATCH 3/9] Added some styling and fixed endpoints of first render --- src/pages/classroom-schedules.tsx | 141 +++++++++++++++++------------- 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/src/pages/classroom-schedules.tsx b/src/pages/classroom-schedules.tsx index d0c4da2..42947c9 100644 --- a/src/pages/classroom-schedules.tsx +++ b/src/pages/classroom-schedules.tsx @@ -1,5 +1,5 @@ import React, {useCallback, useRef, useState, useEffect, useContext, useMemo} from 'react'; -import {Select, Box, Divider, useDisclosure, Skeleton, Table} from '@chakra-ui/react'; +import {Select, Box, Divider, useDisclosure, Skeleton, Table, HStack, Flex} from '@chakra-ui/react'; import {format, add} from 'date-fns'; import Head from 'next/head'; import {observer} from 'mobx-react-lite'; @@ -24,6 +24,21 @@ const ClassroomSchedules = observer(() => { const [rooms, setRooms] = useState([]); const [sectionsInRoom, setSectionsInRoom] = useState>([]); + useEffect(() => { + apiState.setSingleFetchEndpoints(['buildings']); + + if (apiState.selectedTerm?.isFuture) { + apiState.setRecurringFetchEndpoints(['courses']); + } else { + apiState.setRecurringFetchEndpoints(['courses', 'sections']); + } + + return () => { + apiState.setSingleFetchEndpoints([]); + apiState.setRecurringFetchEndpoints([]); + }; + }, [apiState.selectedTerm, apiState]); + let sectionsInBuilding: ISectionFromAPIWithSchedule[] = []; const handleScrollToTop = useCallback(() => { @@ -111,66 +126,74 @@ const ClassroomSchedules = observer(() => { )} - - - - - - - - - { - calendar.view.isMonthView && ( - { - console.log('hello'); - }}/> - ) - } + + + + + + + + + + + + + +
+ { + calendar.view.isMonthView && ( + { + console.log('hello'); + }}/> + ) + } - { - calendar.view.isWeekView && ( - { - console.log('hello'); - }}/> - ) - } -
-
+ { + calendar.view.isWeekView && ( + { + console.log('hello'); + }}/> + ) + } + + + ); }); From e762b67818999cad0e04fa8272368b0c81e06d3d Mon Sep 17 00:00:00 2001 From: Dylan Hojnoski Date: Mon, 23 Sep 2024 12:57:21 -0400 Subject: [PATCH 4/9] Fixed having too many classes in a time slot --- src/pages/classroom-schedules.tsx | 78 ++++++++++++++----------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/src/pages/classroom-schedules.tsx b/src/pages/classroom-schedules.tsx index 42947c9..522656a 100644 --- a/src/pages/classroom-schedules.tsx +++ b/src/pages/classroom-schedules.tsx @@ -1,5 +1,5 @@ -import React, {useCallback, useRef, useState, useEffect, useContext, useMemo} from 'react'; -import {Select, Box, Divider, useDisclosure, Skeleton, Table, HStack, Flex} from '@chakra-ui/react'; +import React, {useCallback, useState, useEffect, useMemo} from 'react'; +import {Select, Skeleton, Table, HStack, Flex} from '@chakra-ui/react'; import {format, add} from 'date-fns'; import Head from 'next/head'; import {observer} from 'mobx-react-lite'; @@ -8,7 +8,7 @@ import useCalendar, {CalendarViewType} from '@veccu/react-calendar'; import useStore from 'src/lib/state/context'; import CalendarToolbar from 'src/components/basket/calendar/toolbar'; import MonthView from 'src/components/basket/calendar/views/month'; -import {type ICourseFromAPI, ISectionFromAPI, type ISectionFromAPIWithSchedule} from 'src/lib/api-types'; +import {type ICourseFromAPI, type ISectionFromAPIWithSchedule} from 'src/lib/api-types'; import occurrenceGeneratorCache from 'src/lib/occurrence-generator-cache'; import WeekView from '../components/basket/calendar/views/week'; import styles from '../components/basket/calendar/styles/calendar.module.scss'; @@ -16,11 +16,8 @@ import styles from '../components/basket/calendar/styles/calendar.module.scss'; const isFirstRender = typeof window === 'undefined'; const ClassroomSchedules = observer(() => { - const courseTableContainerRef = useRef(null); const {apiState} = useStore(); - const calendar = useCalendar({defaultViewType: CalendarViewType.Week}); - const [rooms, setRooms] = useState([]); const [sectionsInRoom, setSectionsInRoom] = useState>([]); @@ -41,12 +38,6 @@ const ClassroomSchedules = observer(() => { let sectionsInBuilding: ISectionFromAPIWithSchedule[] = []; - const handleScrollToTop = useCallback(() => { - if (courseTableContainerRef.current) { - courseTableContainerRef.current.scrollTop = 0; - } - }, []); - const buildings = apiState.buildings; let selectedBuilding: string; let selectedRoom: string; @@ -87,6 +78,10 @@ const ClassroomSchedules = observer(() => { for (const section of sectionsInRoom ?? []) { if (section.parsedTime) { for (const occurrence of occurrenceGeneratorCache(JSON.stringify(section.time), start, end, section.parsedTime)) { + if (events.filter(event => event.start.toISOString() === occurrence.date.toISOString()).length > 3) { + break; + } + events.push({ section, start: occurrence.date as Date, @@ -111,7 +106,7 @@ const ClassroomSchedules = observer(() => { return ( <> @@ -119,8 +114,6 @@ const ClassroomSchedules = observer(() => { {isFirstRender && ( <> - - )} @@ -128,33 +121,34 @@ const ClassroomSchedules = observer(() => { - - - - - - - + + + + + + + + Date: Wed, 6 Nov 2024 15:18:18 -0500 Subject: [PATCH 5/9] Fixed navbar spacing --- src/components/navbar.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 4d08890..253932b 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -23,7 +23,7 @@ const PAGES = [ href: '/transfer-courses', }, { - label: 'Classroom Schedules', + label: 'Classrooms', href: '/classroom-schedules', }, { @@ -66,7 +66,7 @@ const Navbar = observer(() => { return ( - + @@ -91,6 +91,7 @@ const Navbar = observer(() => { mr={6} mt={{base: 4, md: 0}} color='inherit' + whiteSpace={'nowrap'} > {page.label} @@ -104,7 +105,7 @@ const Navbar = observer(() => { width={{base: 'full', md: 'auto'}} mt={{base: 4, md: 0}} alignItems='center' - flex={{lg: 1}} + flexGrow={1} visibility={(shouldShowCourseSearch || shouldShowTransferSearch) ? 'visible' : 'hidden'} > { @@ -125,8 +126,10 @@ const Navbar = observer(() => { { From bd1a3d7c28c9652eef277c665fd7063d616acff8 Mon Sep 17 00:00:00 2001 From: gmfenech Date: Thu, 7 Nov 2024 13:44:40 -0500 Subject: [PATCH 6/9] fix calendar start date --- .husky/pre-commit | 3 ++- src/pages/classroom-schedules.tsx | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 3723623..8c0fe37 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1,2 @@ -yarn lint-staged +#!/bin/sh +yarn lint-staged \ No newline at end of file diff --git a/src/pages/classroom-schedules.tsx b/src/pages/classroom-schedules.tsx index 522656a..9b20a68 100644 --- a/src/pages/classroom-schedules.tsx +++ b/src/pages/classroom-schedules.tsx @@ -65,6 +65,19 @@ const ClassroomSchedules = observer(() => { setSectionsInRoom(sections); }, []); + const firstDate = useMemo(() => { + const dates = sectionsInRoom + .map(section => section.parsedTime?.firstDate?.date) + .filter(Boolean) as Date[]; + return dates.sort((a, b) => a.getTime() - b.getTime())[0]; + }, [sectionsInRoom]); + + useEffect(() => { + if (firstDate) { + calendar.navigation.setDate(firstDate); + } + }, [firstDate, apiState.selectedTerm]); + const bodyWithEvents = useMemo(() => ({ ...calendar.body, value: calendar.body.value.map(week => ({ From 1f56ab98e97ee26b3b82c3f6816ee9cbb1d6bb42 Mon Sep 17 00:00:00 2001 From: Dylan Hojnoski Date: Tue, 12 Nov 2024 22:33:30 -0500 Subject: [PATCH 7/9] Cleaned up code --- src/pages/classroom-schedules.tsx | 32 ++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/pages/classroom-schedules.tsx b/src/pages/classroom-schedules.tsx index 9b20a68..c4d8d17 100644 --- a/src/pages/classroom-schedules.tsx +++ b/src/pages/classroom-schedules.tsx @@ -30,6 +30,8 @@ const ClassroomSchedules = observer(() => { apiState.setRecurringFetchEndpoints(['courses', 'sections']); } + setSectionsInRoom([]); + return () => { apiState.setSingleFetchEndpoints([]); apiState.setRecurringFetchEndpoints([]); @@ -66,17 +68,27 @@ const ClassroomSchedules = observer(() => { }, []); const firstDate = useMemo(() => { - const dates = sectionsInRoom - .map(section => section.parsedTime?.firstDate?.date) - .filter(Boolean) as Date[]; - return dates.sort((a, b) => a.getTime() - b.getTime())[0]; + let start = new Date(); + for (const section of sectionsInRoom) { + if (section.parsedTime?.firstDate?.date && section.parsedTime?.lastDate?.date) { + if (section.parsedTime?.firstDate?.date <= new Date() && section.parsedTime?.lastDate?.date >= new Date()) { + return new Date(); + } + + if (section.parsedTime?.firstDate?.date < start) { + start = section.parsedTime?.firstDate?.date; + } + } + } + + return start; }, [sectionsInRoom]); useEffect(() => { if (firstDate) { calendar.navigation.setDate(firstDate); } - }, [firstDate, apiState.selectedTerm]); + }, [firstDate]); const bodyWithEvents = useMemo(() => ({ ...calendar.body, @@ -181,9 +193,8 @@ const ClassroomSchedules = observer(() => { { - console.log('hello'); - }}/> + onEventClick={() => undefined} + /> ) } @@ -192,9 +203,8 @@ const ClassroomSchedules = observer(() => { { - console.log('hello'); - }}/> + onEventClick={() => undefined} + /> ) } From c3f422cacc8ab7a66aab77215353f86e2e44936f Mon Sep 17 00:00:00 2001 From: Dylan Hojnoski Date: Sat, 16 Nov 2024 21:19:20 -0500 Subject: [PATCH 8/9] Reset calendar on builing change --- src/pages/classroom-schedules.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/classroom-schedules.tsx b/src/pages/classroom-schedules.tsx index c4d8d17..1d30c35 100644 --- a/src/pages/classroom-schedules.tsx +++ b/src/pages/classroom-schedules.tsx @@ -57,6 +57,7 @@ const ClassroomSchedules = observer(() => { availableRooms.sort(); setRooms(availableRooms); + setSectionsInRoom([]); }, []); const handleRoomSelect = useCallback(async (event: React.ChangeEvent) => { From 046e87a51c9f74ba5f3e030da6b2f661bb4fe675 Mon Sep 17 00:00:00 2001 From: Dylan Hojnoski Date: Sat, 16 Nov 2024 21:32:00 -0500 Subject: [PATCH 9/9] Fixed calendar start date on forward looking semesters --- src/pages/classroom-schedules.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/classroom-schedules.tsx b/src/pages/classroom-schedules.tsx index 1d30c35..7ae69c0 100644 --- a/src/pages/classroom-schedules.tsx +++ b/src/pages/classroom-schedules.tsx @@ -70,15 +70,17 @@ const ClassroomSchedules = observer(() => { const firstDate = useMemo(() => { let start = new Date(); + if (sectionsInRoom.length > 0 && sectionsInRoom[0].parsedTime?.firstDate?.date) { + start = sectionsInRoom[0].parsedTime?.firstDate?.date; + } + for (const section of sectionsInRoom) { if (section.parsedTime?.firstDate?.date && section.parsedTime?.lastDate?.date) { if (section.parsedTime?.firstDate?.date <= new Date() && section.parsedTime?.lastDate?.date >= new Date()) { return new Date(); } - if (section.parsedTime?.firstDate?.date < start) { - start = section.parsedTime?.firstDate?.date; - } + start = section.parsedTime?.firstDate?.date; } }