Compare commits

..

13 Commits

Author SHA1 Message Date
DhruvArora-03
70c75ff481 add button to the rows, use new ConflictsWithWarning component 2024-02-19 11:29:24 -06:00
DhruvArora-03
7f76af7ab3 fix ConflictsWithWarning 2024-02-19 11:28:33 -06:00
DhruvArora-03
a538f20aad Merge remote-tracking branch 'origin/hackathon' into feat-conflict-row 2024-02-19 11:11:37 -06:00
Lukas Zenick
0acd0b722c html2canvas -> htmlToImage
also fixed derick's bugs
2024-02-18 12:46:35 -06:00
ae32d0b645 feat: Derek/export png (#95)
* Attempting to use more lightweight version

* Did not work.

* This is not what I wanted

* The image saves correctly. Needs padding

* Padding !!

* Removed downloadjs

* Padding more
2024-02-17 19:09:41 -06:00
DhruvArora-03
f214ed7c01 Merge remote-tracking branch 'origin/hackathon' into feat-conflict-row 2024-02-17 18:21:39 -06:00
Lukas Zenick
e44b0c0e45 FIX README 2024-02-17 17:24:27 -06:00
Lukas Zenick
206c97c5b5 fixed README 2024-02-17 17:23:04 -06:00
ac71b838db feat: Derek vinson/calendar header (#94)
* CalendarHeader alignment progress

* Boom

* css

* Between

* Lol

* Gap fix

* whitespace-nowrap

* Gaps

* Finished alignment of CourseStatus and Buttons

* Colors

* ESLint auto format

* Color UT Registration Plus text

* Reverting vscode

---------

Co-authored-by: Vinson Zheng <vinsonzheng499@gmail.com>
2024-02-17 17:02:36 -06:00
Abhinav Chadaga
4f5753917b sort the flattened course schedule (#93)
first by dayIndex, then startIndex, then endIndex.
2024-02-17 16:57:51 -06:00
DhruvArora-03
478ab31706 Merge branch 'testing-useFlattenedCourseSchedule' into hackathon 2024-02-17 16:37:58 -06:00
Samuel Gunter
42420f5502 feat: bottom bar for the calendar page (#91) 2024-02-17 16:25:50 -06:00
Lukas Zenick
a03bcf17b8 broken file 2024-02-17 15:49:52 -06:00
14 changed files with 437 additions and 185 deletions

View File

@@ -14,5 +14,17 @@
1. Clone this repo
2. Run `pnpm install` to install and patch all the required dependencies
3. Run `pnpm run dev` to start the development server
4. Run `pnpm build` to build the extension for production
- If you want to run the development build:
- Run `pnpm run dev`
- If you want to build the extension for production:
- Run `pnpm build`
You may have to rename the `__uno.css.js` to `uno.css.js` in dist
Go to chrome://extensions, ensure you have "Developer Mode" enabled, and click 'Load unpacked'
Navigate to the 'dist' folder and click 'select' to import the extension

View File

@@ -24,7 +24,7 @@
"clsx": "^2.1.0",
"highcharts": "^11.3.0",
"highcharts-react-official": "^3.2.1",
"html2canvas": "^1.4.1",
"html-to-image": "^1.11.11",
"react": "^18.2.0",
"react-devtools-core": "^5.0.0",
"react-dom": "^18.2.0",

37
pnpm-lock.yaml generated
View File

@@ -34,9 +34,9 @@ dependencies:
highcharts-react-official:
specifier: ^3.2.1
version: 3.2.1(highcharts@11.3.0)(react@18.2.0)
html2canvas:
specifier: ^1.4.1
version: 1.4.1
html-to-image:
specifier: ^1.11.11
version: 1.11.11
react:
specifier: ^18.2.0
version: 18.2.0
@@ -5856,11 +5856,6 @@ packages:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
dev: false
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: true
@@ -6553,12 +6548,6 @@ packages:
postcss: 8.4.35
dev: true
/css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
dependencies:
utrie: 1.0.2
dev: false
/css-select@5.1.0:
resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
dependencies:
@@ -8663,12 +8652,8 @@ packages:
engines: {node: '>=8'}
dev: true
/html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
/html-to-image@1.11.11:
resolution: {integrity: sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==}
dev: false
/htmlparser2@8.0.2:
@@ -12430,12 +12415,6 @@ packages:
minimatch: 3.1.2
dev: true
/text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
dependencies:
utrie: 1.0.2
dev: false
/text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true
@@ -12978,12 +12957,6 @@ packages:
engines: {node: '>= 0.4.0'}
dev: true
/utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
dependencies:
base64-arraybuffer: 1.0.2
dev: false
/uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true

View File

@@ -0,0 +1,101 @@
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { Course, Status } from '@shared/types/Course';
import Instructor from '@shared/types/Instructor';
import { CalendarBottomBar } from '@views/components/common/CalendarBottomBar/CalendarBottomBar';
import { getCourseColors } from '../../shared/util/colors';
const exampleGovCourse: Course = new Course({
courseName: 'Nope',
creditHours: 3,
department: 'GOV',
description: ['nah', 'aint typing this', 'corndog'],
flags: ['no flag for you >:)'],
fullName: 'GOV 312L Something something',
instructionMode: 'Online',
instructors: [
new Instructor({
firstName: 'Bevo',
lastName: 'Barrymore',
fullName: 'Bevo Barrymore',
}),
],
isReserved: false,
number: '312L',
schedule: {
meetings: [],
},
semester: {
code: '12345',
season: 'Spring',
year: 2024,
},
status: Status.OPEN,
uniqueId: 12345,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
const examplePsyCourse: Course = new Course({
courseName: 'Nope Again',
creditHours: 3,
department: 'PSY',
description: ['nah', 'aint typing this', 'corndog'],
flags: ['no flag for you >:)'],
fullName: 'PSY 317L Yada yada',
instructionMode: 'Online',
instructors: [
new Instructor({
firstName: 'Bevo',
lastName: 'Etz',
fullName: 'Bevo Etz',
}),
],
isReserved: false,
number: '317L',
schedule: {
meetings: [],
},
semester: {
code: '12346',
season: 'Spring',
year: 2024,
},
status: Status.CLOSED,
uniqueId: 12346,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
const meta = {
title: 'Components/Common/CalendarBottomBar',
component: CalendarBottomBar,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof CalendarBottomBar>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
courses: [
{
colors: getCourseColors('pink', 200),
courseDeptAndInstr: `${exampleGovCourse.department} ${exampleGovCourse.number} ${exampleGovCourse.instructors[0].lastName}`,
status: exampleGovCourse.status,
},
{
colors: getCourseColors('slate', 500),
courseDeptAndInstr: `${examplePsyCourse.department} ${examplePsyCourse.number} ${examplePsyCourse.instructors[0].lastName}`,
status: examplePsyCourse.status,
},
],
},
render: props => (
<div className='outline-red outline w-292.5!'>
<CalendarBottomBar {...props} />
</div>
),
};

View File

@@ -3,7 +3,7 @@ import { Meta, StoryObj } from '@storybook/react';
import CalendarHeader from '@views/components/common/CalendarHeader/CalenderHeader';
const meta = {
title: 'Components/CalendarHeader',
title: 'Components/Common/CalendarHeader',
component: CalendarHeader,
parameters: {
layout: 'centered',

View File

@@ -1,5 +1,96 @@
import { Meta, StoryObj } from '@storybook/react';
import ConflictsWithWarning from '@views/components/common/ConflictsWithWarning/ConflictsWithWarning';
import { Course, Status } from 'src/shared/types/Course';
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
import Instructor from 'src/shared/types/Instructor';
export const ExampleCourse: 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,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
export const ExampleCourse2: Course = new Course({
courseName: 'PRINCIPLES OF COMPUTER SYSTEMS',
creditHours: 3,
department: 'C S',
description: [
'Restricted to computer science majors.',
'An introduction to computer systems software abstractions with an emphasis on the connection of these abstractions to underlying computer hardware. Key abstractions include threads, virtual memory, protection, and I/O. Requires writing of synchronized multithreaded programs and pieces of an operating system.',
'Computer Science 439 and 439H may not both be counted.',
'Prerequisite: Computer Science 429, or 429H with a grade of at least C-.',
'May be counted toward the Independent Inquiry flag requirement.',
],
flags: ['Independent Inquiry'],
fullName: 'C S 439 PRINCIPLES OF COMPUTER SYSTEMS',
instructionMode: 'In Person',
instructors: [
new Instructor({
firstName: 'Allison',
lastName: 'Norman',
fullName: 'Allison Norman',
}),
],
isReserved: false,
number: '439',
schedule: {
meetings: [
new CourseMeeting({
days: ['Tuesday', 'Thursday'],
startTime: 930,
endTime: 1050,
}),
new CourseMeeting({
days: ['Friday'],
startTime: 600,
endTime: 720,
}),
],
},
semester: {
code: '12345',
season: 'Spring',
year: 2024,
},
status: Status.WAITLISTED,
uniqueId: 67890,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
const meta = {
title: 'Components/Common/ConflictsWithWarning',
@@ -9,8 +100,10 @@ const meta = {
},
tags: ['autodocs'],
argTypes: {
ConflictingCourse: { control: 'string' },
SectionNumber: { control: 'string' },
conflicts: { control: 'object' },
},
args: {
conflicts: [ExampleCourse, ExampleCourse2],
},
} satisfies Meta<typeof ConflictsWithWarning>;
export default meta;
@@ -19,7 +112,6 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
ConflictingCourse: 'BVO 311C',
SectionNumber: '47280',
conflicts: [ExampleCourse, ExampleCourse2],
},
};

View File

@@ -0,0 +1,44 @@
import React from 'react';
import clsx from 'clsx';
import Text from '../Text/Text';
import CalendarCourseBlock, { CalendarCourseCellProps } from '../CalendarCourseCell/CalendarCourseCell';
import { Button } from '../Button/Button';
import ImageIcon from '~icons/material-symbols/image';
import CalendarMonthIcon from '~icons/material-symbols/calendar-month';
type CalendarBottomBarProps = {
courses: CalendarCourseCellProps[];
};
/**
*
*/
export const CalendarBottomBar = ({ courses }: CalendarBottomBarProps): JSX.Element => {
if (courses.length === -1) console.log('foo'); // dumb line to make eslint happy
return (
<div className='w-full flex py-1.25'>
<div className='flex flex-grow items-center gap-3.75 pl-7.5 pr-2.5'>
<Text variant='h4'>Async. and Other:</Text>
<div className='h-14 inline-flex gap-2.5'>
{courses.map(course => (
<CalendarCourseBlock
courseDeptAndInstr={course.courseDeptAndInstr}
status={course.status}
colors={course.colors}
key={course.courseDeptAndInstr}
className={clsx(course.className, 'w-35!')}
/>
))}
</div>
</div>
<div className='flex items-center pl-2.5 pr-7.5'>
<Button variant='single' color='ut-black' icon={CalendarMonthIcon}>
Save as .CAL
</Button>
<Button variant='single' color='ut-black' icon={ImageIcon}>
Save as .PNG
</Button>
</div>
</div>
);
};

View File

@@ -12,6 +12,7 @@ export interface CalendarCourseCellProps {
timeAndLocation?: string;
status: Status;
colors: CourseColors;
className?: string;
}
const CalendarCourseCell: React.FC<CalendarCourseCellProps> = ({
@@ -19,6 +20,7 @@ const CalendarCourseCell: React.FC<CalendarCourseCellProps> = ({
timeAndLocation,
status,
colors,
className,
}: CalendarCourseCellProps) => {
let rightIcon: React.ReactNode | null = null;
if (status === Status.WAITLISTED) {
@@ -34,7 +36,7 @@ const CalendarCourseCell: React.FC<CalendarCourseCellProps> = ({
return (
<div
className={`w-full flex justify-center rounded p-2 ${fontColor}`}
className={clsx('w-full flex justify-center rounded p-2', fontColor, className)}
style={{
backgroundColor: colors.primaryColor,
}}

View File

@@ -1,12 +1,12 @@
import React, { useRef } from 'react';
import html2canvas from 'html2canvas';
import * as htmlToImage from 'html-to-image';
import { DAY_MAP } from 'src/shared/types/CourseMeeting';
import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule';
import calIcon from 'src/assets/icons/cal.svg';
import pngIcon from 'src/assets/icons/png.svg';
import CalendarCell from '../CalendarGridCell/CalendarGridCell';
import CalendarCourseCell from '../CalendarCourseCell/CalendarCourseCell';
import styles from './CalendarGrid.module.scss';
import calIcon from 'src/assets/icons/cal.svg';
import pngIcon from 'src/assets/icons/png.svg';
const daysOfWeek = Object.keys(DAY_MAP).filter(key => !['S', 'SU'].includes(key));
const hoursOfDay = Array.from({ length: 14 }, (_, index) => index + 8);
@@ -38,15 +38,36 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Pr
const calendarRef = useRef(null); // Create a ref for the calendar grid
const saveAsPNG = () => {
if (calendarRef.current) {
html2canvas(calendarRef.current).then((canvas) => {
// Create an a element to trigger download
const a = document.createElement('a');
a.href = canvas.toDataURL('image/png');
a.download = 'calendar.png';
a.click();
htmlToImage
.toPng(calendarRef.current, {
backgroundColor: 'white',
style: {
background: 'white',
marginTop: '20px',
marginBottom: '20px',
marginRight: '20px',
marginLeft: '20px',
},
})
.then(dataUrl => {
let img = new Image();
img.src = dataUrl;
fetch(dataUrl)
.then(response => response.blob())
.then(blob => {
const href = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = href;
link.download = 'my-schedule.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch(error => console.error('Error downloading file:', error));
})
.catch(error => {
console.error('oops, something went wrong!', error);
});
}
};
return (
@@ -85,19 +106,22 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Pr
gridRow: `${block.calendarGridPoint.startIndex} / ${block.calendarGridPoint.endIndex}`,
}}
>
<CalendarCourseCell courseDeptAndInstr={block.componentProps.courseDeptAndInstr}
status={block.componentProps.status} colors={block.componentProps.colors}/>
<CalendarCourseCell
courseDeptAndInstr={block.componentProps.courseDeptAndInstr}
status={block.componentProps.status}
colors={block.componentProps.colors}
/>
</div>
))}
<div className={styles.buttonContainer}>
<div className={styles.divider}></div> {/* First divider */}
<div className={styles.divider} /> {/* First divider */}
<button className={styles.calendarButton}>
<img src={calIcon} className={styles.buttonIcon} alt="CAL" />
<img src={calIcon} className={styles.buttonIcon} alt='CAL' />
Save as .CAL
</button>
<div className={styles.divider}></div> {/* Second divider */}
<div className={styles.divider} /> {/* Second divider */}
<button onClick={saveAsPNG} className={styles.calendarButton}>
<img src={pngIcon} className={styles.buttonIcon} alt="PNG" />
<img src={pngIcon} className={styles.buttonIcon} alt='PNG' />
Save as .PNG
</button>
</div>

View File

@@ -12,40 +12,41 @@ import ScheduleTotalHoursAndCourses from '../ScheduleTotalHoursAndCourses/Schedu
import CourseStatus from '../CourseStatus/CourseStatus';
const CalendarHeader = () => (
<div
style={{
display: 'flex',
minWidth: '672px',
minHeight: '79px',
padding: '15px 0px',
justifyContent: 'space-between',
alignItems: 'center',
alignContent: 'center',
rowGap: '10px',
alignSelf: 'stretch',
flexWrap: 'wrap',
}}
>
<div className='min-h-79px min-w-672px flex px-0 py-15'>
<div className='flex flex-row gap-20'>
<div className='flex gap-10'>
<div className='flex gap-1'>
<Button variant='single' icon={MenuIcon} color='ut-gray' />
<div style={{ display: 'flex', alignItems: 'center' }}>
<div className='flex items-center'>
<LogoIcon style={{ marginRight: '5px' }} />
<Text>Your Logo Text</Text>
<div className='flex flex-col gap-1 whitespace-nowrap'>
<Text className='leading-trim text-cap font-roboto text-base text-ut-burntorange font-medium'>
UT Registration
</Text>
<Text className='leading-trim text-cap font-roboto text-base text-ut-orange font-medium'>
Plus
</Text>
</div>
</div>
</div>
<div className='flex flex-col'>
<ScheduleTotalHoursAndCourses scheduleName='SCHEDULE' totalHours={22} totalCourses={8} />
DATA UPDATED ON: 12:00 AM 02/01/2024
</div>
</div>
<div className='flex flex-row items-center space-x-8'>
<div className='flex flex-row space-x-4'>
<CourseStatus size='small' status={Status.WAITLISTED} />
<CourseStatus size='small' status={Status.CLOSED} />
<CourseStatus size='small' status={Status.CANCELLED} />
<div style={{ display: 'flex' }}>
<Button variant='outline' icon={UndoIcon} color='ut-black' />
<Button variant='outline' icon={RedoIcon} color='ut-black' />
</div>
<Button variant='outline' icon={SettingsIcon} color='ut-black' />
<div className='flex flex-row'>
<Button variant='single' icon={UndoIcon} color='ut-black' />
<Button variant='single' icon={RedoIcon} color='ut-black' />
<Button variant='single' icon={SettingsIcon} color='ut-black' />
</div>
</div>
</div>
<Divider type='solid' />
</div>
);

View File

@@ -1,12 +1,14 @@
import React from 'react';
import { Course } from 'src/shared/types/Course';
import clsx from 'clsx';
import Text from '../Text/Text';
/**
* Props for ConflictWithWarningProps
*/
export interface ConflictsWithWarningProps {
ConflictingCourse: string;
SectionNumber: string;
className?: string;
conflicts: Course[];
}
/**
@@ -15,29 +17,21 @@ export interface ConflictsWithWarningProps {
*
* @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} ) {
export default function ConflictsWithWarning({ className, conflicts }: ConflictsWithWarningProps): JSX.Element {
return (
<Text
variant='mini'
as='span'
className='text-white'
className={clsx(
className,
'min-w-21 w-21 flex flex-col items-start gap-2.5 rounded bg-[#AF2E2D] p-2.5 text-white'
)}
>
{children}
<div>Conflicts With:</div>
{conflicts.map(course => (
<div>
{`${course.department} ${course.number} (${course.uniqueId})`}
</div>
))}
</Text>
);
}

View File

@@ -21,7 +21,7 @@ export default function ScheduleTotalHoursAndCourses({
totalCourses,
}: ScheduleTotalHoursAndCoursesProps): JSX.Element {
return (
<div className='min-w-64 flex flex-wrap content-center items-baseline gap-2 uppercase'>
<div className='min-w-64 flex whitespace-nowrap content-center items-baseline gap-2 uppercase'>
<Text className='text-[#BF5700]' variant='h1' as='span'>
{`${scheduleName}: `}
</Text>

View File

@@ -3,9 +3,9 @@ import { UserSchedule } from '@shared/types/UserSchedule';
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import { Button } from '../../common/Button/Button';
import Icon from '../../common/Icon/Icon';
import Text from '../../common/Text/Text';
import styles from './TableRow.module.scss';
import ConflictsWithWarning from '../../common/ConflictsWithWarning/ConflictsWithWarning';
import AddIcon from '~icons/material-symbols/add-circle';
interface Props {
isSelected: boolean;
@@ -54,7 +54,7 @@ export default function TableRow({ row, isSelected, activeSchedule, onClick }: P
return () => {
element.classList.remove(styles.inActiveSchedule);
};
}, [activeSchedule, element.classList]);
}, [activeSchedule, course, element.classList]);
useEffect(() => {
if (!activeSchedule || !course) {
@@ -84,20 +84,14 @@ export default function TableRow({ row, isSelected, activeSchedule, onClick }: P
return ReactDOM.createPortal(
<>
<Button className={styles.rowButton} onClick={onClick} variant='secondary'>
<Icon name='bar_chart' color='white' size='medium' />
</Button>
{conflicts.length > 0 && (
<div className={styles.conflictTooltip}>
<div className={styles.body}>
{conflicts.map(c => (
<Text /* size='small' */ key={c.uniqueId}>
{c.department} {c.number} ({c.uniqueId})
</Text>
))}
</div>
</div>
)}
<Button
icon={AddIcon}
className={styles.rowButton}
color='ut-burntorange'
onClick={onClick}
variant='single'
/>
{conflicts.length > 0 && <ConflictsWithWarning className={styles.conflictTooltip} conflicts={conflicts} />}
</>,
container
);

View File

@@ -19,7 +19,7 @@ interface CalendarGridPoint {
* Return type of useFlattenedCourseSchedule
*/
export interface CalendarGridCourse {
calendarGridPoint?: CalendarGridPoint;
calendarGridPoint: CalendarGridPoint;
componentProps: CalendarCourseCellProps;
}
@@ -33,7 +33,8 @@ export function useFlattenedCourseSchedule(): CalendarGridCourse[] {
const [activeSchedule] = useSchedules();
const { courses } = activeSchedule;
return courses.flatMap(course => {
return courses
.flatMap(course => {
const {
status,
department,
@@ -46,6 +47,11 @@ export function useFlattenedCourseSchedule(): CalendarGridCourse[] {
// asynch, online course
return [
{
calendarGridPoint: {
dayIndex: 0,
startIndex: 0,
endIndex: 0,
},
componentProps: {
courseDeptAndInstr,
status,
@@ -83,5 +89,14 @@ export function useFlattenedCourseSchedule(): CalendarGridCourse[] {
},
}));
});
})
.sort((a: CalendarGridCourse, b: CalendarGridCourse) => {
if (a.calendarGridPoint.dayIndex !== b.calendarGridPoint.dayIndex) {
return a.calendarGridPoint.dayIndex - b.calendarGridPoint.dayIndex;
}
if (a.calendarGridPoint.startIndex !== b.calendarGridPoint.startIndex) {
return a.calendarGridPoint.startIndex - b.calendarGridPoint.startIndex;
}
return a.calendarGridPoint.endIndex - b.calendarGridPoint.endIndex;
});
}