From 5f04211684ef5537edc38f11b29019b09bf451a2 Mon Sep 17 00:00:00 2001 From: Aponia Date: Thu, 5 Oct 2023 22:14:12 -0700 Subject: [PATCH 1/2] refactor: made course pane components functional (#714) --- .../RightPane/CoursePane/CoursePaneRoot.tsx | 113 +++---- .../RightPane/CoursePane/CourseRenderPane.tsx | 295 +++++++----------- 2 files changed, 169 insertions(+), 239 deletions(-) diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/CoursePaneRoot.tsx b/apps/antalmanac/src/components/RightPane/CoursePane/CoursePaneRoot.tsx index 0092e2fac..f953dda83 100644 --- a/apps/antalmanac/src/components/RightPane/CoursePane/CoursePaneRoot.tsx +++ b/apps/antalmanac/src/components/RightPane/CoursePane/CoursePaneRoot.tsx @@ -1,5 +1,4 @@ -import { withStyles } from '@material-ui/core/styles'; -import { PureComponent } from 'react'; +import { useCallback, useEffect, useReducer } from 'react'; import RightPaneStore from '../RightPaneStore'; import CoursePaneButtonRow from './CoursePaneButtonRow'; @@ -9,50 +8,10 @@ import analyticsEnum, { logAnalytics } from '$lib/analytics'; import { openSnackbar } from '$actions/AppStoreActions'; import { clearCache } from '$lib/course-helpers'; -const styles = { - container: { - height: '100%', - }, -}; +function RightPane() { + const [key, forceUpdate] = useReducer((currentCount) => currentCount + 1, 0); -class RightPane extends PureComponent { - // When a user clicks the refresh button in CoursePaneButtonRow, - // we increment the refresh state by 1. - // Since it's the key for CourseRenderPane, it triggers a rerender - // and reloads the latest course data - state = { - refresh: 0, - }; - - returnToSearchBarEvent = (event: KeyboardEvent) => { - if ( - !(RightPaneStore.getDoDisplaySearch() || RightPaneStore.getOpenSpotAlertPopoverActive()) && - (event.key === 'Backspace' || event.key === 'Escape') - ) { - event.preventDefault(); - RightPaneStore.toggleSearch(); - this.forceUpdate(); - } - }; - - componentDidMount() { - document.addEventListener('keydown', this.returnToSearchBarEvent, false); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.returnToSearchBarEvent, false); - } - - refreshSearch = () => { - logAnalytics({ - category: analyticsEnum.classSearch.title, - action: analyticsEnum.classSearch.actions.REFRESH, - }); - clearCache(); - this.setState({ refresh: this.state.refresh + 1 }); - }; - - toggleSearch = () => { + const toggleSearch = useCallback(() => { if ( RightPaneStore.getFormData().ge !== 'ANY' || RightPaneStore.getFormData().deptValue !== 'ALL' || @@ -60,31 +19,57 @@ class RightPane extends PureComponent { RightPaneStore.getFormData().instructor !== '' ) { RightPaneStore.toggleSearch(); - this.forceUpdate(); + forceUpdate(); } else { openSnackbar( 'error', `Please provide one of the following: Department, GE, Course Code/Range, or Instructor` ); } - }; + }, []); + + const refreshSearch = useCallback(() => { + logAnalytics({ + category: analyticsEnum.classSearch.title, + action: analyticsEnum.classSearch.actions.REFRESH, + }); + clearCache(); + forceUpdate(); + }, []); + + useEffect(() => { + const handleReturnToSearch = (event: KeyboardEvent) => { + if ( + !(RightPaneStore.getDoDisplaySearch() || RightPaneStore.getOpenSpotAlertPopoverActive()) && + (event.key === 'Backspace' || event.key === 'Escape') + ) { + event.preventDefault(); + RightPaneStore.toggleSearch(); + forceUpdate(); + } + }; + + document.addEventListener('keydown', handleReturnToSearch, false); + + return () => { + document.removeEventListener('keydown', handleReturnToSearch, false); + }; + }, []); - render() { - return ( -
- - {RightPaneStore.getDoDisplaySearch() ? ( - - ) : ( - - )} -
- ); - } + return ( +
+ + {RightPaneStore.getDoDisplaySearch() ? ( + + ) : ( + + )} +
+ ); } -export default withStyles(styles)(RightPane); +export default RightPane; diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx b/apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx index a5d9b28a2..2bd16b5b5 100644 --- a/apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx +++ b/apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx @@ -1,8 +1,6 @@ -import { IconButton, Theme } from '@material-ui/core'; -import { withStyles } from '@material-ui/core/styles'; -import { ClassNameMap, Styles } from '@material-ui/core/styles/withStyles'; +import { IconButton } from '@material-ui/core'; import CloseIcon from '@material-ui/icons/Close'; -import React, { PureComponent } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import LazyLoad from 'react-lazyload'; import { Alert } from '@mui/material'; @@ -21,62 +19,7 @@ import { isDarkMode, queryWebsocMultiple } from '$lib/helpers'; import analyticsEnum from '$lib/analytics'; import { queryWebsoc } from '$lib/course-helpers'; -const styles: Styles = (theme) => ({ - course: { - paddingLeft: theme.spacing(2), - paddingRight: theme.spacing(2), - [theme.breakpoints.up('sm')]: { - paddingLeft: theme.spacing(3), - paddingRight: theme.spacing(3), - }, - paddingTop: theme.spacing(), - paddingBottom: theme.spacing(), - display: 'flex', - alignItems: 'center', - flexWrap: 'wrap', - minHeight: theme.spacing(6), - cursor: 'pointer', - }, - text: { - flexGrow: 1, - display: 'inline', - width: '100%', - }, - ad: { - flexGrow: 1, - display: 'inline', - width: '100%', - }, - icon: { - cursor: 'pointer', - marginLeft: theme.spacing(), - }, - root: { - height: '100%', - overflowY: 'scroll', - position: 'relative', - }, - noResultsDiv: { - height: '100%', - width: '100%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }, - loadingGifStyle: { - height: '100%', - width: '100%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }, - spacing: { - height: '50px', - marginBottom: '5px', - }, -}); - -const flattenSOCObject = (SOCObject: WebsocAPIResponse): (WebsocSchool | WebsocDepartment | AACourse)[] => { +function flattenSOCObject(SOCObject: WebsocAPIResponse): (WebsocSchool | WebsocDepartment | AACourse)[] { const courseColors = AppStore.getAddedCourses().reduce((accumulator, { section }) => { accumulator[section.sectionCode] = section.color; return accumulator; @@ -192,135 +135,137 @@ const SectionTableWrapped = ( return
{component}
; }; -interface CourseRenderPaneProps { - classes: ClassNameMap; -} +export function CourseRenderPane() { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); + const [scheduleNames, setScheduleNames] = useState(AppStore.getScheduleNames()); + const [courseData, setCourseData] = useState<(WebsocSchool | WebsocDepartment | AACourse)[]>([]); -interface CourseRenderPaneState { - courseData: (WebsocSchool | WebsocDepartment | AACourse)[]; - loading: boolean; - error: boolean; - scheduleNames: string[]; -} + const loadCourses = useCallback(async () => { + setLoading(true); -class CourseRenderPane extends PureComponent { - state: CourseRenderPaneState = { - courseData: [], - loading: true, - error: false, - scheduleNames: AppStore.getScheduleNames(), - }; + const formData = RightPaneStore.getFormData(); - loadCourses = () => { - this.setState({ loading: true }, async () => { - const formData = RightPaneStore.getFormData(); + const params = { + department: formData.deptValue, + term: formData.term, + ge: formData.ge, + courseNumber: formData.courseNumber, + sectionCodes: formData.sectionCode, + instructorName: formData.instructor, + units: formData.units, + endTime: formData.endTime, + startTime: formData.startTime, + fullCourses: formData.coursesFull, + building: formData.building, + room: formData.room, + division: formData.division, + }; - const params = { - department: formData.deptValue, - term: formData.term, - ge: formData.ge, - courseNumber: formData.courseNumber, - sectionCodes: formData.sectionCode, - instructorName: formData.instructor, - units: formData.units, - endTime: formData.endTime, - startTime: formData.startTime, - fullCourses: formData.coursesFull, - building: formData.building, - room: formData.room, - division: formData.division, - }; + try { + // If params has a comma, it means that we are searching for multiple units. + const socObject = params.units.includes(',') + ? await queryWebsocMultiple(params, 'units') + : await queryWebsoc(params); - try { - let jsonResp; - if (params.units.includes(',')) { - jsonResp = await queryWebsocMultiple(params, 'units'); - } else { - jsonResp = await queryWebsoc(params); - } - this.setState({ - loading: false, - error: false, - courseData: flattenSOCObject(jsonResp), - }); - } catch (error) { - this.setState({ - loading: false, - error: true, - }); - } - }); - }; + setError(false); + setCourseData(flattenSOCObject(socObject)); + } catch (error) { + setError(true); + } finally { + setLoading(false); + } + }, []); - componentDidMount() { - this.loadCourses(); - AppStore.on('scheduleNamesChange', this.updateScheduleNames); - } + useEffect(() => { + loadCourses(); + }, []); - componentWillUnmount() { - AppStore.removeListener('scheduleNamesChange', this.updateScheduleNames); - } + useEffect(() => { + const updateScheduleNames = () => { + setScheduleNames(AppStore.getScheduleNames()); + }; - updateScheduleNames = () => { - this.setState({ scheduleNames: AppStore.getScheduleNames() }); - }; + AppStore.on('scheduleNamesChange', updateScheduleNames); - render() { - const { classes } = this.props; - let currentView; + return () => { + AppStore.off('scheduleNamesChange', updateScheduleNames); + }; + }, []); - if (this.state.loading) { - currentView = ( -
- Loading courses -
- ); - } else if (!this.state.error) { - const renderData = { - courseData: this.state.courseData, - scheduleNames: this.state.scheduleNames, - }; + if (loading) { + return ( +
+ Loading courses +
+ ); + } - currentView = ( - <> - -
-
- {this.state.courseData.length === 0 ? ( -
- No Results Found -
- ) : ( - this.state.courseData.map( - (_: WebsocSchool | WebsocDepartment | AACourse, index: number) => { - let heightEstimate = 200; - if ((this.state.courseData[index] as AACourse).sections !== undefined) - heightEstimate = - (this.state.courseData[index] as AACourse).sections.length * 60 + 20 + 40; + if (error) { + return ( +
+
+ No Results Found +
+
+ ); + } - return ( - - {SectionTableWrapped(index, renderData)} - - ); - } - ) - )} -
- - ); - } else { - currentView = ( -
-
+ return ( + <> + +
+
+ {courseData.length === 0 ? ( +
No Results Found
-
- ); - } + ) : ( + courseData.map((_, index: number) => { + let heightEstimate = 200; + if ((courseData[index] as AACourse).sections !== undefined) + heightEstimate = (courseData[index] as AACourse).sections.length * 60 + 20 + 40; - return currentView; - } + return ( + + {SectionTableWrapped(index, { courseData, scheduleNames })} + + ); + }) + )} +
+ + ); } -export default withStyles(styles)(CourseRenderPane); +export default CourseRenderPane; From 308bcb5f2da29090d6f5e195ac490622994afa86 Mon Sep 17 00:00:00 2001 From: Aponia Date: Sat, 7 Oct 2023 12:32:25 -0700 Subject: [PATCH 2/2] fix: remove incorrect margin x and y (#723) Co-authored-by: Kevin Wu --- apps/antalmanac/src/components/Map/Map.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/antalmanac/src/components/Map/Map.tsx b/apps/antalmanac/src/components/Map/Map.tsx index 242f478bb..2f33e5715 100644 --- a/apps/antalmanac/src/components/Map/Map.tsx +++ b/apps/antalmanac/src/components/Map/Map.tsx @@ -217,7 +217,7 @@ export default function CourseMap() { {/* Menu floats above the map. */} - + {days.map((day) => (