Compare commits

..

1 Commits

Author SHA1 Message Date
Vinson Zheng
0eefaa1860 Halfway Tailwind conversion 2024-02-17 19:37:45 -06:00
8 changed files with 100 additions and 192 deletions

View File

@@ -24,7 +24,7 @@
"clsx": "^2.1.0",
"highcharts": "^11.3.0",
"highcharts-react-official": "^3.2.1",
"html-to-image": "^1.11.11",
"html2canvas": "^1.4.1",
"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)
html-to-image:
specifier: ^1.11.11
version: 1.11.11
html2canvas:
specifier: ^1.4.1
version: 1.4.1
react:
specifier: ^18.2.0
version: 18.2.0
@@ -5856,6 +5856,11 @@ 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
@@ -6548,6 +6553,12 @@ 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:
@@ -8652,8 +8663,12 @@ packages:
engines: {node: '>=8'}
dev: true
/html-to-image@1.11.11:
resolution: {integrity: sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==}
/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
dev: false
/htmlparser2@8.0.2:
@@ -12415,6 +12430,12 @@ 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
@@ -12957,6 +12978,12 @@ 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

@@ -1,96 +1,5 @@
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',
@@ -100,10 +9,8 @@ const meta = {
},
tags: ['autodocs'],
argTypes: {
conflicts: { control: 'object' },
},
args: {
conflicts: [ExampleCourse, ExampleCourse2],
ConflictingCourse: { control: 'string' },
SectionNumber: { control: 'string' },
},
} satisfies Meta<typeof ConflictsWithWarning>;
export default meta;
@@ -112,6 +19,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
conflicts: [ExampleCourse, ExampleCourse2],
ConflictingCourse: 'BVO 311C',
SectionNumber: '47280',
},
};

View File

@@ -1,5 +1,5 @@
import React, { useRef } from 'react';
import * as htmlToImage from 'html-to-image';
import html2canvas from 'html2canvas';
import { DAY_MAP } from 'src/shared/types/CourseMeeting';
import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule';
import calIcon from 'src/assets/icons/cal.svg';
@@ -15,9 +15,11 @@ for (let i = 0; i < 13; i++) {
const row = [];
let hour = hoursOfDay[i];
row.push(
<div key={hour} className={styles.timeBlock}>
<div className={styles.timeLabelContainer}>
<p>{(hour % 12 === 0 ? 12 : hour % 12) + (hour < 12 ? ' AM' : ' PM')}</p>
<div key={hour} className='flex flex-col items-end'>
<div className='flex flex-1 flex-col items-end gap-17'>
<p className='font-roboto-flex mb-0 mr-10 mt-[-10px] h-6.6 self-stretch text-left text-gray-900 font-normal'>
{(hour % 12 === 0 ? 12 : hour % 12) + (hour < 12 ? ' AM' : ' PM')}
</p>
</div>
</div>
);
@@ -38,53 +40,21 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Pr
const calendarRef = useRef(null); // Create a ref for the calendar grid
const saveAsPNG = () => {
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);
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();
});
}
};
return (
<div className={styles.calendar}>
<div className={styles.dayLabelContainer} />
{/* Displaying the rest of the calendar */}
<div ref={calendarRef} className={styles.timeAndGrid}>
{/* <div className={styles.timeColumn}>
<div className={styles.timeBlock}></div>
{hoursOfDay.map((hour) => (
<div key={hour} className={styles.timeBlock}>
<div className={styles.timeLabelContainer}>
<p>{hour % 12 === 0 ? 12 : hour % 12} {hour < 12 ? 'AM' : 'PM'}</p>
</div>
</div>
))}
</div> */}
<div className='relative flex flex-col gap-10'>
<div className='h-13 min-h-13 min-w-40 flex flex-1 flex-row items-center justify-center gap-10 pb-15' />
<div ref={calendarRef} className='flex'>
<div className={styles.calendarGrid}>
{/* Displaying day labels */}
<div className={styles.timeBlock} />

View File

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

View File

@@ -87,9 +87,7 @@ export default function CourseButtons({ course, activeSchedule }: Props) {
className={styles.button}
title='Search for this professor on RateMyProfessor'
>
<Text /* size='medium' weight='regular' */color='white'>
RateMyProf
</Text>
<Text /* size='medium' weight='regular' */ color='white'>RateMyProf</Text>
<Icon className={styles.icon} color='white' name='school' size='medium' />
</Button>
<Button
@@ -98,9 +96,7 @@ export default function CourseButtons({ course, activeSchedule }: Props) {
className={styles.button}
title='Search for syllabi for this course'
>
<Text /* size='medium' weight='regular' */ color='white'>
Syllabi
</Text>
<Text /* size='medium' weight='regular' */ color='white'>Syllabi</Text>
<Icon className={styles.icon} color='white' name='grading' size='medium' />
</Button>
<Button
@@ -109,9 +105,7 @@ export default function CourseButtons({ course, activeSchedule }: Props) {
className={styles.button}
title='Search for textbooks for this course'
>
<Text /* size='medium' weight='regular' color='white' */>
Textbook
</Text>
<Text /* size='medium' weight='regular' color='white' */>Textbook</Text>
<Icon className={styles.icon} color='white' name='collections_bookmark' size='medium' />
</Button>
<Button
@@ -121,10 +115,7 @@ export default function CourseButtons({ course, activeSchedule }: Props) {
variant={isCourseSaved ? 'danger' : 'success'}
className={styles.button}
>
<Text /* size='medium' weight='regular' color='white' */ >
{isCourseSaved ? 'Remove' : 'Add'}
</Text>
<Text /* size='medium' weight='regular' color='white' */>{isCourseSaved ? 'Remove' : 'Add'}</Text>
<Icon className={styles.icon} color='white' name={isCourseSaved ? 'remove' : 'add'} size='medium' />
</Button>
</Card>

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, course, element.classList]);
}, [activeSchedule, element.classList]);
useEffect(() => {
if (!activeSchedule || !course) {
@@ -84,14 +84,20 @@ export default function TableRow({ row, isSelected, activeSchedule, onClick }: P
return ReactDOM.createPortal(
<>
<Button
icon={AddIcon}
className={styles.rowButton}
color='ut-burntorange'
onClick={onClick}
variant='single'
/>
{conflicts.length > 0 && <ConflictsWithWarning className={styles.conflictTooltip} conflicts={conflicts} />}
<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>
)}
</>,
container
);