diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/CoursePaneRoot.tsx b/apps/antalmanac/src/components/RightPane/CoursePane/CoursePaneRoot.tsx index 7ed8ad365..f71613d22 100644 --- a/apps/antalmanac/src/components/RightPane/CoursePane/CoursePaneRoot.tsx +++ b/apps/antalmanac/src/components/RightPane/CoursePane/CoursePaneRoot.tsx @@ -13,7 +13,7 @@ import { Grades } from '$lib/grades'; import { WebSOC } from '$lib/websoc'; import { useCoursePaneStore } from '$stores/CoursePaneStore'; -function RightPane() { +export function CoursePaneRoot() { const { key, forceUpdate, searchIsDisplayed, displaySearch, displaySections } = useCoursePaneStore(); const handleSearch = useCallback(() => { @@ -64,5 +64,3 @@ function 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 2b2feb0d8..f8d313b15 100644 --- a/apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx +++ b/apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx @@ -207,6 +207,7 @@ export default function CourseRenderPane(props: { id?: number }) { building: formData.building, room: formData.room, division: formData.division, + excludeRestrictionCodes: formData.excludeRestrictionCodes.split('').join(','), // comma delimited string (e.g. ABC -> A,B,C) }; const gradesQueryParams = { diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch.tsx b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch.tsx deleted file mode 100644 index 9a323c959..000000000 --- a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch.tsx +++ /dev/null @@ -1,393 +0,0 @@ -import { - Box, - Button, - Collapse, - FormControl, - FormControlLabel, - InputLabel, - MenuItem, - Select, - Switch, - TextField, - Theme, - Typography, -} from '@material-ui/core'; -import { withStyles } from '@material-ui/core/styles'; -import { ClassNameMap, Styles } from '@material-ui/core/styles/withStyles'; -import { ExpandLess, ExpandMore } from '@material-ui/icons'; -import { ChangeEvent, PureComponent } from 'react'; - -import RightPaneStore from '../../RightPaneStore'; - -import { getLocalStorageAdvanced, setLocalStorageAdvanced } from '$lib/localStorage'; - -const styles: Styles = { - fieldContainer: { - display: 'flex', - gap: '1.5rem', - flexWrap: 'wrap', - paddingLeft: '8px', - paddingRight: '8px', - marginBottom: '1rem', - }, - units: { - width: '80px', - }, - timePicker: { - width: '130px', - }, - onlineSwitch: { - margin: 0, - justifyContent: 'flex-end', - left: 0, - }, -}; - -interface AdvancedSearchTextFieldsProps { - classes?: ClassNameMap; -} - -interface AdvancedSearchTextFieldsState { - instructor: string; - units: string; - endTime: string; - startTime: string; - coursesFull: string; - building: string; - room: string; - division: string; -} - -interface AdvancedSearchProps { - classes: ClassNameMap; -} - -interface AdvancedSearchState { - expandAdvanced: boolean; -} - -class UnstyledAdvancedSearchTextFields extends PureComponent< - AdvancedSearchTextFieldsProps, - AdvancedSearchTextFieldsState -> { - state = { - instructor: RightPaneStore.getFormData().instructor, - units: RightPaneStore.getFormData().units, - endTime: RightPaneStore.getFormData().endTime, - startTime: RightPaneStore.getFormData().startTime, - coursesFull: RightPaneStore.getFormData().coursesFull, - building: RightPaneStore.getFormData().building, - room: RightPaneStore.getFormData().room, - division: RightPaneStore.getFormData().division, - }; - - componentDidMount() { - RightPaneStore.on('formReset', this.resetField); - } - - componentWillUnmount() { - RightPaneStore.removeListener('formReset', this.resetField); - } - - resetField = () => { - this.setState({ - instructor: RightPaneStore.getFormData().instructor, - units: RightPaneStore.getFormData().units, - endTime: RightPaneStore.getFormData().endTime, - startTime: RightPaneStore.getFormData().startTime, - coursesFull: RightPaneStore.getFormData().coursesFull, - building: RightPaneStore.getFormData().building, - room: RightPaneStore.getFormData().room, - division: RightPaneStore.getFormData().division, - }); - }; - - handleChange = (name: string) => (event: ChangeEvent<{ checked?: boolean; name?: string; value: unknown }>) => { - const stateObj = { url: 'url' }; - const url = new URL(window.location.href); - const urlParam = new URLSearchParams(url.search); - - if (name === 'online') { - if (event.target.checked) { - this.setState({ building: 'ON', room: 'LINE' }); - RightPaneStore.updateFormValue('building', 'ON'); - RightPaneStore.updateFormValue('room', 'LINE'); - - urlParam.set('building', 'ON'); - urlParam.set('room', 'LINE'); - } else { - this.setState({ building: '', room: '' }); - RightPaneStore.updateFormValue('building', ''); - RightPaneStore.updateFormValue('room', ''); - - urlParam.delete('building'); - urlParam.delete('room'); - } - } else { - const value = event.target.value; - this.setState({ [name]: event.target.value } as unknown as AdvancedSearchTextFieldsState); - - if (value !== '') { - urlParam.set(name, String(value)); - } else { - urlParam.delete(name); - } - - RightPaneStore.updateFormValue(name, event.target.value as string); - } - - const param = urlParam.toString(); - const new_url = `${param.trim() ? '?' : ''}${param}`; - history.replaceState(stateObj, 'url', '/' + new_url); - }; - - /** - * UPDATE (6-28-19): Transferred course code and course number search boxes to - * separate classes. - */ - render() { - const { classes } = this.props; - - // List of times from 2:00am-11:00pm - const menuItemTimes = [ - ...[...Array(10).keys()].map((v) => `${v + 2}:00am`), - '12:00pm', - ...[...Array(11).keys()].map((v) => `${v + 1}:00pm`), - ]; - // Creates a MenuItem for time selection - const createdMenuItemTime = (time: string) => ( - - {time ? time : None} - - ); - // Build arrays of MenuItem elements for time selection - const startsAfterMenuItems = ['', '1:00am', ...menuItemTimes].map((time) => createdMenuItemTime(time)); - const endsBeforeMenuItems = ['', ...menuItemTimes].map((time) => createdMenuItemTime(time)); - - return ( - - - - - - - Class Full Option - - - - - - Course Level - - - - - - Starts After - - - - - Ends Before - - - - - } - label="Online Only" - labelPlacement="top" - className={classes?.onlineSwitch} - /> - - - - - - ); - } -} - -const AdvancedSearchTextFields = withStyles(styles)(UnstyledAdvancedSearchTextFields); - -const parentStyles = { - container: { - display: 'inline-flex', - marginTop: 10, - marginBottom: 10, - cursor: 'pointer', - - '& > div': { - marginRight: 5, - }, - }, -}; - -class AdvancedSearch extends PureComponent { - constructor(props: AdvancedSearchProps) { - super(props); - - let advanced = false; - - if (typeof Storage !== 'undefined') { - advanced = getLocalStorageAdvanced() === 'expanded'; - } - - const formData = RightPaneStore.getFormData(); - const defaultFormData = RightPaneStore.getDefaultFormData(); - for (const [key, value] of Object.entries(formData)) { - if (key === 'deptLabel' || key === 'deptValue') { - continue; - } - - if (defaultFormData[key] != value) { - advanced = true; - break; - } - } - - this.state = { - expandAdvanced: advanced, - }; - } - - componentDidMount() { - RightPaneStore.on('formReset', this.resetParams); - } - - resetParams() { - const stateObj = { url: 'url' }; - const url = new URL(window.location.href); - const urlParam = new URLSearchParams(url.search); - - const formData = RightPaneStore.getFormData(); - for (const key of Object.keys(formData)) { - if (key === 'deptLabel' || key === 'deptValue') { - continue; - } - - urlParam.delete(key); - } - - const param = urlParam.toString(); - const new_url = `${param.trim() ? '?' : ''}${param}`; - history.replaceState(stateObj, 'url', '/' + new_url); - } - - handleExpand = () => { - const nextExpansionState = !this.state.expandAdvanced; - setLocalStorageAdvanced(nextExpansionState ? 'expanded' : 'notexpanded'); - this.setState({ expandAdvanced: nextExpansionState }); - }; - - render() { - return ( - <> - - - - - - ); - } -} - -export default withStyles(parentStyles)(AdvancedSearch); diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch/AdvancedSearch.tsx b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch/AdvancedSearch.tsx new file mode 100644 index 000000000..f614724dc --- /dev/null +++ b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch/AdvancedSearch.tsx @@ -0,0 +1,112 @@ +import { Button, Collapse, Typography } from '@material-ui/core'; +import { withStyles } from '@material-ui/core/styles'; +import { ClassNameMap } from '@material-ui/core/styles/withStyles'; +import { ExpandLess, ExpandMore } from '@material-ui/icons'; +import { PureComponent } from 'react'; + +import RightPaneStore from '../../../RightPaneStore'; + +import { AdvancedSearchTextFields } from '$components/RightPane/CoursePane/SearchForm/AdvancedSearch/AdvancedSearchTextFields'; +import { getLocalStorageAdvanced, setLocalStorageAdvanced } from '$lib/localStorage'; + +const parentStyles = { + container: { + display: 'inline-flex', + marginTop: 10, + marginBottom: 10, + cursor: 'pointer', + + '& > div': { + marginRight: 5, + }, + }, +}; + +interface AdvancedSearchProps { + classes: ClassNameMap; +} + +interface AdvancedSearchState { + expandAdvanced: boolean; +} + +class AdvancedSearch extends PureComponent { + constructor(props: AdvancedSearchProps) { + super(props); + + let advanced = false; + + if (typeof Storage !== 'undefined') { + advanced = getLocalStorageAdvanced() === 'expanded'; + } + + const formData = RightPaneStore.getFormData(); + const defaultFormData = RightPaneStore.getDefaultFormData(); + for (const [key, value] of Object.entries(formData)) { + if (key === 'deptLabel' || key === 'deptValue') { + continue; + } + + if (defaultFormData[key] != value) { + advanced = true; + break; + } + } + + this.state = { + expandAdvanced: advanced, + }; + } + + componentDidMount() { + RightPaneStore.on('formReset', this.resetParams); + } + + resetParams() { + const stateObj = { url: 'url' }; + const url = new URL(window.location.href); + const urlParam = new URLSearchParams(url.search); + + const formData = RightPaneStore.getFormData(); + for (const key of Object.keys(formData)) { + if (key === 'deptLabel' || key === 'deptValue') { + continue; + } + + urlParam.delete(key); + } + + const param = urlParam.toString(); + const new_url = `${param.trim() ? '?' : ''}${param}`; + history.replaceState(stateObj, 'url', '/' + new_url); + } + + handleExpand = () => { + const nextExpansionState = !this.state.expandAdvanced; + setLocalStorageAdvanced(nextExpansionState ? 'expanded' : 'notexpanded'); + this.setState({ expandAdvanced: nextExpansionState }); + }; + + render() { + return ( + <> + + + + + + ); + } +} + +export default withStyles(parentStyles)(AdvancedSearch); diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch/AdvancedSearchTextFields.tsx b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch/AdvancedSearchTextFields.tsx new file mode 100644 index 000000000..a496f75ea --- /dev/null +++ b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch/AdvancedSearchTextFields.tsx @@ -0,0 +1,294 @@ +import { TextField, Box, FormControl, InputLabel, Select, Switch, FormControlLabel } from '@material-ui/core'; +import { MenuItem } from '@mui/material'; +import { useState, useEffect, useCallback } from 'react'; + +import { EXCLUDE_RESTRICTION_CODES_OPTIONS } from '$components/RightPane/CoursePane/SearchForm/AdvancedSearch/constants'; +import RightPaneStore from '$components/RightPane/RightPaneStore'; + +export function AdvancedSearchTextFields() { + const [instructor, setInstructor] = useState(RightPaneStore.getFormData().instructor); + const [units, setUnits] = useState(RightPaneStore.getFormData().units); + const [endTime, setEndTime] = useState(RightPaneStore.getFormData().endTime); + const [startTime, setStartTime] = useState(RightPaneStore.getFormData().startTime); + const [coursesFull, setCoursesFull] = useState(RightPaneStore.getFormData().coursesFull); + const [building, setBuilding] = useState(RightPaneStore.getFormData().building); + const [room, setRoom] = useState(RightPaneStore.getFormData().room); + const [division, setDivision] = useState(RightPaneStore.getFormData().division); + const [excludeRestrictionCodes, setExcludeRestrictionCodes] = useState( + RightPaneStore.getFormData().excludeRestrictionCodes + ); + + const resetField = useCallback(() => { + const formData = RightPaneStore.getFormData(); + setInstructor(formData.instructor); + setUnits(formData.units); + setEndTime(formData.endTime); + setStartTime(formData.startTime); + setCoursesFull(formData.coursesFull); + setBuilding(formData.building); + setRoom(formData.room); + setDivision(formData.division); + setExcludeRestrictionCodes(formData.excludeRestrictionCodes); + }, []); + + useEffect(() => { + RightPaneStore.on('formReset', resetField); + return () => { + RightPaneStore.removeListener('formReset', resetField); + }; + }, [resetField]); + + const handleChange = + (name: string) => + ( + event: React.ChangeEvent< + HTMLInputElement | HTMLTextAreaElement | { name?: string | undefined; value: unknown } + > + ) => { + const stateObj = { url: 'url' }; + const url = new URL(window.location.href); + const urlParam = new URLSearchParams(url.search); + const value = event.target.value as string | string[]; + + if (name === 'online') { + if (event.target instanceof HTMLInputElement && event.target.checked) { + setBuilding('ON'); + setRoom('LINE'); + RightPaneStore.updateFormValue('building', 'ON'); + RightPaneStore.updateFormValue('room', 'LINE'); + urlParam.set('building', 'ON'); + urlParam.set('room', 'LINE'); + } else { + setBuilding(''); + setRoom(''); + RightPaneStore.updateFormValue('building', ''); + RightPaneStore.updateFormValue('room', ''); + urlParam.delete('building'); + urlParam.delete('room'); + } + } else { + const stringValue = Array.isArray(value) ? value.join('') : value; + + switch (name) { + case 'instructor': + setInstructor(stringValue); + break; + case 'units': + setUnits(stringValue); + break; + case 'endTime': + setEndTime(stringValue); + break; + case 'startTime': + setStartTime(stringValue); + break; + case 'coursesFull': + setCoursesFull(stringValue); + break; + case 'building': + setBuilding(stringValue); + break; + case 'room': + setRoom(stringValue); + break; + case 'division': + setDivision(stringValue); + break; + case 'excludeRestrictionCodes': + setExcludeRestrictionCodes(stringValue); + break; + default: + break; + } + + if (stringValue !== '') { + urlParam.set(name, String(stringValue)); + } else { + urlParam.delete(name); + } + + RightPaneStore.updateFormValue(name, stringValue); + } + + const param = urlParam.toString(); + const newUrl = `${param.trim() ? '?' : ''}${param}`; + history.replaceState(stateObj, 'url', '/' + newUrl); + }; + + // List of times from 2:00am-11:00pm + const menuItemTimes = [ + ...[...Array(10).keys()].map((v) => `${v + 2}:00am`), + '12:00pm', + ...[...Array(11).keys()].map((v) => `${v + 1}:00pm`), + ]; + + const createdMenuItemTime = (time: string) => ( + + {time ? time : None} + + ); + + const startsAfterMenuItems = ['', '1:00am', ...menuItemTimes].map((time) => createdMenuItemTime(time)); + const endsBeforeMenuItems = ['', ...menuItemTimes].map((time) => createdMenuItemTime(time)); + + return ( + + + + + + + Class Full Option + + + + + + Course Level + + + + + + Starts After + + + + + Ends Before + + + + + } + label="Online Only" + labelPlacement="top" + style={{ margin: 0, justifyContent: 'flex-end' }} + /> + + + + + + + Exclude Restrictions + + + + ); +} diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch/constants.ts b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch/constants.ts new file mode 100644 index 000000000..0660d92f4 --- /dev/null +++ b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/AdvancedSearch/constants.ts @@ -0,0 +1,20 @@ +export const EXCLUDE_RESTRICTION_CODES_OPTIONS = [ + { value: 'A', label: 'A: Prerequisite required' }, + { value: 'B', label: 'B: Authorization code required' }, + { value: 'C', label: 'C: Fee required' }, + { value: 'D', label: 'D: Pass/Not Pass option only' }, + { value: 'E', label: 'E: Freshmen only' }, + { value: 'F', label: 'F: Sophomores only' }, + { value: 'G', label: 'G: Lower-division only' }, + { value: 'H', label: 'H: Juniors only' }, + { value: 'I', label: 'I: Seniors only' }, + { value: 'J', label: 'J: Upper-division only' }, + { value: 'K', label: 'K: Graduate only' }, + { value: 'L', label: 'L: Major only' }, + { value: 'M', label: 'M: Non-major only' }, + { value: 'N', label: 'N: School major only' }, + { value: 'O', label: 'O: Non-school major only' }, + { value: 'R', label: 'R: Biomedical Pass/Fail course (School of Medicine only)' }, + { value: 'S', label: 'S: Satisfactory/Unsatisfactory only' }, + { value: 'X', label: 'X: Separate authorization codes required to add, drop, or change enrollment' }, +]; diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/LegacySearch.tsx b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/LegacySearch.tsx index fd78f0da9..44f1d3ee2 100644 --- a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/LegacySearch.tsx +++ b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/LegacySearch.tsx @@ -2,7 +2,7 @@ import { Button, Theme } from '@material-ui/core'; import { withStyles } from '@material-ui/core/styles'; import { ClassNameMap, Styles } from '@material-ui/core/styles/withStyles'; -import AdvancedSearch from './AdvancedSearch'; +import AdvancedSearch from './AdvancedSearch/AdvancedSearch'; import CourseNumberSearchBar from './CourseNumberSearchBar'; import DeptSearchBar from './DeptSearchBar/DeptSearchBar'; import GESelector from './GESelector'; @@ -42,12 +42,14 @@ const styles: Styles = { buttonContainer: { width: '100%', display: 'flex', - justifyContent: 'space-evenly', + justifyContent: 'center', + gap: 16, }, }; function LegacySearch(props: { classes: ClassNameMap; onSubmit: () => void; onReset: () => void }) { const { classes, onSubmit, onReset } = props; + return ( <>
diff --git a/apps/antalmanac/src/components/RightPane/RightPaneStore.ts b/apps/antalmanac/src/components/RightPane/RightPaneStore.ts index 5772a5236..6217dc1b3 100644 --- a/apps/antalmanac/src/components/RightPane/RightPaneStore.ts +++ b/apps/antalmanac/src/components/RightPane/RightPaneStore.ts @@ -17,6 +17,7 @@ const defaultFormValues: Record = { building: '', room: '', division: '', + excludeRestrictionCodes: '', }; export interface BuildingFocusInfo { diff --git a/apps/antalmanac/src/components/SharedRoot.tsx b/apps/antalmanac/src/components/SharedRoot.tsx index 32a8c494c..fa23ff437 100644 --- a/apps/antalmanac/src/components/SharedRoot.tsx +++ b/apps/antalmanac/src/components/SharedRoot.tsx @@ -5,10 +5,10 @@ import { Link, useParams } from 'react-router-dom'; import Calendar from './Calendar/CalendarRoot'; import AddedCoursePane from './RightPane/AddedCourses/AddedCoursePane'; -import CoursePane from './RightPane/CoursePane/CoursePaneRoot'; import darkModeLoadingGif from './RightPane/CoursePane/SearchForm/Gifs/dark-loading.gif'; import loadingGif from './RightPane/CoursePane/SearchForm/Gifs/loading.gif'; +import { CoursePaneRoot } from '$components/RightPane/CoursePane/CoursePaneRoot'; import { getLocalStorageUserId } from '$lib/localStorage'; import { useThemeStore } from '$stores/SettingsStore'; import { useTabStore } from '$stores/TabStore'; @@ -91,6 +91,7 @@ type ScheduleManagementTabsProps = { */ function ScheduleManagementMobileTabs(props: ScheduleManagementTabsProps) { const { value, setActiveTab } = props; + const isDark = useThemeStore((store) => store.isDark); const onChange = (_event: React.SyntheticEvent, value: number) => { setActiveTab(value); @@ -102,7 +103,7 @@ function ScheduleManagementMobileTabs(props: ScheduleManagementTabsProps) { @@ -170,7 +171,7 @@ function ScheduleManagementTabsContent(props: { activeTab: number; isMobile: boo case 0: return ; case 1: - return ; + return ; case 2: return ; case 3: diff --git a/apps/antalmanac/src/routes/Home.tsx b/apps/antalmanac/src/routes/Home.tsx index 83f682fec..6ed8aed5c 100644 --- a/apps/antalmanac/src/routes/Home.tsx +++ b/apps/antalmanac/src/routes/Home.tsx @@ -13,18 +13,10 @@ import { Tutorial } from '$components/Tutorial'; function MobileHome() { return ( - - - - - - -
- - - - - + +
+ + ); } @@ -32,11 +24,7 @@ function DesktopHome() { const theme = useTheme(); return ( - - - - - + <>
@@ -66,9 +54,7 @@ function DesktopHome() { - - - + ); } @@ -77,5 +63,15 @@ export default function Home() { const isMobileScreen = useMediaQuery(theme.breakpoints.down('sm')); - return isMobileScreen ? : ; + return ( + + + + + + {isMobileScreen ? : } + + + + ); }