diff --git a/packages/frontend/components/Sidebar/NUPathSection.tsx b/packages/frontend/components/Sidebar/NUPathSection.tsx new file mode 100644 index 000000000..019015369 --- /dev/null +++ b/packages/frontend/components/Sidebar/NUPathSection.tsx @@ -0,0 +1,282 @@ +import { NUPathEnum, ScheduleCourse2 } from "@graduate/common"; +import { SidebarValidationStatus } from "./Sidebar"; +import { useState } from "react"; +import { Box, Flex, Spinner, Text } from "@chakra-ui/react"; +import { HelperToolTip } from "../Help"; +import { + CheckIcon, + ChevronDownIcon, + ChevronUpIcon, + SmallCloseIcon, +} from "@chakra-ui/icons"; + +interface NUPathSectionProps { + coursesTaken: ScheduleCourse2[]; + dndIdPrefix: string; + loading?: boolean; +} +const nuPathDisplayAndAbbr: [ + nupath: NUPathEnum, + displayName: string, + abbreviation: string +][] = [ + [NUPathEnum.ND, "Natural and Designed World", "ND"], + [NUPathEnum.EI, "Creative Expression/Innovation", "EI"], + [NUPathEnum.IC, "Interpreting Culture", "IC"], + [NUPathEnum.FQ, "Formal and Quantitative Reasoning", "FQ"], + [NUPathEnum.SI, "Societies and Institutions", "SI"], + [NUPathEnum.AD, "Analyzing/Using Data", "AD"], + [NUPathEnum.DD, "Difference and Diversity", "DD"], + [NUPathEnum.ER, "Ethical Reasoning", "ER"], + [NUPathEnum.WF, "First Year Writing", "WF"], + [NUPathEnum.WD, "Advanced Writing in the Disciplines", "WD"], + [NUPathEnum.WI, "Writing Intensive", "WI"], + [NUPathEnum.EX, "Integration Experience", "EX"], + [NUPathEnum.CE, "Capstone Experience", "CE"], +]; +const grey = "neutral.400"; +const green = "states.success.main"; + +const NUPathSection: React.FC = ({ + coursesTaken, + dndIdPrefix, + loading, +}) => { + const [opened, setOpened] = useState(false); + + let validationStatus = SidebarValidationStatus.Error; + + const nupathMap: Record = {}; + + for (const course of coursesTaken) { + if (!course.nupaths) { + continue; + } + + for (const nupath of course.nupaths) { + nupathMap[nupath] = (nupathMap[nupath] || 0) + 1; + } + } + + const wiCount = nupathMap[NUPathEnum.WI] || 0; + + if (loading) { + validationStatus = SidebarValidationStatus.Loading; + } else if (Object.keys(nupathMap).length === 13 && wiCount >= 2) { + // Sidebar is complete if all 13 nupaths have been fulfilled (including 2 writing intensives) + validationStatus = SidebarValidationStatus.Complete; + } + + return ( + + { + setOpened(!opened); + }} + direction="row" + justifyContent="space-between" + alignItems="flex-start" + color="dark.main" + fontWeight="bold" + p="md" + margin="0" + backgroundColor="neutral.50" + transition="background-color 0.25s ease" + _hover={{ + backgroundColor: "neutral.100", + }} + _active={{ + backgroundColor: "neutral.200", + }} + display="flex" + position="sticky" + top="0px" + zIndex={1} + > + + + {/* + The following three icons appear and disappear based on opacity to allow for transitions (if they're conditionally rendered, then transitions can't occur). + */} + + + + + + NUpath Requirements + + + + {opened ? ( + + ) : ( + + )} + + + + {loading && ( + + + Loading... + + )} + {opened && !loading && ( + + + Complete the following NUpath requirements: + + <> + {nuPathDisplayAndAbbr.map( + ([nupath, displayName, abbreviation], idx) => { + const numTaken = nupathMap[nupath] || 0; + return ( + + ); + } + )} + + + )} + + + ); +}; + +interface NUPathRequirementProps { + nupath: string; + abbreviation: string; + displayName: string; + numTaken: number; +} + +const NUPathRequirement: React.FC = ({ + nupath, + abbreviation, + displayName, + numTaken, +}) => { + const isWI = nupath === NUPathEnum.WI; + const isSatisfied = (isWI && numTaken >= 2) || (!isWI && numTaken >= 1); + + return ( + + + + + + + + {abbreviation} + + + {displayName} + + {isWI && } + + + ); +}; + +export default NUPathSection; diff --git a/packages/frontend/components/Sidebar/Sidebar.tsx b/packages/frontend/components/Sidebar/Sidebar.tsx index f6db4d986..f04dcc7bb 100644 --- a/packages/frontend/components/Sidebar/Sidebar.tsx +++ b/packages/frontend/components/Sidebar/Sidebar.tsx @@ -34,6 +34,7 @@ import { } from "../../validation-worker/worker-messages"; import { useFetchCourses, useMajor } from "../../hooks"; import { HelperToolTip } from "../Help"; +import NUPathSection from "./NUPathSection"; export enum SidebarValidationStatus { Loading = "Loading", @@ -92,15 +93,16 @@ const Sidebar: React.FC = memo( MajorValidationResult | undefined >(undefined); + const coursesTaken = [ + ...getAllCoursesFromPlan(selectedPlan), + ...transferCourses, + ]; + const revalidateMajor = () => { setValidationStatus(undefined); if (!selectedPlan || !major || !workerRef.current) return; currentRequestNum += 1; - const coursesTaken = [ - ...getAllCoursesFromPlan(selectedPlan), - ...transferCourses, - ]; const validationInfo: WorkerPostInfo = { major: major, taken: coursesTaken, @@ -227,6 +229,11 @@ const Sidebar: React.FC = memo( > {courseData && ( <> + {major.requirementSections.map((section, index) => { const sectionValidationError: MajorValidationError | undefined = getSectionError(index, validationStatus);