Merge branch 'hackathon' into abhinavchadaga/course-catalog-popup
This commit is contained in:
50
package.json
50
package.json
@@ -17,6 +17,7 @@
|
|||||||
"build-storybook": "storybook build"
|
"build-storybook": "storybook build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hello-pangea/dnd": "^16.5.0",
|
||||||
"@types/sql.js": "^1.4.9",
|
"@types/sql.js": "^1.4.9",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"chrome-extension-toolkit": "^0.0.51",
|
"chrome-extension-toolkit": "^0.0.51",
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-devtools-core": "^5.0.0",
|
"react-devtools-core": "^5.0.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-window": "^1.8.10",
|
||||||
"sass": "^1.70.0",
|
"sass": "^1.70.0",
|
||||||
"sql.js": "1.10.2",
|
"sql.js": "1.10.2",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
@@ -34,30 +36,30 @@
|
|||||||
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
||||||
"@iconify-json/material-symbols": "^1.1.72",
|
"@iconify-json/material-symbols": "^1.1.72",
|
||||||
"@storybook/addon-designs": "^7.0.9",
|
"@storybook/addon-designs": "^7.0.9",
|
||||||
"@storybook/addon-essentials": "^7.6.12",
|
"@storybook/addon-essentials": "^7.6.13",
|
||||||
"@storybook/addon-links": "^7.6.12",
|
"@storybook/addon-links": "^7.6.13",
|
||||||
"@storybook/blocks": "^7.6.12",
|
"@storybook/blocks": "^7.6.13",
|
||||||
"@storybook/react": "^7.6.12",
|
"@storybook/react": "^7.6.13",
|
||||||
"@storybook/react-vite": "^7.6.12",
|
"@storybook/react-vite": "^7.6.13",
|
||||||
"@storybook/test": "^7.6.12",
|
"@storybook/test": "^7.6.13",
|
||||||
"@svgr/core": "^8.1.0",
|
"@svgr/core": "^8.1.0",
|
||||||
"@svgr/plugin-jsx": "^8.1.0",
|
"@svgr/plugin-jsx": "^8.1.0",
|
||||||
"@types/chrome": "^0.0.260",
|
"@types/chrome": "^0.0.260",
|
||||||
"@types/node": "^20.11.16",
|
"@types/node": "^20.11.17",
|
||||||
"@types/prompts": "^2.4.9",
|
"@types/prompts": "^2.4.9",
|
||||||
"@types/react": "^18.2.51",
|
"@types/react": "^18.2.55",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react-dom": "^18.2.19",
|
||||||
"@types/semver": "^7.5.6",
|
"@types/semver": "^7.5.6",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^6.20.0",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
"@unocss/eslint-config": "^0.58.4",
|
"@unocss/eslint-config": "^0.58.5",
|
||||||
"@unocss/postcss": "^0.58.4",
|
"@unocss/postcss": "^0.58.5",
|
||||||
"@unocss/preset-uno": "^0.58.4",
|
"@unocss/preset-uno": "^0.58.5",
|
||||||
"@unocss/preset-web-fonts": "^0.58.4",
|
"@unocss/preset-web-fonts": "^0.58.5",
|
||||||
"@unocss/reset": "^0.58.5",
|
"@unocss/reset": "^0.58.5",
|
||||||
"@unocss/transformer-directives": "^0.58.4",
|
"@unocss/transformer-directives": "^0.58.5",
|
||||||
"@unocss/transformer-variant-group": "^0.58.4",
|
"@unocss/transformer-variant-group": "^0.58.5",
|
||||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||||
"chromatic": "^10.9.1",
|
"chromatic": "^10.9.1",
|
||||||
"cssnano": "^6.0.3",
|
"cssnano": "^6.0.3",
|
||||||
@@ -71,7 +73,7 @@
|
|||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jsdoc": "^48.0.4",
|
"eslint-plugin-jsdoc": "^48.0.6",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
@@ -79,15 +81,15 @@
|
|||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
"eslint-plugin-storybook": "^0.6.15",
|
"eslint-plugin-storybook": "^0.6.15",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"postcss": "^8.4.33",
|
"postcss": "^8.4.35",
|
||||||
"prettier": "^3.2.4",
|
"prettier": "^3.2.5",
|
||||||
"react-dev-utils": "^12.0.1",
|
"react-dev-utils": "^12.0.1",
|
||||||
"react-devtools": "^5.0.0",
|
"react-devtools": "^5.0.0",
|
||||||
"storybook": "^7.6.12",
|
"storybook": "^7.6.13",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"unocss": "^0.58.4",
|
"unocss": "^0.58.5",
|
||||||
"unplugin-icons": "^0.18.3",
|
"unplugin-icons": "^0.18.5",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.1.1",
|
||||||
"vite-plugin-inspect": "^0.8.3"
|
"vite-plugin-inspect": "^0.8.3"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|||||||
1854
pnpm-lock.yaml
generated
1854
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
62
src/stories/components/CalendarCourse.stories.tsx
Normal file
62
src/stories/components/CalendarCourse.stories.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { Course, Status } from '@shared/types/Course';
|
||||||
|
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
|
||||||
|
import { CourseSchedule } from '@shared/types/CourseSchedule';
|
||||||
|
import Instructor from '@shared/types/Instructor';
|
||||||
|
import CalendarCourse from '@views/components/common/CalendarCourseBlock/CalendarCourseMeeting';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/CalendarCourseMeeting',
|
||||||
|
component: CalendarCourse,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
course: { control: 'object' },
|
||||||
|
meetingIdx: { control: 'number' },
|
||||||
|
color: { control: 'color' },
|
||||||
|
rightIcon: { control: 'object' },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof CalendarCourse>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
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.OPEN,
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
};
|
||||||
23
src/stories/components/Chip.stories.tsx
Normal file
23
src/stories/components/Chip.stories.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { Chip } from 'src/views/components/common/Chip/Chip';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/Chip',
|
||||||
|
component: Chip,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
label: { control: 'text' },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Chip>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
label: 'QR',
|
||||||
|
},
|
||||||
|
};
|
||||||
25
src/stories/components/ConflictsWithWarning.stories.tsx
Normal file
25
src/stories/components/ConflictsWithWarning.stories.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import ConflictsWithWarning from '@views/components/common/ConflictsWithWarning/ConflictsWithWarning';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/ConflictsWithWarning',
|
||||||
|
component: ConflictsWithWarning,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
ConflictingCourse: { control: 'string' },
|
||||||
|
SectionNumber: { control: 'string' },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof ConflictsWithWarning>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
ConflictingCourse: 'BVO 311C',
|
||||||
|
SectionNumber: '47280',
|
||||||
|
},
|
||||||
|
};
|
||||||
25
src/stories/components/InfoCard.stories.tsx
Normal file
25
src/stories/components/InfoCard.stories.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { InfoCard } from 'src/views/components/common/InfoCard/InfoCard';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/InfoCard',
|
||||||
|
component: InfoCard,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
titleText: { control: 'text' },
|
||||||
|
bodyText: { control: 'text' },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof InfoCard>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
titleText: 'WAITLIST SIZE',
|
||||||
|
bodyText: '14 Students',
|
||||||
|
},
|
||||||
|
};
|
||||||
96
src/stories/components/List.stories.tsx
Normal file
96
src/stories/components/List.stories.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { Course, Status } from '@shared/types/Course';
|
||||||
|
import { CourseMeeting } from '@shared/types/CourseMeeting';
|
||||||
|
import Instructor from '@shared/types/Instructor';
|
||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import List from '@views/components/common/List/List';
|
||||||
|
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
|
||||||
|
import React from 'react';
|
||||||
|
import { test_colors } from './PopupCourseBlock.stories';
|
||||||
|
|
||||||
|
const numberOfCourses = 5;
|
||||||
|
|
||||||
|
const generateCourses = count => {
|
||||||
|
const courses = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const course = new Course({
|
||||||
|
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
|
creditHours: 3,
|
||||||
|
department: 'C S',
|
||||||
|
description: [
|
||||||
|
'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.',
|
||||||
|
'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.',
|
||||||
|
'May be counted toward the Quantitative Reasoning flag requirement.',
|
||||||
|
'Designed to accommodate 100 or more students.',
|
||||||
|
'Taught as a Web-based course.',
|
||||||
|
],
|
||||||
|
flags: ['Quantitative Reasoning'],
|
||||||
|
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
|
instructionMode: 'Online',
|
||||||
|
instructors: [
|
||||||
|
new Instructor({
|
||||||
|
firstName: 'Bevo',
|
||||||
|
lastName: 'Bevo',
|
||||||
|
fullName: 'Bevo Bevo',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
isReserved: false,
|
||||||
|
number: '303E',
|
||||||
|
schedule: {
|
||||||
|
meetings: [
|
||||||
|
new CourseMeeting({
|
||||||
|
days: ['Tuesday', 'Thursday'],
|
||||||
|
endTime: 660,
|
||||||
|
startTime: 570,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
semester: {
|
||||||
|
code: '12345',
|
||||||
|
season: 'Spring',
|
||||||
|
year: 2024,
|
||||||
|
},
|
||||||
|
status: Status.WAITLISTED,
|
||||||
|
uniqueId: 12345 + i, // Make uniqueId different for each course
|
||||||
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
|
||||||
|
});
|
||||||
|
|
||||||
|
courses.push(course);
|
||||||
|
}
|
||||||
|
|
||||||
|
return courses;
|
||||||
|
};
|
||||||
|
|
||||||
|
const exampleCourses = generateCourses(numberOfCourses);
|
||||||
|
const generateCourseBlocks = (exampleCourses, colors) =>
|
||||||
|
exampleCourses.map((course, i) => <PopupCourseBlock key={course.uniqueId} course={course} colors={colors[i]} />);
|
||||||
|
const exampleCourseBlocks = generateCourseBlocks(exampleCourses, test_colors);
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/List',
|
||||||
|
component: List,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
draggableElements: { control: 'object' },
|
||||||
|
itemHeight: { control: 'number' },
|
||||||
|
listHeight: { control: 'number' },
|
||||||
|
listWidth: { control: 'number' },
|
||||||
|
gap: { control: 'number' },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof List>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
draggableElements: exampleCourseBlocks,
|
||||||
|
itemHeight: 55,
|
||||||
|
listHeight: 300,
|
||||||
|
listWidth: 300,
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -95,7 +95,7 @@ export const Variants: Story = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const colors = Object.keys(theme.colors)
|
export const test_colors = Object.keys(theme.colors)
|
||||||
// check that the color is a colorway (is an object)
|
// check that the color is a colorway (is an object)
|
||||||
.filter(color => typeof theme.colors[color] === 'object')
|
.filter(color => typeof theme.colors[color] === 'object')
|
||||||
.slice(0, 17)
|
.slice(0, 17)
|
||||||
@@ -104,7 +104,7 @@ const colors = Object.keys(theme.colors)
|
|||||||
export const AllColors: Story = {
|
export const AllColors: Story = {
|
||||||
render: props => (
|
render: props => (
|
||||||
<div className='grid grid-rows-9 grid-cols-2 grid-flow-col max-w-2xl w-90vw gap-x-4 gap-y-2'>
|
<div className='grid grid-rows-9 grid-cols-2 grid-flow-col max-w-2xl w-90vw gap-x-4 gap-y-2'>
|
||||||
{colors.map((color, i) => (
|
{test_colors.map((color, i) => (
|
||||||
<PopupCourseBlock key={color.primaryColor} course={exampleCourse} colors={color} />
|
<PopupCourseBlock key={color.primaryColor} course={exampleCourse} colors={color} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses/ScheduleTotalHoursAndCourses';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/ScheduleTotalHoursAndCourses',
|
||||||
|
component: ScheduleTotalHoursAndCourses,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
scheduleName: { control: 'text' },
|
||||||
|
totalHours: { control: 'number' },
|
||||||
|
totalCourses: { control: 'number' }
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof ScheduleTotalHoursAndCourses>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
scheduleName: 'SCHEDULE',
|
||||||
|
totalHours: 22,
|
||||||
|
totalCourses: 8
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
.component {
|
||||||
|
display: flex;
|
||||||
|
padding: 7px 7px 9px 7px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 5px;
|
||||||
|
flex: 1 0 0;
|
||||||
|
align-self: stretch;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #cbd5e1;
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
gap: 7px;
|
||||||
|
.course-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
width: 154px;
|
||||||
|
.course {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.time-and-location {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Course } from 'src/shared/types/Course';
|
||||||
|
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
|
||||||
|
import styles from './CalendarCourseMeeting.module.scss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the CalendarCourseMeeting component.
|
||||||
|
*/
|
||||||
|
export interface CalendarCourseMeetingProps {
|
||||||
|
/** 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;
|
||||||
|
/** The icon to display on the right side of the course. This is optional. */
|
||||||
|
rightIcon?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `CalendarCourseMeeting` is a functional component that displays a course meeting.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <CalendarCourseMeeting course={course} meeting={meeting} color="red" rightIcon={<Icon />} />
|
||||||
|
*/
|
||||||
|
const CalendarCourseMeeting: React.FC<CalendarCourseMeetingProps> = ({
|
||||||
|
course,
|
||||||
|
meetingIdx,
|
||||||
|
}: CalendarCourseMeetingProps) => {
|
||||||
|
let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null;
|
||||||
|
return (
|
||||||
|
<div className={styles.component}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles['course-detail']}>
|
||||||
|
<div className={styles.course}>
|
||||||
|
{course.department} {course.number} - {course.instructors[0].lastName}
|
||||||
|
</div>
|
||||||
|
<div className={styles['time-and-location']}>
|
||||||
|
{`${meeting.getTimeString({ separator: '-', capitalize: true })}${
|
||||||
|
meeting.location ? ` - ${meeting.location.building}` : ''
|
||||||
|
}`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CalendarCourseMeeting;
|
||||||
27
src/views/components/common/Chip/Chip.tsx
Normal file
27
src/views/components/common/Chip/Chip.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Text from '../Text/Text';
|
||||||
|
|
||||||
|
|
||||||
|
export const flags = ["WR", "QR", "GC", "CD", "E", "II"]
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reusable chip component that follows the design system of the extension.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function Chip({
|
||||||
|
label
|
||||||
|
}: React.PropsWithChildren<Props>): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Text as = 'div' variant = 'h4'
|
||||||
|
className="min-w-5 inline-flex items-center justify-center gap-2.5 rounded-lg px-1 py-0.5"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#FFD600"
|
||||||
|
}}>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Text from '../Text/Text';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for ConflictWithWarningProps
|
||||||
|
*/
|
||||||
|
export interface ConflictsWithWarningProps {
|
||||||
|
ConflictingCourse: string;
|
||||||
|
SectionNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ConflictsWithWarning component is used to display a warning message when a course conflicts
|
||||||
|
* with another course as part of the labels and details section
|
||||||
|
*
|
||||||
|
* @param props ConflictsWithWarningProps
|
||||||
|
*/
|
||||||
|
export default function ConflictsWithWarning( { ConflictingCourse, SectionNumber }: ConflictsWithWarningProps): JSX.Element {
|
||||||
|
const UniqueCourseConflictText = `${ConflictingCourse} (${SectionNumber})`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-w-21 w-21 flex flex-col items-start gap-2.5 rounded bg-[#AF2E2D] p-2.5">
|
||||||
|
<ConflictsWithoutWarningText>
|
||||||
|
Conflicts With:
|
||||||
|
</ConflictsWithoutWarningText>
|
||||||
|
<ConflictsWithoutWarningText>
|
||||||
|
{UniqueCourseConflictText}
|
||||||
|
</ConflictsWithoutWarningText>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConflictsWithoutWarningText( {children}: {children: string} ) {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
variant='mini'
|
||||||
|
as='span'
|
||||||
|
className='text-white'
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
src/views/components/common/InfoCard/InfoCard.tsx
Normal file
53
src/views/components/common/InfoCard/InfoCard.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Text from '../Text/Text';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
titleText: string;
|
||||||
|
bodyText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A maybe reusable InfoCard component that follows the design system of the extension.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function InfoCard({
|
||||||
|
titleText,
|
||||||
|
bodyText
|
||||||
|
}: React.PropsWithChildren<Props>): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div style = {{
|
||||||
|
display: "flex",
|
||||||
|
width: "200px",
|
||||||
|
minWidth: "200px",
|
||||||
|
maxWidth: "200px",
|
||||||
|
padding: "15px",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: "1px solid #D6D2C4",
|
||||||
|
background: "#FFF" //White
|
||||||
|
}}>
|
||||||
|
<div style = {{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
gap: "7px",
|
||||||
|
alignSelf: "stretch",
|
||||||
|
}}>
|
||||||
|
<Text variant = "h4" as = 'span'
|
||||||
|
style = {{
|
||||||
|
color: '#F8971F', //Orange
|
||||||
|
}}>
|
||||||
|
{titleText}
|
||||||
|
</Text>
|
||||||
|
<Text variant = "small" as = 'span'
|
||||||
|
style = {{
|
||||||
|
color: '#333F48', //Black
|
||||||
|
}}>
|
||||||
|
{bodyText}
|
||||||
|
</Text>
|
||||||
|
</ div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ export default function Link(props: PropsWithChildren<Props>) {
|
|||||||
<Text
|
<Text
|
||||||
color='bluebonnet'
|
color='bluebonnet'
|
||||||
{...passedProps}
|
{...passedProps}
|
||||||
span
|
as='span'
|
||||||
className={clsx(
|
className={clsx(
|
||||||
styles.link,
|
styles.link,
|
||||||
{
|
{
|
||||||
|
|||||||
168
src/views/components/common/List/List.tsx
Normal file
168
src/views/components/common/List/List.tsx
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||||
|
import React, { ReactElement, useCallback, useState } from 'react';
|
||||||
|
import { areEqual } from 'react-window';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ctrl + f dragHandleProps on PopupCourseBlock.tsx for example implementation of drag handle (two lines of code)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the List component.
|
||||||
|
*/
|
||||||
|
export interface ListProps {
|
||||||
|
draggableElements: any[]; // Will later define draggableElements based on what types
|
||||||
|
// of components are draggable.
|
||||||
|
itemHeight: number;
|
||||||
|
listHeight: number;
|
||||||
|
listWidth: number;
|
||||||
|
gap: number; // Impacts the spacing between items in the list
|
||||||
|
}
|
||||||
|
|
||||||
|
function initial(draggableElements: any[] = []) {
|
||||||
|
return draggableElements.map((element, index) => ({
|
||||||
|
id: `id:${index}`,
|
||||||
|
content: element as ReactElement,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function reorder(list: { id: string; content: ReactElement }[], startIndex: number, endIndex: number) {
|
||||||
|
const result = Array.from(list);
|
||||||
|
const [removed] = result.splice(startIndex, 1);
|
||||||
|
result.splice(endIndex, 0, removed);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyle({ provided, style /* , isDragging, gap */ }) {
|
||||||
|
const combined = {
|
||||||
|
...style,
|
||||||
|
...provided.draggableProps.style,
|
||||||
|
};
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Item({ provided, item, style, isDragging /* , gap */ }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...provided.draggableProps}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
style={getStyle({ provided, style /* , isDragging, gap */ })}
|
||||||
|
className={`item ${isDragging ? 'is-dragging' : ''}`}
|
||||||
|
>
|
||||||
|
{React.cloneElement(item.content, { dragHandleProps: provided.dragHandleProps })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RowProps {
|
||||||
|
data: any; // DraggableElements[]; Need to define DraggableElements interface once those components are ready
|
||||||
|
index: number;
|
||||||
|
style: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Row: React.FC<RowProps> = React.memo(({ data: { items, gap }, index, style }) => {
|
||||||
|
const item = items[index];
|
||||||
|
const adjustedStyle = {
|
||||||
|
...style,
|
||||||
|
height: `calc(${style.height}px - ${gap}px)`, // Reduce the height by gap to accommodate the margin
|
||||||
|
marginBottom: `${gap}px`, // Add gap as bottom margin
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Draggable draggableId={item.id} index={index} key={item.id}>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
{provided => <Item provided={provided} item={item} style={adjustedStyle} gap={gap} />}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
}, areEqual);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `List` is a functional component that displays a course meeting.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <List draggableElements={elements} />
|
||||||
|
*/
|
||||||
|
const List: React.FC<ListProps> = ({ draggableElements, itemHeight, listHeight, listWidth, gap = 12 }: ListProps) => {
|
||||||
|
const [items, setItems] = useState(() => initial(draggableElements));
|
||||||
|
|
||||||
|
const onDragEnd = useCallback(
|
||||||
|
result => {
|
||||||
|
if (!result.destination) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.source.index === result.destination.index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newItems = reorder(items, result.source.index, result.destination.index);
|
||||||
|
|
||||||
|
setItems(newItems as { id: string; content: React.ReactElement }[]);
|
||||||
|
},
|
||||||
|
[items]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ overflow: 'hidden', width: listWidth }}>
|
||||||
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
|
<Droppable
|
||||||
|
droppableId='droppable'
|
||||||
|
direction='vertical'
|
||||||
|
renderClone={(provided, snapshot, rubric) => {
|
||||||
|
let { style } = provided.draggableProps;
|
||||||
|
const transform = style?.transform;
|
||||||
|
|
||||||
|
if (snapshot.isDragging && transform) {
|
||||||
|
console.log(transform);
|
||||||
|
let [, _x, y] = transform.match(/translate\(([-\d]+)px, ([-\d]+)px\)/) || [];
|
||||||
|
|
||||||
|
style.transform = `translate3d(0px, ${y}px, 0px)`; // Apply constrained y value
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Item
|
||||||
|
provided={provided}
|
||||||
|
isDragging={snapshot.isDragging}
|
||||||
|
item={items[rubric.source.index]}
|
||||||
|
style={{
|
||||||
|
style,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{provided => (
|
||||||
|
<div
|
||||||
|
{...provided.droppableProps}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
style={{ width: `${listWidth}px` }}
|
||||||
|
className=''
|
||||||
|
>
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<Draggable key={item.id} draggableId={item.id} index={index}>
|
||||||
|
{draggableProvided => (
|
||||||
|
<div
|
||||||
|
ref={draggableProvided.innerRef}
|
||||||
|
{...draggableProvided.draggableProps}
|
||||||
|
{...draggableProvided.dragHandleProps}
|
||||||
|
style={{
|
||||||
|
...draggableProvided.draggableProps.style,
|
||||||
|
// if last item, don't add margin
|
||||||
|
marginBottom: index === items.length - 1 ? '0px' : `${gap}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.content}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
))}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default List;
|
||||||
@@ -13,6 +13,7 @@ export interface PopupCourseBlockProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
course: Course;
|
course: Course;
|
||||||
colors: CourseColors;
|
colors: CourseColors;
|
||||||
|
dragHandleProps?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,7 +21,7 @@ export interface PopupCourseBlockProps {
|
|||||||
*
|
*
|
||||||
* @param props PopupCourseBlockProps
|
* @param props PopupCourseBlockProps
|
||||||
*/
|
*/
|
||||||
export default function PopupCourseBlock({ className, course, colors }: PopupCourseBlockProps): JSX.Element {
|
export default function PopupCourseBlock({ className, course, colors, dragHandleProps }: PopupCourseBlockProps): JSX.Element {
|
||||||
// whiteText based on secondaryColor
|
// whiteText based on secondaryColor
|
||||||
const fontColor = pickFontColor(colors.primaryColor);
|
const fontColor = pickFontColor(colors.primaryColor);
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ export default function PopupCourseBlock({ className, course, colors }: PopupCou
|
|||||||
backgroundColor: colors.secondaryColor,
|
backgroundColor: colors.secondaryColor,
|
||||||
}}
|
}}
|
||||||
className='flex cursor-move items-center self-stretch rounded rounded-r-0'
|
className='flex cursor-move items-center self-stretch rounded rounded-r-0'
|
||||||
|
{...dragHandleProps}
|
||||||
>
|
>
|
||||||
<DragIndicatorIcon className='h-6 w-6 text-white' />
|
<DragIndicatorIcon className='h-6 w-6 text-white' />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Text from '../Text/Text';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for ScheduleTotalHoursAndCourses
|
||||||
|
*/
|
||||||
|
export interface ScheduleTotalHoursAndCoursesProps {
|
||||||
|
scheduleName: string;
|
||||||
|
totalHours: number;
|
||||||
|
totalCourses: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ScheduleTotalHoursAndCourses as per the Labels and Details Figma section
|
||||||
|
*
|
||||||
|
* @param props ScheduleTotalHoursAndCoursesProps
|
||||||
|
*/
|
||||||
|
export default function ScheduleTotalHoursAndCoursess({ scheduleName, totalHours, totalCourses }: ScheduleTotalHoursAndCoursesProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div className="flex min-w-64 content-center gap-2 flex-wrap uppercase items-baseline">
|
||||||
|
<Text
|
||||||
|
className="text-[#BF5700]"
|
||||||
|
variant='h1'
|
||||||
|
as='span'
|
||||||
|
>
|
||||||
|
{`${scheduleName}: `}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
variant='h3'
|
||||||
|
as='div'
|
||||||
|
className="text-[#1A2024] flex flex-row gap-2 items-center"
|
||||||
|
>
|
||||||
|
{`${totalHours} HOURS`}
|
||||||
|
<Text
|
||||||
|
variant='h4'
|
||||||
|
as='span'
|
||||||
|
className="text-[#333F48]"
|
||||||
|
>
|
||||||
|
{`${totalCourses} courses`}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ export default function CourseDescription({ course }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Card className={styles.container}>
|
<Card className={styles.container}>
|
||||||
{status === LoadStatus.ERROR && (
|
{status === LoadStatus.ERROR && (
|
||||||
<Text color='speedway_brick' size='medium' weight='bold' align='center'>
|
<Text color='speedway_brick' /* size='medium' weight='bold' align='center' */>
|
||||||
Please refresh the page and log back in using your UT EID and password
|
Please refresh the page and log back in using your UT EID and password
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -72,7 +72,7 @@ function DescriptionLine({ line }: LineProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text className={className} size='medium'>
|
<Text className={className} /* size='medium' */>
|
||||||
{line}
|
{line}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export default function CourseButtons({ course, activeSchedule }: Props) {
|
|||||||
className={styles.button}
|
className={styles.button}
|
||||||
title='Search for this professor on RateMyProfessor'
|
title='Search for this professor on RateMyProfessor'
|
||||||
>
|
>
|
||||||
<Text size='medium' weight='regular' color='white'>
|
<Text /* size='medium' weight='regular' */color='white'>
|
||||||
RateMyProf
|
RateMyProf
|
||||||
</Text>
|
</Text>
|
||||||
<Icon className={styles.icon} color='white' name='school' size='medium' />
|
<Icon className={styles.icon} color='white' name='school' size='medium' />
|
||||||
@@ -98,7 +98,7 @@ export default function CourseButtons({ course, activeSchedule }: Props) {
|
|||||||
className={styles.button}
|
className={styles.button}
|
||||||
title='Search for syllabi for this course'
|
title='Search for syllabi for this course'
|
||||||
>
|
>
|
||||||
<Text size='medium' weight='regular' color='white'>
|
<Text /* size='medium' weight='regular' */ color='white'>
|
||||||
Syllabi
|
Syllabi
|
||||||
</Text>
|
</Text>
|
||||||
<Icon className={styles.icon} color='white' name='grading' size='medium' />
|
<Icon className={styles.icon} color='white' name='grading' size='medium' />
|
||||||
@@ -109,7 +109,7 @@ export default function CourseButtons({ course, activeSchedule }: Props) {
|
|||||||
className={styles.button}
|
className={styles.button}
|
||||||
title='Search for textbooks for this course'
|
title='Search for textbooks for this course'
|
||||||
>
|
>
|
||||||
<Text size='medium' weight='regular' color='white'>
|
<Text /* size='medium' weight='regular' color='white' */>
|
||||||
Textbook
|
Textbook
|
||||||
</Text>
|
</Text>
|
||||||
<Icon className={styles.icon} color='white' name='collections_bookmark' size='medium' />
|
<Icon className={styles.icon} color='white' name='collections_bookmark' size='medium' />
|
||||||
@@ -121,7 +121,8 @@ export default function CourseButtons({ course, activeSchedule }: Props) {
|
|||||||
type={isCourseSaved ? 'danger' : 'success'}
|
type={isCourseSaved ? 'danger' : 'success'}
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
>
|
>
|
||||||
<Text size='medium' weight='regular' color='white'>
|
|
||||||
|
<Text /* size='medium' weight='regular' color='white' */ >
|
||||||
{isCourseSaved ? 'Remove' : 'Add'}
|
{isCourseSaved ? 'Remove' : 'Add'}
|
||||||
</Text>
|
</Text>
|
||||||
<Icon className={styles.icon} color='white' name={isCourseSaved ? 'remove' : 'add'} size='medium' />
|
<Icon className={styles.icon} color='white' name={isCourseSaved ? 'remove' : 'add'} size='medium' />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Card from '@views/components/common/Card/Card';
|
|||||||
import Icon from '@views/components/common/Icon/Icon';
|
import Icon from '@views/components/common/Icon/Icon';
|
||||||
import Link from '@views/components/common/Link/Link';
|
import Link from '@views/components/common/Link/Link';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
import CourseButtons from './CourseButtons/CourseButtons';
|
// import CourseButtons from './CourseButtons/CourseButtons';
|
||||||
import styles from './CourseHeader.module.scss';
|
import styles from './CourseHeader.module.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -26,23 +26,23 @@ export default function CourseHeader({ course, activeSchedule, onClose }: Props)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={styles.header}>
|
<Card className={styles.header}>
|
||||||
<Icon className={styles.close} size='large' name='close' onClick={onClose} />
|
<Icon className={styles.close} /* size='large' */ name='close' onClick={onClose} />
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
<Text className={styles.courseName} size='large' weight='bold' color='black'>
|
<Text className={styles.courseName} /* size='large' weight='bold' color='black' */>
|
||||||
{course.courseName} ({course.department} {course.number})
|
{course.courseName} ({course.department} {course.number})
|
||||||
</Text>
|
</Text>
|
||||||
<Link
|
<Link
|
||||||
url={course.url}
|
url={course.url}
|
||||||
className={styles.uniqueId}
|
className={styles.uniqueId}
|
||||||
size='medium'
|
/* size='medium'
|
||||||
weight='semi_bold'
|
weight='semi_bold' */
|
||||||
color='burnt_orange'
|
color='burnt_orange'
|
||||||
title='View course details on UT Course Schedule'
|
title='View course details on UT Course Schedule'
|
||||||
>
|
>
|
||||||
#{course.uniqueId}
|
#{course.uniqueId}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Text size='medium' className={styles.instructors}>
|
<Text /* size='medium' className={styles.instructors} */>
|
||||||
{`with ${!course.instructors.length ? 'TBA' : ''}`}
|
{`with ${!course.instructors.length ? 'TBA' : ''}`}
|
||||||
{course.instructors.map((instructor, index) => {
|
{course.instructors.map((instructor, index) => {
|
||||||
const name = instructor.toString({
|
const name = instructor.toString({
|
||||||
@@ -58,8 +58,8 @@ export default function CourseHeader({ course, activeSchedule, onClose }: Props)
|
|||||||
{numInstructors > 1 && index === course.instructors.length - 1 ? '& ' : ''}
|
{numInstructors > 1 && index === course.instructors.length - 1 ? '& ' : ''}
|
||||||
<Link
|
<Link
|
||||||
key={name}
|
key={name}
|
||||||
size='medium'
|
/* size='medium'
|
||||||
weight='normal'
|
weight='normal' */
|
||||||
url={url}
|
url={url}
|
||||||
title="View instructor's directory page"
|
title="View instructor's directory page"
|
||||||
>
|
>
|
||||||
@@ -71,15 +71,15 @@ export default function CourseHeader({ course, activeSchedule, onClose }: Props)
|
|||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
{course.schedule.meetings.map(meeting => (
|
{course.schedule.meetings.map(meeting => (
|
||||||
<Text size='medium' className={styles.meeting} key={meeting.startTime}>
|
<Text /* size='medium' */ className={styles.meeting} key={meeting.startTime}>
|
||||||
<Text span size='medium' weight='bold' color='black'>
|
<Text as='span' /* size='medium' weight='bold' */ color='black'>
|
||||||
{meeting.getDaysString({
|
{meeting.getDaysString({
|
||||||
format: 'long',
|
format: 'long',
|
||||||
separator: 'short',
|
separator: 'short',
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
{' at '}
|
{' at '}
|
||||||
<Text span size='medium'>
|
<Text as='span' /* size='medium' */>
|
||||||
{meeting.getTimeString({
|
{meeting.getTimeString({
|
||||||
separator: 'to',
|
separator: 'to',
|
||||||
capitalize: true,
|
capitalize: true,
|
||||||
@@ -87,8 +87,8 @@ export default function CourseHeader({ course, activeSchedule, onClose }: Props)
|
|||||||
</Text>
|
</Text>
|
||||||
{' in '}
|
{' in '}
|
||||||
<Link
|
<Link
|
||||||
size='medium'
|
/* size='medium'
|
||||||
weight='normal'
|
weight='normal' */
|
||||||
title='View building on UT Map'
|
title='View building on UT Map'
|
||||||
url={getBuildingUrl(meeting.location?.building)}
|
url={getBuildingUrl(meeting.location?.building)}
|
||||||
disabled={!meeting.location?.building}
|
disabled={!meeting.location?.building}
|
||||||
@@ -98,7 +98,7 @@ export default function CourseHeader({ course, activeSchedule, onClose }: Props)
|
|||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<CourseButtons course={course} activeSchedule={activeSchedule} />
|
{/* <CourseButtons course={course} activeSchedule={activeSchedule} /> */}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,18 +203,18 @@ export default function GradeDistribution({ course }: Props) {
|
|||||||
{status === DataStatus.LOADING && <Spinner />}
|
{status === DataStatus.LOADING && <Spinner />}
|
||||||
{status === DataStatus.ERROR && (
|
{status === DataStatus.ERROR && (
|
||||||
<Card className={styles.text}>
|
<Card className={styles.text}>
|
||||||
<Text color='speedway_brick' size='medium' weight='semi_bold'>
|
<Text color='speedway_brick' /* size='medium' weight='semi_bold' */>
|
||||||
There was an error fetching the grade distribution data
|
There was an error fetching the grade distribution data
|
||||||
</Text>
|
</Text>
|
||||||
<Icon color='speedway_brick' size='large' name='sentiment_dissatisfied' />
|
<Icon color='speedway_brick' /* size='large' */ name='sentiment_dissatisfied' />
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
{status === DataStatus.NOT_FOUND && (
|
{status === DataStatus.NOT_FOUND && (
|
||||||
<Card className={styles.text}>
|
<Card className={styles.text}>
|
||||||
<Text color='charcoal' size='medium' weight='semi_bold'>
|
<Text color='charcoal' /* size='medium' weight='semi_bold' */>
|
||||||
No grade distribution data was found for this course
|
No grade distribution data was found for this course
|
||||||
</Text>
|
</Text>
|
||||||
<Icon color='charcoal' size='x_large' name='search_off' />
|
<Icon color='charcoal' /* size='x_large' */ name='search_off' />
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default function TableRow({ row, isSelected, activeSchedule, onClick }: P
|
|||||||
element.classList.remove(styles.isConflict);
|
element.classList.remove(styles.isConflict);
|
||||||
setConflicts([]);
|
setConflicts([]);
|
||||||
};
|
};
|
||||||
}, [activeSchedule, course]);
|
}, [activeSchedule, course, element.classList]);
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return null;
|
return null;
|
||||||
@@ -91,7 +91,7 @@ export default function TableRow({ row, isSelected, activeSchedule, onClick }: P
|
|||||||
<div className={styles.conflictTooltip}>
|
<div className={styles.conflictTooltip}>
|
||||||
<div className={styles.body}>
|
<div className={styles.body}>
|
||||||
{conflicts.map(c => (
|
{conflicts.map(c => (
|
||||||
<Text size='small' key={c.uniqueId}>
|
<Text /* size='small' */ key={c.uniqueId}>
|
||||||
{c.department} {c.number} ({c.uniqueId})
|
{c.department} {c.number} ({c.uniqueId})
|
||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user