From a41cb3ed8711eff9a2fbbe435e867c073904f452 Mon Sep 17 00:00:00 2001 From: Abhinav Chadaga Date: Sat, 3 Feb 2024 16:43:59 -0600 Subject: [PATCH 1/4] feat: calendar course block component (#75) --- .../components/CalendarCourseCell.stories.tsx | 67 +++++++++++++++++++ .../CalendarCourseCell/CalendarCourseCell.tsx | 50 ++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 src/stories/components/CalendarCourseCell.stories.tsx create mode 100644 src/views/components/common/CalendarCourseCell/CalendarCourseCell.tsx diff --git a/src/stories/components/CalendarCourseCell.stories.tsx b/src/stories/components/CalendarCourseCell.stories.tsx new file mode 100644 index 00000000..647a766d --- /dev/null +++ b/src/stories/components/CalendarCourseCell.stories.tsx @@ -0,0 +1,67 @@ +import { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; +import { Course, Status } from 'src/shared/types/Course'; +import { CourseMeeting, DAY_MAP } from 'src/shared/types/CourseMeeting'; +import { CourseSchedule } from 'src/shared/types/CourseSchedule'; +import Instructor from 'src/shared/types/Instructor'; +import CalendarCourseCell from 'src/views/components/common/CalendarCourseCell/CalendarCourseCell'; + +const meta = { + title: 'Components/Common/CalendarCourseCell', + component: CalendarCourseCell, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + course: { control: 'object' }, + meetingIdx: { control: 'number' }, + color: { control: 'color' }, + }, + render: (args: any) => ( +
+ +
+ ), +} satisfies Meta; +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + course: new Course({ + uniqueId: 123, + number: '311C', + fullName: "311C - Bevo's Default Course", + courseName: "Bevo's Default Course", + department: 'BVO', + creditHours: 3, + status: Status.WAITLISTED, + instructors: [new Instructor({ firstName: '', lastName: 'Bevo', fullName: 'Bevo' })], + isReserved: false, + url: '', + flags: [], + schedule: new CourseSchedule({ + meetings: [ + new CourseMeeting({ + days: [DAY_MAP.M, DAY_MAP.W, DAY_MAP.F], + startTime: 480, + endTime: 570, + location: { + building: 'UTC', + room: '123', + }, + }), + ], + }), + instructionMode: 'In Person', + semester: { + year: 2024, + season: 'Spring', + }, + }), + meetingIdx: 0, + color: 'red', + }, +}; diff --git a/src/views/components/common/CalendarCourseCell/CalendarCourseCell.tsx b/src/views/components/common/CalendarCourseCell/CalendarCourseCell.tsx new file mode 100644 index 00000000..27b2519e --- /dev/null +++ b/src/views/components/common/CalendarCourseCell/CalendarCourseCell.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Course, Status } from 'src/shared/types/Course'; +import { CourseMeeting } from 'src/shared/types/CourseMeeting'; +import ClosedIcon from '~icons/material-symbols/lock'; +import WaitlistIcon from '~icons/material-symbols/timelapse'; +import CancelledIcon from '~icons/material-symbols/warning'; +import Text from '../Text/Text'; + +export interface CalendarCourseBlockProps { + /** The Course that the meeting is for. */ + course: Course; + /* index into course meeting array to display */ + meetingIdx?: number; + /** The background color for the course. */ + color: string; +} + +const CalendarCourseBlock: React.FC = ({ course, meetingIdx }: CalendarCourseBlockProps) => { + let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null; + let rightIcon: React.ReactNode | null = null; + if (course.status === Status.WAITLISTED) { + rightIcon = ; + } else if (course.status === Status.CLOSED) { + rightIcon = ; + } else if (course.status === Status.CANCELLED) { + rightIcon = ; + } + + return ( +
+
+ + {course.department} {course.number} - {course.instructors[0].lastName} + + + {`${meeting.getTimeString({ separator: '–', capitalize: true })}${ + meeting.location ? ` – ${meeting.location.building}` : '' + }`} + +
+ {rightIcon && ( +
+ {rightIcon} +
+ )} +
+ ); +}; + +export default CalendarCourseBlock; From 1b51d65c89a4b544f4f2a9c60106e14300f9a3b0 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 5 Feb 2024 18:24:02 -0600 Subject: [PATCH 2/4] feat: Create icon helper (#77) * create icon helper * change getStatusIcon to StatusIcon react component --------- Co-authored-by: Razboy20 --- src/shared/util/icons.tsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/shared/util/icons.tsx diff --git a/src/shared/util/icons.tsx b/src/shared/util/icons.tsx new file mode 100644 index 00000000..88f4ebfd --- /dev/null +++ b/src/shared/util/icons.tsx @@ -0,0 +1,24 @@ +import React, { SVGProps } from 'react'; +import ClosedIcon from '~icons/material-symbols/lock'; +import WaitlistIcon from '~icons/material-symbols/timelapse'; +import CancelledIcon from '~icons/material-symbols/warning'; +import { Status } from '../types/Course'; + +/** + * Get Icon component based on status + * @param props.status status + * @returns React.ReactElement - the icon component + */ +export function StatusIcon(props: SVGProps & { status: Status }): React.ReactElement { + const { status, ...rest } = props; + + switch (props.status) { + case Status.WAITLISTED: + return ; + case Status.CLOSED: + return ; + case Status.CANCELLED: + return ; + default: + } +} From b2b6a062805141b1a4a716981fcafec75df41fb0 Mon Sep 17 00:00:00 2001 From: Razboy20 Date: Mon, 5 Feb 2024 21:27:22 -0600 Subject: [PATCH 3/4] refactor: replace classnames with clsx (#78) --- package.json | 2 +- pnpm-lock.yaml | 15 ++++++++------- src/views/components/common/Button/Button.tsx | 4 ++-- src/views/components/common/Card/Card.tsx | 6 +++--- src/views/components/common/Divider/Divider.tsx | 4 ++-- src/views/components/common/Icon/Icon.tsx | 4 ++-- src/views/components/common/Link/Link.tsx | 4 ++-- src/views/components/common/Popup/Popup.tsx | 6 +++--- src/views/components/common/Spinner/Spinner.tsx | 4 ++-- src/views/components/common/Text/Text.tsx | 4 ++-- .../CourseDescription/CourseDescription.tsx | 4 ++-- 11 files changed, 29 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index e8515d4e..fd7ab2a6 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@types/sql.js": "^1.4.9", "@vitejs/plugin-react": "^4.2.1", "chrome-extension-toolkit": "^0.0.51", - "classnames": "^2.5.1", + "clsx": "^2.1.0", "highcharts": "^11.3.0", "highcharts-react-official": "^3.2.1", "react": "^18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4310cf74..c5afb4a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,9 +22,9 @@ dependencies: chrome-extension-toolkit: specifier: ^0.0.51 version: 0.0.51 - classnames: - specifier: ^2.5.1 - version: 2.5.1 + clsx: + specifier: ^2.1.0 + version: 2.1.0 highcharts: specifier: ^11.3.0 version: 11.3.0 @@ -6104,10 +6104,6 @@ packages: consola: 3.2.3 dev: true - /classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - dev: false - /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -6159,6 +6155,11 @@ packages: engines: {node: '>=0.8'} dev: true + /clsx@2.1.0: + resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} + engines: {node: '>=6'} + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: diff --git a/src/views/components/common/Button/Button.tsx b/src/views/components/common/Button/Button.tsx index 902325bd..98d1e4ef 100644 --- a/src/views/components/common/Button/Button.tsx +++ b/src/views/components/common/Button/Button.tsx @@ -1,4 +1,4 @@ -import classNames from 'classnames'; +import clsx from 'clsx'; import React from 'react'; import styles from './Button.module.scss'; @@ -30,7 +30,7 @@ export function Button({