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 = (
-
-
-
- );
- } else if (!this.state.error) {
- const renderData = {
- courseData: this.state.courseData,
- scheduleNames: this.state.scheduleNames,
- };
+ if (loading) {
+ return (
+
+
+
+ );
+ }
- currentView = (
- <>
-
-
-
- {this.state.courseData.length === 0 ? (
-
-
-
- ) : (
- 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 (
+
+
+
+
+
+ );
+ }
- return (
-
- {SectionTableWrapped(index, renderData)}
-
- );
- }
- )
- )}
-
- >
- );
- } else {
- currentView = (
-
-
+ return (
+ <>
+
+
+
+ {courseData.length === 0 ? (
+
-
- );
- }
+ ) : (
+ 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;