From 921ec4e30c072888634048b6839733d4df79e1cc Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Thu, 9 Jan 2025 14:06:22 -0800 Subject: [PATCH 1/6] refactor: tab store --- .../Calendar/CourseCalendarEvent.tsx | 2 +- .../AddedCourses/CustomEventDetailView.tsx | 2 +- .../SectionTableBodyCells/LocationsCell.tsx | 2 +- apps/antalmanac/src/components/SharedRoot.tsx | 92 ++++++------------- apps/antalmanac/src/lib/TutorialHelpers.tsx | 8 +- apps/antalmanac/src/lib/helpers.ts | 2 +- apps/antalmanac/src/routes/Home.tsx | 2 +- apps/antalmanac/src/stores/AppStore.ts | 2 +- apps/antalmanac/src/stores/TabStore.ts | 52 +++++++++-- 9 files changed, 82 insertions(+), 82 deletions(-) diff --git a/apps/antalmanac/src/components/Calendar/CourseCalendarEvent.tsx b/apps/antalmanac/src/components/Calendar/CourseCalendarEvent.tsx index 642a2d283..9a5c38e22 100644 --- a/apps/antalmanac/src/components/Calendar/CourseCalendarEvent.tsx +++ b/apps/antalmanac/src/components/Calendar/CourseCalendarEvent.tsx @@ -171,7 +171,7 @@ const CourseCalendarEvent = (props: CourseCalendarEventProps) => { const isDark = useThemeStore((store) => store.isDark); const focusMap = useCallback(() => { - setActiveTab(2); + setActiveTab('map'); }, [setActiveTab]); const { classes, selectedEvent } = props; diff --git a/apps/antalmanac/src/components/RightPane/AddedCourses/CustomEventDetailView.tsx b/apps/antalmanac/src/components/RightPane/AddedCourses/CustomEventDetailView.tsx index 1eb22a82d..8169a95a4 100644 --- a/apps/antalmanac/src/components/RightPane/AddedCourses/CustomEventDetailView.tsx +++ b/apps/antalmanac/src/components/RightPane/AddedCourses/CustomEventDetailView.tsx @@ -60,7 +60,7 @@ const CustomEventDetailView = (props: CustomEventDetailViewProps) => { const { setActiveTab } = useTabStore(); const focusMap = useCallback(() => { - setActiveTab(2); + setActiveTab('map'); }, [setActiveTab]); return ( diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyCells/LocationsCell.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyCells/LocationsCell.tsx index bafacd1b9..a40619340 100644 --- a/apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyCells/LocationsCell.tsx +++ b/apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyCells/LocationsCell.tsx @@ -18,7 +18,7 @@ export const LocationsCell = ({ meetings }: LocationsCellProps) => { const { setActiveTab } = useTabStore(); const focusMap = useCallback(() => { - setActiveTab(2); + setActiveTab('map'); }, [setActiveTab]); return ( diff --git a/apps/antalmanac/src/components/SharedRoot.tsx b/apps/antalmanac/src/components/SharedRoot.tsx index e8a647ed7..88dacdbbb 100644 --- a/apps/antalmanac/src/components/SharedRoot.tsx +++ b/apps/antalmanac/src/components/SharedRoot.tsx @@ -1,7 +1,7 @@ import { Event, FormatListBulleted, MyLocation, Search } from '@mui/icons-material'; import { GlobalStyles, Paper, Stack, Tab, Tabs, Typography, useMediaQuery, useTheme } from '@mui/material'; import { Suspense, lazy, useEffect, useRef, useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import { ScheduleCalendar } from './Calendar/CalendarRoot'; import AddedCoursePane from './RightPane/AddedCourses/AddedCoursePane'; @@ -75,30 +75,19 @@ const scheduleManagementTabs: Array = [ }, ]; -/** - * A different set of tab buttons will be listed depending on whether the screen is mobile or - * desktop. - * - * Provide the current state of the tab navigation from the parent. - */ -type ScheduleManagementTabsProps = { - value: number; - setActiveTab: (value: number) => void; -}; - /** * For mobile devices, all tabs will be displayed. */ -function ScheduleManagementMobileTabs(props: ScheduleManagementTabsProps) { - const { value, setActiveTab } = props; +function ScheduleManagementMobileTabs() { const isDark = useThemeStore((store) => store.isDark); + const { activeTab, setActiveTabValue } = useTabStore(); const onChange = (_event: React.SyntheticEvent, value: number) => { - setActiveTab(value); + setActiveTabValue(value); }; return ( - + {scheduleManagementTabs.map((tab) => ( store.isDark); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const onChange = (_event: React.SyntheticEvent, value: number) => { - setActiveTab(value + 1); + setActiveTabValue(value); }; return ( - + {scheduleManagementTabs.map((tab) => { - if (tab.mobile) return; - return ( @@ -162,9 +152,8 @@ function ScheduleManagementDesktopTabs(props: ScheduleManagementTabsProps) { ); } -function ScheduleManagementTabsContent(props: { activeTab: number; isMobile: boolean }) { - const { activeTab } = props; - +function ScheduleManagementTabsContent() { + const { activeTab } = useTabStore(); const isDark = useThemeStore((store) => store.isDark); switch (activeTab) { @@ -206,22 +195,16 @@ function ScheduleManagementTabsContent(props: { activeTab: number; isMobile: boo */ export default function ScheduleManagement() { const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('sm')); - const { activeTab, setActiveTab } = useTabStore(); - const { tab } = useParams(); - // Tab index mapped to the last known scrollTop. const [positions, setPositions] = useState>({}); /** * Ref to the scrollable container with all of the tabs-content within it. */ - const ref = useRef(); - - const value = isMobile ? activeTab : activeTab - 1 >= 0 ? activeTab - 1 : 0; + const ref = useRef(null); // Save the current scroll position to the store. const onScroll = (e: React.UIEvent) => { @@ -236,39 +219,24 @@ export default function ScheduleManagement() { useEffect(() => { const userId = getLocalStorageUserId(); - if (userId != null) { - setActiveTab(2); + if (userId === null) { + setActiveTab('search'); + } else if (isMobile) { + setActiveTab('calendar'); } else { - setActiveTab(1); + setActiveTab('added'); } }, [setActiveTab]); - // Handle tab index for mobile screens. - useEffect(() => { - if (isMobile) return; - - if (tab === 'map') { - setActiveTab(3); - } - - if (activeTab == 0) { - setActiveTab(1); - } - }, [activeTab, isMobile, setActiveTab, tab]); - // Restore scroll position if it has been previously saved. useEffect(() => { const savedPosition = positions[activeTab]; - let animationFrame: number; - - if (savedPosition != null) { - animationFrame = requestAnimationFrame(() => { - if (ref.current) { - ref.current.scrollTop = savedPosition; - } - }); - } + const animationFrame = requestAnimationFrame(() => { + if (ref.current && savedPosition != null) { + ref.current.scrollTop = savedPosition; + } + }); return () => { if (animationFrame != null) { @@ -277,17 +245,13 @@ export default function ScheduleManagement() { }; }, [activeTab, positions]); - if (activeTab === 0 && !isMobile) { - return ; - } - return ( {!isMobile && ( - + )} @@ -301,13 +265,13 @@ export default function ScheduleManagement() { ref={ref} onScroll={onScroll} > - + {isMobile && ( - + )} diff --git a/apps/antalmanac/src/lib/TutorialHelpers.tsx b/apps/antalmanac/src/lib/TutorialHelpers.tsx index 4e2655897..baa77d252 100644 --- a/apps/antalmanac/src/lib/TutorialHelpers.tsx +++ b/apps/antalmanac/src/lib/TutorialHelpers.tsx @@ -55,7 +55,7 @@ function KbdCard(props: { children?: React.ReactNode }) { } export function namedStepsFactory(goToStep: (step: number) => void): Record { - const setTab = useTabStore.getState().setActiveTab; + const setActiveTab = useTabStore.getState().setActiveTab; const goToNamedStep = (stepName: TourStepName) => { const stepIndex = tourStepNames.findIndex((step) => step == stepName); @@ -91,7 +91,7 @@ export function namedStepsFactory(goToStep: (step: number) => void): Record { markTourHasRun(); - setTab(0); + setActiveTab('search'); }, mutationObservables: ['#searchBar'], }, @@ -161,7 +161,7 @@ export function namedStepsFactory(goToStep: (step: number) => void): RecordSelect the added courses tab for a list of your courses and details ), - action: () => setTab(1), + action: () => setActiveTab('added'), mutationObservables: ['#course-pane-box'], }, map: { @@ -179,7 +179,7 @@ export function namedStepsFactory(goToStep: (step: number) => void): Record setTab(2), + action: () => setActiveTab('map'), mutationObservables: ['#map-pane'], }, saveAndLoad: { diff --git a/apps/antalmanac/src/lib/helpers.ts b/apps/antalmanac/src/lib/helpers.ts index b2cb54d99..7b643594b 100644 --- a/apps/antalmanac/src/lib/helpers.ts +++ b/apps/antalmanac/src/lib/helpers.ts @@ -43,7 +43,7 @@ export function useQuickSearchForClasses() { RightPaneStore.updateFormValue('courseNumber', courseNumber); RightPaneStore.updateFormValue('term', termValue); navigate(href, { replace: false }); - setActiveTab(1); + setActiveTab('search'); displaySections(); forceUpdate(); }, diff --git a/apps/antalmanac/src/routes/Home.tsx b/apps/antalmanac/src/routes/Home.tsx index e326eef86..a15330fb4 100644 --- a/apps/antalmanac/src/routes/Home.tsx +++ b/apps/antalmanac/src/routes/Home.tsx @@ -25,7 +25,7 @@ function DesktopHome() { const theme = useTheme(); const setScheduleManagementWidth = useScheduleManagementStore((state) => state.setScheduleManagementWidth); - const scheduleManagementRef = useRef(); + const scheduleManagementRef = useRef(null); const handleDrag = useCallback(() => { const scheduleManagementElement = scheduleManagementRef.current; diff --git a/apps/antalmanac/src/stores/AppStore.ts b/apps/antalmanac/src/stores/AppStore.ts index be02dcb9f..1f37a4c09 100644 --- a/apps/antalmanac/src/stores/AppStore.ts +++ b/apps/antalmanac/src/stores/AppStore.ts @@ -359,7 +359,7 @@ class AppStore extends EventEmitter { this.emit('skeletonModeChange'); // Switch to added courses tab since Anteater API can't be reached anyway - useTabStore.getState().setActiveTab(2); + useTabStore.getState().setActiveTab('added'); } changeCurrentSchedule(newScheduleIndex: number) { diff --git a/apps/antalmanac/src/stores/TabStore.ts b/apps/antalmanac/src/stores/TabStore.ts index 61c756327..9e57343b8 100644 --- a/apps/antalmanac/src/stores/TabStore.ts +++ b/apps/antalmanac/src/stores/TabStore.ts @@ -1,20 +1,56 @@ import { create } from 'zustand'; +type TabName = 'calendar' | 'search' | 'added' | 'map'; + interface TabStore { activeTab: number; - setActiveTab: (newTab: number) => void; + + /** + * Sets the appropriate tab value given a string literal union + */ + setActiveTab: (value: TabName) => void; + + /** + * Sets the appropriate tab value given a tab index. + * + * `setActiveTab` (which accepts a string literal union) should be preferred if trying to change tabs programmatically (and not as part of an event handler) + */ + setActiveTabValue: (value: number) => void; } export const useTabStore = create((set) => { - const pathArray = typeof window !== 'undefined' ? window.location.pathname.split('/').slice(1) : []; - const tabName = pathArray[0]; + // const pathArray = typeof window !== 'undefined' ? window.location.pathname.split('/').slice(1) : []; + // const tabName = pathArray[0]; return { - activeTab: tabName === 'added' ? 1 : tabName === 'map' ? 2 : 0, - setActiveTab: (newTab: number) => { - set(() => ({ - activeTab: newTab, - })); + activeTab: 1, + setActiveTab: (name: TabName) => { + if (name === 'calendar') { + set(() => ({ + activeTab: 0, + })); + } + + if (name === 'search') { + set(() => ({ + activeTab: 1, + })); + } + + if (name === 'added') { + set(() => ({ + activeTab: 2, + })); + } + + if (name === 'map') { + set(() => ({ + activeTab: 3, + })); + } + }, + setActiveTabValue: (value: number) => { + set(() => ({ activeTab: value })); }, }; }); From ab5b6eb5fcce2e22fb8f50fb6eef41c7776aea1c Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Thu, 9 Jan 2025 14:28:04 -0800 Subject: [PATCH 2/6] chore: misc. --- apps/antalmanac/src/stores/TabStore.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/antalmanac/src/stores/TabStore.ts b/apps/antalmanac/src/stores/TabStore.ts index 9e57343b8..95125f0fa 100644 --- a/apps/antalmanac/src/stores/TabStore.ts +++ b/apps/antalmanac/src/stores/TabStore.ts @@ -8,7 +8,7 @@ interface TabStore { /** * Sets the appropriate tab value given a string literal union */ - setActiveTab: (value: TabName) => void; + setActiveTab: (name: TabName) => void; /** * Sets the appropriate tab value given a tab index. @@ -19,9 +19,6 @@ interface TabStore { } export const useTabStore = create((set) => { - // const pathArray = typeof window !== 'undefined' ? window.location.pathname.split('/').slice(1) : []; - // const tabName = pathArray[0]; - return { activeTab: 1, setActiveTab: (name: TabName) => { From 42262caa4d9833fcde1975b12fc2cf19e18076eb Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Thu, 9 Jan 2025 14:31:42 -0800 Subject: [PATCH 3/6] refactor: action cell --- .../SectionTableBodyCells/ActionCell.tsx} | 12 ++++++------ .../SectionTableBody/SectionTableBodyRow.tsx | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) rename apps/antalmanac/src/components/RightPane/SectionTable/{cells/action.tsx => SectionTableBody/SectionTableBodyCells/ActionCell.tsx} (94%) diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/cells/action.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyCells/ActionCell.tsx similarity index 94% rename from apps/antalmanac/src/components/RightPane/SectionTable/cells/action.tsx rename to apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyCells/ActionCell.tsx index 92da3434d..f7819e069 100644 --- a/apps/antalmanac/src/components/RightPane/SectionTable/cells/action.tsx +++ b/apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyCells/ActionCell.tsx @@ -3,7 +3,7 @@ import { Box, IconButton, Menu, MenuItem, TableCell, Tooltip, useMediaQuery } fr import { AASection, CourseDetails } from '@packages/antalmanac-types'; import { bindMenu, bindTrigger, usePopupState } from 'material-ui-popup-state/hooks'; -import { MOBILE_BREAKPOINT } from '../../../../globals'; +import { MOBILE_BREAKPOINT } from '../../../../../globals'; import { addCourse, deleteCourse, openSnackbar } from '$actions/AppStoreActions'; import ColorPicker from '$components/ColorPicker'; @@ -13,7 +13,7 @@ import AppStore from '$stores/AppStore'; /** * Props received by components that perform actions on a specified section. */ -interface SectionActionProps { +interface ActionProps { /** * The section to perform actions on. */ @@ -43,7 +43,7 @@ interface SectionActionProps { /** * Sections added to a schedule, can be recolored or deleted. */ -export function ColorAndDelete(props: SectionActionProps) { +export function ColorAndDelete(props: ActionProps) { const { section, term } = props; const isMobileScreen = useMediaQuery(`(max-width: ${MOBILE_BREAKPOINT}`); @@ -86,7 +86,7 @@ const fieldsToReset = ['courseCode', 'courseNumber', 'deptLabel', 'deptValue', ' /** * Sections that have not been added to a schedule can be added to a schedule. */ -export function ScheduleAddCell(props: SectionActionProps) { +export function ScheduleAddCell(props: ActionProps) { const { section, courseDetails, term, scheduleNames, scheduleConflict } = props; const popupState = usePopupState({ popupId: 'SectionTableAddCellPopup', variant: 'popover' }); @@ -168,7 +168,7 @@ export function ScheduleAddCell(props: SectionActionProps) { ); } -export interface SectionActionCellProps extends Omit { +export interface ActionCellProps extends Omit { /** * Whether the section has been added. */ @@ -178,7 +178,7 @@ export interface SectionActionCellProps extends Omit {props.addedCourse ? : } diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyRow.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyRow.tsx index d0d63e8e0..005531841 100644 --- a/apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyRow.tsx +++ b/apps/antalmanac/src/components/RightPane/SectionTable/SectionTableBody/SectionTableBodyRow.tsx @@ -2,7 +2,7 @@ import { TableRow, useTheme } from '@mui/material'; import { AASection, CourseDetails } from '@packages/antalmanac-types'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { SectionActionCell } from '../cells/action'; +import { ActionCell } from './SectionTableBodyCells/ActionCell'; import { CourseCodeCell } from '$components/RightPane/SectionTable/SectionTableBody/SectionTableBodyCells/CourseCodeCell'; import { DayAndTimeCell } from '$components/RightPane/SectionTable/SectionTableBody/SectionTableBodyCells/DayAndTimeCell'; @@ -30,7 +30,7 @@ interface SectionTableBodyRowProps { // These components have too varied of types, any is fine here // eslint-disable-next-line @typescript-eslint/no-explicit-any const tableBodyCells: Record> = { - action: SectionActionCell, + action: ActionCell, sectionCode: CourseCodeCell, sectionDetails: DetailsCell, instructors: InstructorsCell, From b637a90dd9dd59134b3e69fafaa57918ec9d9fab Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Thu, 9 Jan 2025 16:14:35 -0800 Subject: [PATCH 4/6] feat: refactor schedule management --- .../AddedCourses/AddedCoursePane.tsx | 2 +- .../ScheduleManagement/ScheduleManagement.tsx | 89 ++++++ .../ScheduleManagementDesktopTabs.tsx | 29 ++ .../ScheduleManagementMobileTabs.tsx | 21 ++ .../ScheduleManagementTabs.tsx | 105 +++++++ .../ScheduleManagementTabsContent.tsx | 50 ++++ apps/antalmanac/src/components/SharedRoot.tsx | 279 ------------------ apps/antalmanac/src/routes/Home.tsx | 2 +- 8 files changed, 296 insertions(+), 281 deletions(-) create mode 100644 apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx create mode 100644 apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementDesktopTabs.tsx create mode 100644 apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementMobileTabs.tsx create mode 100644 apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabs.tsx create mode 100644 apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabsContent.tsx delete mode 100644 apps/antalmanac/src/components/SharedRoot.tsx diff --git a/apps/antalmanac/src/components/RightPane/AddedCourses/AddedCoursePane.tsx b/apps/antalmanac/src/components/RightPane/AddedCourses/AddedCoursePane.tsx index 7842f4469..5b276fbcd 100644 --- a/apps/antalmanac/src/components/RightPane/AddedCourses/AddedCoursePane.tsx +++ b/apps/antalmanac/src/components/RightPane/AddedCourses/AddedCoursePane.tsx @@ -363,7 +363,7 @@ function AddedSectionsGrid() { ); } -export default function AddedCoursePaneFunctionComponent() { +export function AddedCoursePane() { const [skeletonMode, setSkeletonMode] = useState(AppStore.getSkeletonMode()); useEffect(() => { diff --git a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx new file mode 100644 index 000000000..9bf6e552d --- /dev/null +++ b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx @@ -0,0 +1,89 @@ +import { GlobalStyles, Stack, useMediaQuery, useTheme } from '@mui/material'; +import { useEffect, useRef, useState } from 'react'; + +import { ScheduleManagementDesktopTabs } from '$components/ScheduleManagement/ScheduleManagementDesktopTabs'; +import { ScheduleManagementMobileTabs } from '$components/ScheduleManagement/ScheduleManagementMobileTabs'; +import { ScheduleManagementTabsContent } from '$components/ScheduleManagement/ScheduleManagementTabsContent'; +import { getLocalStorageUserId } from '$lib/localStorage'; +import { useTabStore } from '$stores/TabStore'; + +/** + * List of interactive tab buttons with their accompanying content. + * Each tab's content has functionality for managing the user's schedule. + */ +export function ScheduleManagement() { + const { activeTab, setActiveTab } = useTabStore(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + // Tab index mapped to the last known scrollTop. + const [positions, setPositions] = useState>({}); + + /** + * Ref to the scrollable container with all of the tabs-content within it. + */ + const ref = useRef(null); + + // Save the current scroll position to the store. + const onScroll = (e: React.UIEvent) => { + const positionToSave = e.currentTarget.scrollTop; + setPositions((current) => { + current[activeTab] = positionToSave; + return current; + }); + }; + + // Change the tab to the "added classes" tab if the user was previously logged in. + useEffect(() => { + const userId = getLocalStorageUserId(); + + if (userId === null) { + setActiveTab('search'); + } else if (isMobile) { + setActiveTab('calendar'); + } else { + setActiveTab('added'); + } + }, [setActiveTab]); + + // Restore scroll position if it has been previously saved. + useEffect(() => { + const savedPosition = positions[activeTab]; + + const animationFrame = requestAnimationFrame(() => { + if (ref.current && savedPosition != null) { + ref.current.scrollTop = savedPosition; + } + }); + + return () => { + if (animationFrame != null) { + cancelAnimationFrame(animationFrame); + } + }; + }, [activeTab, positions]); + + return ( + + + + {!isMobile && } + + + + + + + + {isMobile && } + + ); +} diff --git a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementDesktopTabs.tsx b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementDesktopTabs.tsx new file mode 100644 index 000000000..81e844efd --- /dev/null +++ b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementDesktopTabs.tsx @@ -0,0 +1,29 @@ +import { Stack, Typography } from '@mui/material'; + +import { + ScheduleManagementTabInfo, + ScheduleManagementTabs, +} from '$components/ScheduleManagement/ScheduleManagementTabs'; + +export function ScheduleManagementDesktopTabs() { + const TabLabel = ({ tab }: { tab: ScheduleManagementTabInfo }) => { + return ( + + + {tab.label} + + ); + }; + + return ( + } + /> + ); +} diff --git a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementMobileTabs.tsx b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementMobileTabs.tsx new file mode 100644 index 000000000..003206eb0 --- /dev/null +++ b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementMobileTabs.tsx @@ -0,0 +1,21 @@ +import { Stack, Typography } from '@mui/material'; + +import { + ScheduleManagementTabInfo, + ScheduleManagementTabs, +} from '$components/ScheduleManagement/ScheduleManagementTabs'; + +export function ScheduleManagementMobileTabs() { + const TabLabel = ({ tab }: { tab: ScheduleManagementTabInfo }) => { + return ( + + + + {tab.label} + + + ); + }; + + return } />; +} diff --git a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabs.tsx b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabs.tsx new file mode 100644 index 000000000..e6bdf2302 --- /dev/null +++ b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabs.tsx @@ -0,0 +1,105 @@ +import { Event, FormatListBulleted, MyLocation, Search } from '@mui/icons-material'; +import { Paper, SxProps, Tab, Tabs, useMediaQuery, useTheme } from '@mui/material'; +import { Link } from 'react-router-dom'; + +import { useThemeStore } from '$stores/SettingsStore'; +import { useTabStore } from '$stores/TabStore'; + +/** + * Information about the tab navigation buttons. + * + * Each button should be associated with a different aspect of schedule management. + */ +export type ScheduleManagementTabInfo = { + /** + * Label to display on the tab button. + */ + label: string; + + /** + * The path to navigate to in the URL. + */ + href: string; + + /** + * Icon to display. + */ + icon: React.ElementType; + + /** + * ID for the tab? + */ + id?: string; + + /** + * Whether or not this is mobile-only. + */ + mobile?: true; +}; + +const scheduleManagementTabs: Array = [ + { + label: 'Calendar', + icon: Event, + mobile: true, + href: '', + }, + { + label: 'Search', + href: '/', + icon: Search, + }, + { + label: 'Added', + href: '/added', + icon: FormatListBulleted, + id: 'added-courses-tab', + }, + { + label: 'Map', + href: '/map', + icon: MyLocation, + id: 'map-tab', + }, +]; + +interface ScheduleManagementTabsProps { + sx?: SxProps; + label: (tab: ScheduleManagementTabInfo) => React.ReactNode; +} + +export function ScheduleManagementTabs({ sx, label }: ScheduleManagementTabsProps) { + const { activeTab, setActiveTabValue } = useTabStore(); + const isDark = useThemeStore((store) => store.isDark); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + const onChange = (_event: React.SyntheticEvent, value: number) => { + setActiveTabValue(value); + }; + + return ( + + + {scheduleManagementTabs.map((tab) => { + return ( + + ); + })} + + + ); +} + +// sx={{ display: !isMobile && tab.mobile ? 'none' : 'flex' }} diff --git a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabsContent.tsx b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabsContent.tsx new file mode 100644 index 000000000..183c0cb4f --- /dev/null +++ b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabsContent.tsx @@ -0,0 +1,50 @@ +import { lazy, Suspense } from 'react'; + +import darkModeLoadingGif from '../RightPane/CoursePane/SearchForm/Gifs/dark-loading.gif'; +import loadingGif from '../RightPane/CoursePane/SearchForm/Gifs/loading.gif'; + +import { ScheduleCalendar } from '$components/Calendar/CalendarRoot'; +import { AddedCoursePane } from '$components/RightPane/AddedCourses/AddedCoursePane'; +import { CoursePaneRoot } from '$components/RightPane/CoursePane/CoursePaneRoot'; +import { useThemeStore } from '$stores/SettingsStore'; +import { useTabStore } from '$stores/TabStore'; + + +const UCIMap = lazy(() => import('../Map/Map')); + +export function ScheduleManagementTabsContent() { + const { activeTab } = useTabStore(); + const isDark = useThemeStore((store) => store.isDark); + + switch (activeTab) { + case 0: + return ; + case 1: + return ; + case 2: + return ; + case 3: + return ( + + Loading map + + } + > + + + ); + + default: + return null; + } +} diff --git a/apps/antalmanac/src/components/SharedRoot.tsx b/apps/antalmanac/src/components/SharedRoot.tsx deleted file mode 100644 index 88dacdbbb..000000000 --- a/apps/antalmanac/src/components/SharedRoot.tsx +++ /dev/null @@ -1,279 +0,0 @@ -import { Event, FormatListBulleted, MyLocation, Search } from '@mui/icons-material'; -import { GlobalStyles, Paper, Stack, Tab, Tabs, Typography, useMediaQuery, useTheme } from '@mui/material'; -import { Suspense, lazy, useEffect, useRef, useState } from 'react'; -import { Link } from 'react-router-dom'; - -import { ScheduleCalendar } from './Calendar/CalendarRoot'; -import AddedCoursePane from './RightPane/AddedCourses/AddedCoursePane'; -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'; - -const UCIMap = lazy(() => import('./Map/Map')); - -/** - * Information about the tab navigation buttons. - * - * Each button should be associated with a different aspect of schedule management. - */ -type ScheduleManagementTabInfo = { - /** - * Label to display on the tab button. - */ - label: string; - - /** - * The path to navigate to in the URL. - */ - href: string; - - /** - * Icon to display. - */ - icon: React.ElementType; - - /** - * ID for the tab? - */ - id?: string; - - /** - * Whether or not this is mobile-only. - */ - mobile?: true; -}; - -/** - */ -const scheduleManagementTabs: Array = [ - { - label: 'Calendar', - icon: Event, - mobile: true, - href: '', - }, - { - label: 'Search', - href: '/', - icon: Search, - }, - { - label: 'Added', - href: '/added', - icon: FormatListBulleted, - id: 'added-courses-tab', - }, - { - label: 'Map', - href: '/map', - icon: MyLocation, - id: 'map-tab', - }, -]; - -/** - * For mobile devices, all tabs will be displayed. - */ -function ScheduleManagementMobileTabs() { - const isDark = useThemeStore((store) => store.isDark); - const { activeTab, setActiveTabValue } = useTabStore(); - - const onChange = (_event: React.SyntheticEvent, value: number) => { - setActiveTabValue(value); - }; - - return ( - - {scheduleManagementTabs.map((tab) => ( - - - - {tab.label} - - - } - /> - ))} - - ); -} - -/** - * For desktop, some of the tabs will be displayed on the other side. - * i.e. the calendar takes up the left side of the screen. - */ -function ScheduleManagementDesktopTabs() { - const { activeTab, setActiveTabValue } = useTabStore(); - const theme = useTheme(); - const isDark = useThemeStore((store) => store.isDark); - const isMobile = useMediaQuery(theme.breakpoints.down('sm')); - - const onChange = (_event: React.SyntheticEvent, value: number) => { - setActiveTabValue(value); - }; - - return ( - - {scheduleManagementTabs.map((tab) => { - return ( - - - {tab.label} - - } - /> - ); - })} - - ); -} - -function ScheduleManagementTabsContent() { - const { activeTab } = useTabStore(); - const isDark = useThemeStore((store) => store.isDark); - - switch (activeTab) { - case 0: - return ; - case 1: - return ; - case 2: - return ; - case 3: - return ( - - Loading map - - } - > - - - ); - - default: - return null; - } -} - -/** - * List of interactive tab buttons with their accompanying content. - * Each tab's content has functionality for managing the user's schedule. - */ -export default function ScheduleManagement() { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('sm')); - const { activeTab, setActiveTab } = useTabStore(); - - // Tab index mapped to the last known scrollTop. - const [positions, setPositions] = useState>({}); - - /** - * Ref to the scrollable container with all of the tabs-content within it. - */ - const ref = useRef(null); - - // Save the current scroll position to the store. - const onScroll = (e: React.UIEvent) => { - const positionToSave = e.currentTarget.scrollTop; - setPositions((current) => { - current[activeTab] = positionToSave; - return current; - }); - }; - - // Change the tab to the "added classes" tab if the user was previously logged in. - useEffect(() => { - const userId = getLocalStorageUserId(); - - if (userId === null) { - setActiveTab('search'); - } else if (isMobile) { - setActiveTab('calendar'); - } else { - setActiveTab('added'); - } - }, [setActiveTab]); - - // Restore scroll position if it has been previously saved. - useEffect(() => { - const savedPosition = positions[activeTab]; - - const animationFrame = requestAnimationFrame(() => { - if (ref.current && savedPosition != null) { - ref.current.scrollTop = savedPosition; - } - }); - - return () => { - if (animationFrame != null) { - cancelAnimationFrame(animationFrame); - } - }; - }, [activeTab, positions]); - - return ( - - - - {!isMobile && ( - - - - )} - - - - - - - - {isMobile && ( - - - - )} - - ); -} diff --git a/apps/antalmanac/src/routes/Home.tsx b/apps/antalmanac/src/routes/Home.tsx index a15330fb4..b3ce2a27c 100644 --- a/apps/antalmanac/src/routes/Home.tsx +++ b/apps/antalmanac/src/routes/Home.tsx @@ -8,7 +8,7 @@ import { ScheduleCalendar } from '$components/Calendar/CalendarRoot'; import Header from '$components/Header'; import NotificationSnackbar from '$components/NotificationSnackbar'; import PatchNotes from '$components/PatchNotes'; -import ScheduleManagement from '$components/SharedRoot'; +import { ScheduleManagement } from '$components/ScheduleManagement/ScheduleManagement'; import { Tutorial } from '$components/Tutorial'; import { useScheduleManagementStore } from '$stores/ScheduleManagementStore'; From edf272ab8397b904ca130445cc434644da8aaf39 Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Thu, 9 Jan 2025 17:50:27 -0800 Subject: [PATCH 5/6] refactor: schedule management --- .../ScheduleManagement/ScheduleManagement.tsx | 7 ++-- .../ScheduleManagementDesktopTabs.tsx | 29 ---------------- .../ScheduleManagementMobileTabs.tsx | 21 ------------ .../ScheduleManagementTabs.tsx | 34 ++++++++++--------- 4 files changed, 21 insertions(+), 70 deletions(-) delete mode 100644 apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementDesktopTabs.tsx delete mode 100644 apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementMobileTabs.tsx diff --git a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx index 9bf6e552d..664273bac 100644 --- a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx +++ b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx @@ -1,8 +1,7 @@ import { GlobalStyles, Stack, useMediaQuery, useTheme } from '@mui/material'; import { useEffect, useRef, useState } from 'react'; -import { ScheduleManagementDesktopTabs } from '$components/ScheduleManagement/ScheduleManagementDesktopTabs'; -import { ScheduleManagementMobileTabs } from '$components/ScheduleManagement/ScheduleManagementMobileTabs'; +import { ScheduleManagementTabs } from '$components/ScheduleManagement/ScheduleManagementTabs'; import { ScheduleManagementTabsContent } from '$components/ScheduleManagement/ScheduleManagementTabsContent'; import { getLocalStorageUserId } from '$lib/localStorage'; import { useTabStore } from '$stores/TabStore'; @@ -67,7 +66,7 @@ export function ScheduleManagement() { - {!isMobile && } + {!isMobile && } - {isMobile && } + {isMobile && } ); } diff --git a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementDesktopTabs.tsx b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementDesktopTabs.tsx deleted file mode 100644 index 81e844efd..000000000 --- a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementDesktopTabs.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Stack, Typography } from '@mui/material'; - -import { - ScheduleManagementTabInfo, - ScheduleManagementTabs, -} from '$components/ScheduleManagement/ScheduleManagementTabs'; - -export function ScheduleManagementDesktopTabs() { - const TabLabel = ({ tab }: { tab: ScheduleManagementTabInfo }) => { - return ( - - - {tab.label} - - ); - }; - - return ( - } - /> - ); -} diff --git a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementMobileTabs.tsx b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementMobileTabs.tsx deleted file mode 100644 index 003206eb0..000000000 --- a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementMobileTabs.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Stack, Typography } from '@mui/material'; - -import { - ScheduleManagementTabInfo, - ScheduleManagementTabs, -} from '$components/ScheduleManagement/ScheduleManagementTabs'; - -export function ScheduleManagementMobileTabs() { - const TabLabel = ({ tab }: { tab: ScheduleManagementTabInfo }) => { - return ( - - - - {tab.label} - - - ); - }; - - return } />; -} diff --git a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabs.tsx b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabs.tsx index e6bdf2302..47b21a461 100644 --- a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabs.tsx +++ b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagementTabs.tsx @@ -1,5 +1,5 @@ import { Event, FormatListBulleted, MyLocation, Search } from '@mui/icons-material'; -import { Paper, SxProps, Tab, Tabs, useMediaQuery, useTheme } from '@mui/material'; +import { Paper, Tab, Tabs, useMediaQuery, useTheme } from '@mui/material'; import { Link } from 'react-router-dom'; import { useThemeStore } from '$stores/SettingsStore'; @@ -24,7 +24,7 @@ export type ScheduleManagementTabInfo = { /** * Icon to display. */ - icon: React.ElementType; + icon: React.ReactElement; /** * ID for the tab? @@ -40,35 +40,30 @@ export type ScheduleManagementTabInfo = { const scheduleManagementTabs: Array = [ { label: 'Calendar', - icon: Event, + icon: , mobile: true, href: '', }, { label: 'Search', href: '/', - icon: Search, + icon: , }, { label: 'Added', href: '/added', - icon: FormatListBulleted, + icon: , id: 'added-courses-tab', }, { label: 'Map', href: '/map', - icon: MyLocation, + icon: , id: 'map-tab', }, ]; -interface ScheduleManagementTabsProps { - sx?: SxProps; - label: (tab: ScheduleManagementTabInfo) => React.ReactNode; -} - -export function ScheduleManagementTabs({ sx, label }: ScheduleManagementTabsProps) { +export function ScheduleManagementTabs() { const { activeTab, setActiveTabValue } = useTabStore(); const isDark = useThemeStore((store) => store.isDark); const theme = useTheme(); @@ -88,12 +83,21 @@ export function ScheduleManagementTabs({ sx, label }: ScheduleManagementTabsProp id={tab.id} component={Link} to={tab.href} + icon={tab.icon} + iconPosition={isMobile ? 'top' : 'start'} sx={{ - ...sx, + ...(!isMobile + ? { + minHeight: 'auto', + height: '44px', + padding: 3, + minWidth: '33%', + } + : {}), display: !isMobile && tab.mobile ? 'none' : 'flex', ...(isDark ? { '&.Mui-selected': { color: 'white' } } : {}), }} - label={label(tab)} + label={tab.label} /> ); })} @@ -101,5 +105,3 @@ export function ScheduleManagementTabs({ sx, label }: ScheduleManagementTabsProp ); } - -// sx={{ display: !isMobile && tab.mobile ? 'none' : 'flex' }} From 2fb150b97997f6fdca0c86b6318ea6e8a744d803 Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Thu, 9 Jan 2025 18:26:19 -0800 Subject: [PATCH 6/6] fix: read param for initial tab --- .../ScheduleManagement/ScheduleManagement.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx index 664273bac..1b11c8191 100644 --- a/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx +++ b/apps/antalmanac/src/components/ScheduleManagement/ScheduleManagement.tsx @@ -1,5 +1,6 @@ import { GlobalStyles, Stack, useMediaQuery, useTheme } from '@mui/material'; import { useEffect, useRef, useState } from 'react'; +import { useParams } from 'react-router-dom'; import { ScheduleManagementTabs } from '$components/ScheduleManagement/ScheduleManagementTabs'; import { ScheduleManagementTabsContent } from '$components/ScheduleManagement/ScheduleManagementTabsContent'; @@ -12,6 +13,7 @@ import { useTabStore } from '$stores/TabStore'; */ export function ScheduleManagement() { const { activeTab, setActiveTab } = useTabStore(); + const { tab } = useParams(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); @@ -34,6 +36,19 @@ export function ScheduleManagement() { // Change the tab to the "added classes" tab if the user was previously logged in. useEffect(() => { + if (tab) { + switch (tab) { + case 'added': + setActiveTab('added'); + break; + case 'map': + setActiveTab('map'); + break; + } + + return; + } + const userId = getLocalStorageUserId(); if (userId === null) {