diff --git a/src/stories/injected/CourseCatalogInjectedPopup.stories.ts b/src/stories/injected/CourseCatalogInjectedPopup.stories.ts index f57274b82..843b3702d 100644 --- a/src/stories/injected/CourseCatalogInjectedPopup.stories.ts +++ b/src/stories/injected/CourseCatalogInjectedPopup.stories.ts @@ -1,111 +1,59 @@ -/* eslint-disable storybook/story-exports */ -// import { UserSchedule } from '@shared/types/UserSchedule'; -// import type { Meta, StoryObj } from '@storybook/react'; -// import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup'; +import type { Course } from '@shared/types/Course'; +import { Status } from '@shared/types/Course'; +import { UserSchedule } from '@shared/types/UserSchedule'; +import type { Meta, StoryObj } from '@storybook/react'; +import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup'; -// import { exampleCourse } from './mocked'; +import { bevoCourse, bevoScheule, MikeScottCS314Course, MikeScottCS314Schedule } from './mocked'; -// const exampleSchedule: UserSchedule = new UserSchedule({ -// courses: [exampleCourse], -// name: 'Example Schedule', -// hours: 0, -// }); -// TODO (achadaga): import this after -// https://github.com/Longhorn-Developers/UT-Registration-Plus/pull/106 is merged -// const bevoCourse: Course = new Course({ -// uniqueId: 47280, -// number: '311C', -// fullName: "BVO 311C BEVO'S SEMINAR LONGHORN CARE", -// courseName: "BEVO'S SEMINAR LONGHORN CARE", -// department: 'BVO', -// creditHours: 3, -// status: Status.OPEN, -// instructors: [new Instructor({ fullName: 'BEVO', firstName: '', lastName: 'BEVO', middleInitial: '' })], -// isReserved: false, -// description: [ -// 'Restricted to Students in the School of Longhorn Enthusiasts', -// 'Immerse yourself in the daily routine of a longhorn—sunrise pasture walks and the best shady spots for a midday siesta. Understand the behavioral science behind our mascot’s stoic demeanor during games.', -// 'BVO 311C and 312H may not both be counted.', -// 'Prerequisite: Grazing 311 or 311H.', -// 'May be counted toward the Independent Inquiry flag requirement. May be counted toward the Writing flag requirement', -// 'Offered on the letter-grade basis only.', -// ], -// schedule: new CourseSchedule({ -// meetings: [ -// new CourseMeeting({ -// days: ['Tuesday', 'Thursday'], -// startTime: 480, -// endTime: 570, -// location: { building: 'UTC', room: '123' }, -// }), -// new CourseMeeting({ -// days: ['Thursday'], -// startTime: 570, -// endTime: 630, -// location: { building: 'JES', room: '123' }, -// }), -// ], -// }), -// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', -// flags: ['Independent Inquiry', 'Writing'], -// instructionMode: 'In Person', -// semester: { -// code: '12345', -// year: 2024, -// season: 'Spring', -// }, -// }); +const meta = { + title: 'Components/Injected/CourseCatalogInjectedPopup', + component: CourseCatalogInjectedPopup, + args: { + onClose: () => {}, + }, + argTypes: { + course: { + control: { + type: 'object', + }, + }, + activeSchedule: { + control: { + type: 'object', + }, + }, + onClose: { + control: { + type: 'function', + }, + }, + }, +} satisfies Meta; -// const meta = { -// title: 'Components/Injected/CourseCatalogInjectedPopup', -// component: CourseCatalogInjectedPopup, -// args: { -// course: exampleCourse, -// activeSchedule: exampleSchedule, -// onClose: () => {}, -// }, -// argTypes: { -// course: { -// control: { -// type: 'object', -// }, -// }, -// activeSchedule: { -// control: { -// type: 'object', -// }, -// }, -// onClose: { -// control: { -// type: 'function', -// }, -// }, -// }, -// } satisfies Meta; +export default meta; +type Story = StoryObj; -// export default meta; -// type Story = StoryObj; +export const OpenCourse: Story = { + args: { + course: MikeScottCS314Course, + activeSchedule: MikeScottCS314Schedule, + }, +}; -// export const OpenCourse: Story = { -// args: { -// course: exampleCourse, -// activeSchedule: exampleSchedule, -// onClose: () => {}, -// }, -// }; +export const ClosedCourse: Story = { + args: { + course: { + ...MikeScottCS314Course, + status: Status.CLOSED, + } as Course, + activeSchedule: new UserSchedule({ courses: [], name: '', hours: 0 }), + }, +}; -// export const ClosedCourse: Story = { -// args: { -// course: { -// ...exampleCourse, -// status: Status.CLOSED, -// } satisfies Course, -// }, -// }; - -// export const CourseWithNoData: Story = { -// args: { -// course: bevoCourse, -// }, -// }; -export default {}; +export const CourseWithNoData: Story = { + args: { + course: bevoCourse, + activeSchedule: bevoScheule, + }, +}; diff --git a/src/stories/injected/CoursePopup.stories.ts b/src/stories/injected/CoursePopup.stories.ts deleted file mode 100644 index d9a3895c3..000000000 --- a/src/stories/injected/CoursePopup.stories.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Course, Status } from '@shared/types/Course'; -import { UserSchedule } from '@shared/types/UserSchedule'; -import type { Meta, StoryObj } from '@storybook/react'; -import CoursePopup from '@views/components/injected/CoursePopupOld/CoursePopup'; - -import { exampleCourse, exampleSchedule } from './mocked'; - -const meta = { - title: 'Components/Injected/CoursePopup', - component: CoursePopup, - // tags: ['autodocs'], - args: { - course: exampleCourse, - activeSchedule: exampleSchedule, - }, - argTypes: { - course: { - control: { - type: 'other', - }, - }, - activeSchedule: { - control: { - type: 'other', - }, - }, - }, - parameters: { - design: { - type: 'figma', - url: 'https://www.figma.com/file/8tsCay2FRqctrdcZ3r9Ahw/UTRP?type=design&node-id=602-1879&mode=design&t=BoS5xBrpSsjgQXqv-11', - }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Open: Story = { - args: { - course: new Course({ ...exampleCourse, status: Status.OPEN }), - activeSchedule: new UserSchedule({ - courses: [], - name: 'Example Schedule', - hours: 0, - }), - }, -}; - -export const Closed: Story = { - args: { - course: new Course({ ...exampleCourse, status: Status.CLOSED }), - }, -}; - -export const Cancelled: Story = { - args: { - course: new Course({ ...exampleCourse, status: Status.CANCELLED }), - }, -}; diff --git a/src/stories/injected/mocked.ts b/src/stories/injected/mocked.ts index b3e256dbd..f59d00806 100644 --- a/src/stories/injected/mocked.ts +++ b/src/stories/injected/mocked.ts @@ -1,5 +1,5 @@ import { Course, Status } from '@shared/types/Course'; -import { CourseMeeting } from '@shared/types/CourseMeeting'; +import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting'; import Instructor from '@shared/types/Instructor'; import { UserSchedule } from '@shared/types/UserSchedule'; @@ -47,7 +47,7 @@ export const exampleCourse: Course = new Course({ season: 'Spring', year: 2024, }, - status: Status.CANCELLED, + status: Status.OPEN, uniqueId: 12345, url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', }); @@ -55,5 +55,106 @@ export const exampleCourse: Course = new Course({ export const exampleSchedule: UserSchedule = new UserSchedule({ courses: [exampleCourse], name: 'Example Schedule', - hours: 0, + hours: 3, +}); + +export const bevoCourse: Course = new Course({ + uniqueId: 47280, + number: '311C', + fullName: "BVO 311C BEVO'S SEMINAR LONGHORN CARE", + courseName: "BEVO'S SEMINAR LONGHORN CARE", + department: 'BVO', + creditHours: 3, + status: Status.OPEN, + instructors: [new Instructor({ fullName: 'BEVO', firstName: '', lastName: 'BEVO', middleInitial: '' })], + isReserved: false, + description: [ + 'Restricted to Students in the School of Longhorn Enthusiasts', + 'Immerse yourself in the daily routine of a longhorn—sunrise pasture walks and the best shady spots for a midday siesta. Understand the behavioral science behind our mascot’s stoic demeanor during games.', + 'BVO 311C and 312H may not both be counted.', + 'Prerequisite: Grazing 311 or 311H.', + 'May be counted toward the Independent Inquiry flag requirement. May be counted toward the Writing flag requirement', + 'Offered on the letter-grade basis only.', + ], + schedule: { + meetings: [ + new CourseMeeting({ + days: ['Tuesday', 'Thursday'], + startTime: 480, + endTime: 570, + location: { building: 'UTC', room: '123' }, + }), + new CourseMeeting({ + days: ['Thursday'], + startTime: 570, + endTime: 630, + location: { building: 'JES', room: '123' }, + }), + ], + }, + url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', + flags: ['Independent Inquiry', 'Writing'], + instructionMode: 'In Person', + semester: { + code: '12345', + year: 2024, + season: 'Spring', + }, +}); + +export const bevoScheule: UserSchedule = new UserSchedule({ + courses: [bevoCourse], + name: 'Bevo Schedule', + hours: 3, +}); + +export const MikeScottCS314Course: Course = new Course({ + uniqueId: 50805, + number: '314', + fullName: 'C S 314 DATA STRUCTURES', + courseName: 'DATA STRUCTURES', + department: 'C S', + creditHours: 3, + status: Status.OPEN, + instructors: [ + new Instructor({ fullName: 'SCOTT, MICHAEL', firstName: 'MICHAEL', lastName: 'SCOTT', middleInitial: 'D' }), + ], + isReserved: true, + description: [ + 'Second part of a two-part sequence in programming. Introduction to specifications, simple unit testing, and debugging; building and using canonical data structures; algorithm analysis and reasoning techniques such as assertions and invariants.', + 'Computer Science 314 and 314H may not both be counted.', + 'BVO 311C and 312H may not both be counted.', + 'Prerequisite: Computer Science 312 or 312H with a grade of at least C-.', + 'May be counted toward the Quantitative Reasoning flag requirement.', + ], + schedule: { + meetings: [ + new CourseMeeting({ + days: [DAY_MAP.T, DAY_MAP.TH], + startTime: 480, + endTime: 570, + location: { building: 'UTC', room: '123' }, + }), + new CourseMeeting({ + days: [DAY_MAP.TH], + startTime: 570, + endTime: 630, + location: { building: 'JES', room: '123' }, + }), + ], + }, + url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/50825/', + flags: ['Writing', 'Independent Inquiry'], + instructionMode: 'In Person', + semester: { + code: '12345', + year: 2024, + season: 'Spring', + }, +}); + +export const MikeScottCS314Schedule: UserSchedule = new UserSchedule({ + courses: [MikeScottCS314Course], + name: 'Mike Scott CS314 Schedule', + hours: 3, }); diff --git a/src/views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup.tsx b/src/views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup.tsx index c9a9a8d6a..b710e0ca4 100644 --- a/src/views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup.tsx +++ b/src/views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup.tsx @@ -9,7 +9,7 @@ import HeadingAndActions from './HeadingAndActions'; interface CourseCatalogInjectedPopupProps { course: Course; - activeSchedule?: UserSchedule; + activeSchedule: UserSchedule; onClose: () => void; } @@ -27,16 +27,12 @@ export default function CourseCatalogInjectedPopup({ course, activeSchedule, onClose, -}: CourseCatalogInjectedPopupProps) { +}: CourseCatalogInjectedPopupProps): JSX.Element { return (
- +
diff --git a/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx b/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx index 60955e89f..4413f5cb7 100644 --- a/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx +++ b/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx @@ -7,8 +7,7 @@ import { Button } from '@views/components/common/Button/Button'; import { Chip, flagMap } from '@views/components/common/Chip/Chip'; import Divider from '@views/components/common/Divider/Divider'; import Text from '@views/components/common/Text/Text'; -import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript'; -import React, { useState } from 'react'; +import React from 'react'; import Add from '~icons/material-symbols/add'; import CalendarMonth from '~icons/material-symbols/calendar-month'; @@ -25,7 +24,7 @@ interface HeadingAndActionProps { /* The course to display */ course: Course; /* The active schedule */ - activeSchedule?: UserSchedule; + activeSchedule: UserSchedule; /* The function to call when the popup should be closed */ onClose: () => void; } diff --git a/src/views/components/injected/CoursePopupOld/CourseDescription/CourseDescription.module.scss b/src/views/components/injected/CoursePopupOld/CourseDescription/CourseDescription.module.scss deleted file mode 100644 index 07f9ca671..000000000 --- a/src/views/components/injected/CoursePopupOld/CourseDescription/CourseDescription.module.scss +++ /dev/null @@ -1,29 +0,0 @@ -@use 'src/views/styles/colors.module.scss'; - -.container { - margin: 20px; - padding: 12px; - - .description { - list-style-type: disc; - margin: 0px; - padding-left: 20px; - max-height: 200px; - overflow-y: auto; - - li { - padding: 0px 4px 4px; - .prerequisite { - font-weight: bold; - } - - .onlyOne { - font-style: italic; - } - - .restriction { - color: colors.$speedway_brick; - } - } - } -} diff --git a/src/views/components/injected/CoursePopupOld/CourseDescription/CourseDescription.tsx b/src/views/components/injected/CoursePopupOld/CourseDescription/CourseDescription.tsx deleted file mode 100644 index b4cda3054..000000000 --- a/src/views/components/injected/CoursePopupOld/CourseDescription/CourseDescription.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import type { Course } from '@shared/types/Course'; -import Card from '@views/components/common/Card/Card'; -import Spinner from '@views/components/common/Spinner/Spinner'; -import Text from '@views/components/common/Text/Text'; -import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper'; -import { SiteSupport } from '@views/lib/getSiteSupport'; -import clsx from 'clsx'; -import React, { useEffect, useState } from 'react'; - -import styles from './CourseDescription.module.scss'; - -type Props = { - course: Course; -}; - -const LoadStatus = { - LOADING: 'LOADING', - DONE: 'DONE', - ERROR: 'ERROR', -} as const; - -type LoadStatusType = (typeof LoadStatus)[keyof typeof LoadStatus]; - -/** - * Renders the course description component. - * - * @param {Props} props - The component props. - * @param {Course} props.course - The course object. - * @returns {JSX.Element} The rendered course description component. - */ -export default function CourseDescription({ course }: Props) { - const [description, setDescription] = useState([]); - const [status, setStatus] = useState(LoadStatus.LOADING); - - useEffect(() => { - fetchDescription(course) - .then(description => { - setStatus(LoadStatus.DONE); - setDescription(description); - }) - .catch(() => { - setStatus(LoadStatus.ERROR); - }); - }, [course]); - - return ( - - {status === LoadStatus.ERROR && ( - - Please refresh the page and log back in using your UT EID and password - - )} - {status === LoadStatus.LOADING && } - {status === LoadStatus.DONE && ( -
    - {description.map(paragraph => ( -
  • - -
  • - ))} -
- )} -
- ); -} - -interface LineProps { - line: string; -} - -function DescriptionLine({ line }: LineProps) { - const lowerCaseLine = line.toLowerCase(); - - const className = clsx({ - [styles.prerequisite]: lowerCaseLine.includes('prerequisite'), - [styles.onlyOne]: - lowerCaseLine.includes('may be') || lowerCaseLine.includes('only one') || lowerCaseLine.includes('may not'), - [styles.restriction]: lowerCaseLine.includes('restrict'), - }); - - return {line}; -} - -async function fetchDescription(course: Course): Promise { - if (!course.description?.length) { - const response = await fetch(course.url); - const text = await response.text(); - const doc = new DOMParser().parseFromString(text, 'text/html'); - - const scraper = new CourseCatalogScraper(SiteSupport.COURSE_CATALOG_DETAILS); - course.description = scraper.getDescription(doc); - } - return course.description; -} diff --git a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseButtons/CourseButtons.module.scss b/src/views/components/injected/CoursePopupOld/CourseHeader/CourseButtons/CourseButtons.module.scss deleted file mode 100644 index 701fe303c..000000000 --- a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseButtons/CourseButtons.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -@import 'src/views/styles/base.module.scss'; - -.container { - margin: 12px 4px; - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: center; - box-shadow: none; - - .button { - flex: 1; - } - - .icon { - margin: 4px; - } -} diff --git a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseButtons/CourseButtons.tsx b/src/views/components/injected/CoursePopupOld/CourseHeader/CourseButtons/CourseButtons.tsx deleted file mode 100644 index 22efadee4..000000000 --- a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseButtons/CourseButtons.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { background } from '@shared/messages'; -import type { Course } from '@shared/types/Course'; -import type { UserSchedule } from '@shared/types/UserSchedule'; -import { Button } from '@views/components/common/Button/Button'; -import Card from '@views/components/common/Card/Card'; -import Icon from '@views/components/common/Icon/Icon'; -import Text from '@views/components/common/Text/Text'; -import React from 'react'; - -import styles from './CourseButtons.module.scss'; - -type Props = { - activeSchedule?: UserSchedule; - course: Course; -}; - -const { openNewTab, addCourse, removeCourse } = background; - -/** - * This component displays the buttons for the course info popup, that allow the user to either - * navigate to other pages that are useful for the course, or to do actions on the current course. - */ -export default function CourseButtons({ course, activeSchedule }: Props) { - const openRateMyProfessorURL = () => { - const primaryInstructor = course.instructors?.[0]; - if (!primaryInstructor) return; - - const name = primaryInstructor.toString({ - format: 'first_last', - case: 'capitalize', - }); - - const url = new URL('https://www.ratemyprofessors.com/search.jsp'); - url.searchParams.append('queryBy', 'teacherName'); - url.searchParams.append('schoolName', 'university of texas at austin'); - url.searchParams.append('queryoption', 'HEADER'); - url.searchParams.append('query', name); - url.searchParams.append('facetSearch', 'true'); - - openNewTab({ url: url.toString() }); - }; - - const openSyllabiURL = () => { - const { department, number } = course; - - const { firstName, lastName } = course.instructors?.[0] ?? {}; - - const url = new URL('https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/'); - url.searchParams.append('department', department); - url.searchParams.append('course_number', number); - url.searchParams.append('instructor_first', firstName ?? ''); - url.searchParams.append('instructor_last', lastName ?? ''); - url.searchParams.append('course_type', 'In Residence'); - url.searchParams.append('search', 'Search'); - - openNewTab({ url: url.toString() }); - }; - - const openTextbookURL = () => { - const { department, number, semester, uniqueId } = course; - const url = new URL('https://www.universitycoop.com/adoption-search-results'); - url.searchParams.append('sn', `${semester.code}__${department}__${number}__${uniqueId}`); - - openNewTab({ url: url.toString() }); - }; - - const handleSaveCourse = async () => { - if (!activeSchedule) return; - addCourse({ course, scheduleName: activeSchedule.name }); - }; - - const handleRemoveCourse = async () => { - if (!activeSchedule) return; - removeCourse({ course, scheduleName: activeSchedule.name }); - }; - - const isCourseSaved = (() => { - if (!activeSchedule) return false; - return Boolean(activeSchedule.containsCourse(course)); - })(); - - return ( - - - - - - - ); -} diff --git a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseHeader.module.scss b/src/views/components/injected/CoursePopupOld/CourseHeader/CourseHeader.module.scss deleted file mode 100644 index d0806848d..000000000 --- a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseHeader.module.scss +++ /dev/null @@ -1,45 +0,0 @@ -@import 'src/views/styles/base.module.scss'; -.header { - height: auto; - color: white; - padding: 12px; - margin: 20px; - align-items: center; - position: relative; - justify-content: center; - - .close { - position: absolute; - top: 12px; - right: 12px; - cursor: pointer; - } - - .title { - display: flex; - align-items: center; - margin-right: 40px; - - .courseName { - display: -webkit-box; - -webkit-line-clamp: 1; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - // underline - } - - .uniqueId { - flex: 1; - margin-left: 8px; - } - } - - .instructors { - margin-top: 8px; - } - - .meeting { - margin-top: 8px; - } -} diff --git a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseHeader.tsx b/src/views/components/injected/CoursePopupOld/CourseHeader/CourseHeader.tsx deleted file mode 100644 index eac9d5f40..000000000 --- a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseHeader.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import type { Course } from '@shared/types/Course'; -import type { UserSchedule } from '@shared/types/UserSchedule'; -import { Button } from '@views/components/common/Button/Button'; -import Card from '@views/components/common/Card/Card'; -import Icon from '@views/components/common/Icon/Icon'; -import Link from '@views/components/common/Link/Link'; -import Text from '@views/components/common/Text/Text'; -import React from 'react'; - -import CloseIcon from '~icons/material-symbols/close'; -import CopyIcon from '~icons/material-symbols/content-copy'; - -import CourseButtons from './CourseButtons/CourseButtons'; -import styles from './CourseHeader.module.scss'; - -type Props = { - course: Course; - activeSchedule?: UserSchedule; - onClose: () => void; -}; - -/** - * This component displays the header of the course info popup. - * It displays the course name, unique id, instructors, and schedule, all formatted nicely. - */ -export default function CourseHeader({ course, activeSchedule, onClose }: Props) { - // const getBuildingUrl = (building?: string): string | undefined => { - // if (!building) return undefined; - // return `https://utdirect.utexas.edu/apps/campus/buildings/nlogon/maps/UTM/${building}/`; - // }; - - return ( -
-
- - {course.courseName} - - - {`(${course.department} ${course.number})`} - -
- - -
-
-
- - with{' '} - {course.instructors.map(instructor => ( - {instructor.lastName} - ))} - -
-
- // - // - //
- // - // {course.courseName} ({course.department} {course.number}) blahhhhh - // - // - // #{course.uniqueId} - // - //
- // - // {`with ${!course.instructors.length ? 'TBA' : ''}`} - // {course.instructors.map((instructor, index) => { - // const name = instructor.toString({ - // format: 'first_last', - // case: 'capitalize', - // }); - - // const url = instructor.getDirectoryUrl(); - // const numInstructors = course.instructors.length; - // const isLast = course.instructors.length > 1 && index === course.instructors.length - 1; - // return ( - // - // {numInstructors > 1 && index === course.instructors.length - 1 ? '& ' : ''} - // - // {name} - // - // {numInstructors > 2 && !isLast ? ', ' : ''} - // - // ); - // })} - // - // {course.schedule.meetings.map(meeting => ( - // - // - // {meeting.getDaysString({ - // format: 'long', - // separator: 'short', - // })} - // - // {' at '} - // - // {meeting.getTimeString({ - // separator: 'to', - // capitalize: true, - // })} - // - // {' in '} - // - // {meeting.location?.building ?? 'TBA'} - // - // - // ))} - - // - //
- ); -} diff --git a/src/views/components/injected/CoursePopupOld/CoursePopup.module.scss b/src/views/components/injected/CoursePopupOld/CoursePopup.module.scss deleted file mode 100644 index 8e263b4c1..000000000 --- a/src/views/components/injected/CoursePopupOld/CoursePopup.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -.popup { - border-radius: 12px; - position: relative; - width: 55%; - overflow-y: auto; - max-height: 90%; - - // fade in animation - animation: fadeIn 0.2s ease-out; -} - -// fade in animation - -@keyframes fadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} diff --git a/src/views/components/injected/CoursePopupOld/CoursePopup.tsx b/src/views/components/injected/CoursePopupOld/CoursePopup.tsx deleted file mode 100644 index 971a4f1b5..000000000 --- a/src/views/components/injected/CoursePopupOld/CoursePopup.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { Course } from '@shared/types/Course'; -import type { UserSchedule } from '@shared/types/UserSchedule'; -import Popup from '@views/components/common/Popup/Popup'; -import React from 'react'; - -import CourseDescription from './CourseDescription/CourseDescription'; -import CourseHeader from './CourseHeader/CourseHeader'; -import styles from './CoursePopup.module.scss'; -import GradeDistribution from './GradeDistribution/GradeDistribution'; - -interface Props { - course: Course; - activeSchedule?: UserSchedule; - onClose: () => void; -} - -/** - * The popup that appears when the user clicks on a course for more details. - */ -export default function CoursePopup({ course, activeSchedule, onClose }: Props) { - return ( - - - - - - ); -} diff --git a/src/views/components/injected/CoursePopupOld/GradeDistribution/GradeDistribution.module.scss b/src/views/components/injected/CoursePopupOld/GradeDistribution/GradeDistribution.module.scss deleted file mode 100644 index 04bef21e4..000000000 --- a/src/views/components/injected/CoursePopupOld/GradeDistribution/GradeDistribution.module.scss +++ /dev/null @@ -1,48 +0,0 @@ -@use 'src/views/styles/colors.module.scss'; -@use 'src/views/styles/elevation.module.scss'; - -.chartContainer { - height: 250px; - margin: 20px; - padding: 12px; - position: relative; - - .selectContainer { - display: flex; - position: absolute; - width: 100%; - margin-top: -8px; - justify-content: center; - - select { - z-index: elevation.$MAX_Z_INDEX; - padding: 4px; - font-family: 'Inter'; - border-radius: 8px; - border-color: colors.$charcoal; - } - } - - :global(.highcharts-background) { - fill: transparent; - } -} - -.textContainer { - margin: 20px; - padding: 12px; - display: flex; - align-items: center; - justify-content: center; - - .text { - padding: 12px; - box-shadow: none; - text-align: center; - - // add some vertical padding to each element - > * { - margin: 0.2em 0; - } - } -} diff --git a/src/views/components/injected/CoursePopupOld/GradeDistribution/GradeDistribution.tsx b/src/views/components/injected/CoursePopupOld/GradeDistribution/GradeDistribution.tsx deleted file mode 100644 index a5bef8891..000000000 --- a/src/views/components/injected/CoursePopupOld/GradeDistribution/GradeDistribution.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import type { Course, Semester } from '@shared/types/Course'; -import type { Distribution, LetterGrade } from '@shared/types/Distribution'; -import Card from '@views/components/common/Card/Card'; -import Icon from '@views/components/common/Icon/Icon'; -import Spinner from '@views/components/common/Spinner/Spinner'; -import Text from '@views/components/common/Text/Text'; -import { - NoDataError, - queryAggregateDistribution, - querySemesterDistribution, -} from '@views/lib/database/queryDistribution'; -import colors from '@views/styles/colors.module.scss'; -import Highcharts from 'highcharts'; -import HighchartsReact from 'highcharts-react-official'; -import React, { useEffect, useRef, useState } from 'react'; - -import styles from './GradeDistribution.module.scss'; - -const DataStatus = { - LOADING: 'LOADING', - FOUND: 'FOUND', - NOT_FOUND: 'NOT_FOUND', - ERROR: 'ERROR', -} as const; - -type DataStatusType = (typeof DataStatus)[keyof typeof DataStatus]; - -interface Props { - course: Course; -} - -const GRADE_COLORS = { - A: colors.turtle_pond, - 'A-': colors.turtle_pond, - 'B+': colors.cactus, - B: colors.cactus, - 'B-': colors.cactus, - 'C+': colors.sunshine, - C: colors.sunshine, - 'C-': colors.sunshine, - 'D+': colors.tangerine, - D: colors.tangerine, - 'D-': colors.tangerine, - F: colors.speedway_brick, -} as const satisfies Record; - -/** - * A chart to fetch and display the grade distribution for a course - * @returns - */ -export default function GradeDistribution({ course }: Props) { - const ref = useRef(null); - const [semesters, setSemesters] = useState([]); - const [selectedSemester, setSelectedSemester] = useState(null); - const [distribution, setDistribution] = useState(null); - const [status, setStatus] = useState(DataStatus.LOADING); - - const [chartOptions, setChartOptions] = useState({ - title: { - text: undefined, - }, - subtitle: { - text: undefined, - }, - legend: { - enabled: false, - }, - xAxis: { - title: { - text: 'Grades', - }, - categories: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F'], - crosshair: true, - }, - yAxis: { - min: 0, - title: { - text: 'Students', - }, - }, - chart: { - style: { - fontFamily: 'Inter', - fontWeight: '600', - }, - spacingBottom: 25, - spacingTop: 25, - height: 250, - }, - credits: { - enabled: false, - }, - accessibility: { - enabled: false, - }, - tooltip: { - headerFormat: '{point.key}', - pointFormat: - '', - footerFormat: '
{point.y:.0f} Students
', - shared: true, - useHTML: true, - }, - plotOptions: { - bar: { - pointPadding: 0.2, - borderWidth: 0, - }, - series: { - animation: { - duration: 700, - }, - }, - }, - series: [ - { - type: 'column', - name: 'Grades', - data: Array.from({ length: 12 }, () => 0), - }, - ], - }); - - const updateChart = (distribution: Distribution) => { - setChartOptions(options => ({ - ...options, - series: [ - { - type: 'column', - name: 'Grades', - data: Object.entries(distribution).map(([grade, count]) => ({ - y: count, - color: GRADE_COLORS[grade as LetterGrade], - })), - }, - ], - })); - window.dispatchEvent(new Event('resize')); - }; - - useEffect(() => { - queryAggregateDistribution(course) - .then(([distribution, semesters]) => { - setSemesters(semesters); - updateChart(distribution); - setStatus(DataStatus.FOUND); - }) - .catch(err => { - if (err instanceof NoDataError) { - return setStatus(DataStatus.NOT_FOUND); - } - return setStatus(DataStatus.ERROR); - }); - }, [course]); - - useEffect(() => { - (async () => { - let distribution: Distribution; - if (selectedSemester) { - distribution = await querySemesterDistribution(course, selectedSemester); - } else { - [distribution] = await queryAggregateDistribution(course); - } - updateChart(distribution); - setStatus(DataStatus.FOUND); - })().catch(err => { - if (err instanceof NoDataError) { - return setStatus(DataStatus.NOT_FOUND); - } - return setStatus(DataStatus.ERROR); - }); - }, [selectedSemester, course]); - - const handleSelectSemester = (event: React.ChangeEvent) => { - const index = parseInt(event.target.value, 10); - if (index === 0) { - setSelectedSemester(null); - } else { - setSelectedSemester(semesters[index - 1]); - } - }; - - if (status === DataStatus.FOUND) { - return ( - - {semesters.length > 0 && ( -
- -
- )} - -
- ); - } - - return ( - - {status === DataStatus.LOADING && } - {status === DataStatus.ERROR && ( - - - There was an error fetching the grade distribution data - - - - )} - {status === DataStatus.NOT_FOUND && ( - - - No grade distribution data was found for this course - - - - )} - - ); -}