added onclose to popup, coursepopup now displaying time info, renamed vars, added compiler for scss to typescript and tsconfig plugins

This commit is contained in:
Sriram Hariharan
2023-03-06 21:02:29 -06:00
parent 9b76f8afa0
commit 950c4a573a
9 changed files with 663 additions and 40 deletions

View File

@@ -17,11 +17,11 @@ export const DAY_MAP = {
export type Day = typeof DAY_MAP[keyof typeof DAY_MAP];
/** A physical room that a class is taught in */
export type Room = {
export type Location = {
/** The UT building code for where the class is taught */
building: string;
/** The room number for where the class is taught */
number: string;
room: string;
};
/**
@@ -35,7 +35,7 @@ export class CourseMeeting {
/** The end time of the course, in minutes since midnight */
endTime: number;
/** The location that the course is taught */
room?: Room;
location?: Location;
constructor(meeting: Serialized<CourseMeeting>) {
Object.assign(this, meeting);
@@ -124,5 +124,16 @@ type TimeStringOptions = {
type DaysStringOptions = {
/** The format of the days string, short = MWF, long = Monday, Wednesday, Friday */
format: 'short' | 'long';
/**
* The separator between the days
*
* 'none' = `MWF`
*
* 'conjunction' = `Monday, Wednesday, and Friday`
*
* 'disjunction' = `Monday, Wednesday, or Friday`
*
* 'narrow' = `Monday Wednesday Friday`
*/
separator: Intl.ListFormatStyle | 'none';
};

View File

@@ -18,10 +18,10 @@ export class CourseSchedule {
* Given a string representation of the meeting information for a class, parse it into a CourseMeeting object
* @param dayLine a string representation of the days of the week that the course is taught: MWF, TR, etc.
* @param timeLine a string representation of a time-range that the course is taught: 10:00 am - 11:00 am, 1:00 pm - 2:00 pm, etc.
* @param roomLine a string representation of the room that the course is taught in: JGB 2.302, etc.
* @param locLine a string representation of the location that the course is taught in: JGB 2.302, etc.
* @returns CourseMeeting object representing the meeting information
*/
static parse(dayLine: string, timeLine: string, roomLine: string): CourseMeeting {
static parse(dayLine: string, timeLine: string, locLine: string): CourseMeeting {
try {
let days: Day[] = dayLine
.split('')
@@ -51,19 +51,19 @@ export class CourseSchedule {
return Number(hour) * 60 + Number(minute);
});
const [building, number] = roomLine.split(' ');
const [building, room] = locLine.split(' ');
return new CourseMeeting({
days,
startTime,
endTime,
room: {
location: {
building,
number,
room,
},
});
} catch (e) {
throw new Error(`Failed to parse schedule: ${dayLine} ${timeLine} ${roomLine}`);
throw new Error(`Failed to parse schedule: ${dayLine} ${timeLine} ${locLine}`);
}
}
}

View File

@@ -1,5 +1,5 @@
import classNames from 'classnames';
import React, { PropsWithChildren } from 'react';
import React, { PropsWithChildren, useCallback } from 'react';
import styles from './Popup.module.scss';
interface Props {
@@ -8,21 +8,52 @@ interface Props {
className?: string;
/** Should it display a subtle dark overlay over the rest of the screen */
overlay?: boolean;
onClose?: () => void;
}
/**
*
* A reusable popup component that can be used to display content on the page
* @returns
*/
export default function Popup(props: PropsWithChildren<Props>) {
export default function Popup({ onClose, children, className, style, testId, overlay }: PropsWithChildren<Props>) {
const containerRef = React.useRef<HTMLDivElement>(null);
const bodyRef = React.useRef<HTMLDivElement>(null);
const handleClickOutside = useCallback(
(event: MouseEvent) => {
if (!bodyRef.current) return;
if (!bodyRef.current.contains(event.target as Node)) {
onClose?.();
}
},
[onClose, bodyRef]
);
React.useEffect(() => {
const shadowRoot = document.getElementById('ut-registration-plus-container')?.shadowRoot;
if (!shadowRoot) return;
shadowRoot.addEventListener('mousedown', handleClickOutside);
return () => {
shadowRoot.removeEventListener('mousedown', handleClickOutside);
};
}, [handleClickOutside]);
return (
<div
style={props.style}
style={style}
ref={containerRef}
className={classNames(styles.container, {
[styles.overlay]: props.overlay,
[styles.overlay]: overlay,
})}
data-testid={props.testId}
data-testid={testId}
>
<div className={classNames(styles.body, props.className)}>{props.children}</div>
<div ref={bodyRef} className={classNames(styles.body, className)}>
{children}
</div>
</div>
);
}

View File

@@ -18,7 +18,7 @@ interface Props {
export default function CoursePopup({ course, onClose }: Props) {
console.log(course);
return (
<Popup className={styles.popup} overlay>
<Popup className={styles.popup} overlay onClose={onClose}>
<Icon className={styles.close} size='large' name='close' onClick={onClose} />
<Card className={styles.body}>
<Text className={styles.title} size='large' weight='bold' color='black'>
@@ -40,6 +40,28 @@ export default function CoursePopup({ course, onClose }: Props) {
format: 'first_last',
})}
</Text>
{course.schedule.meetings.map(meeting => (
<Text size='medium'>
<Text span size='medium' weight='bold' color='black'>
{meeting.getDaysString({
format: 'long',
separator: 'short',
})}
</Text>
{' at '}
<Text span size='medium'>
{meeting.getTimeString({
separator: 'to',
capitalize: true,
})}
</Text>
{' in '}
<Link span size='medium' weight='bold' color='bluebonnet'>
{meeting.location?.building}
</Link>
</Text>
))}
</Card>
</Popup>
);

View File

@@ -14,7 +14,7 @@ enum TableDataSelector {
STATUS = 'td[data-th="Status"]',
SCHEDULE_DAYS = 'td[data-th="Days"]>span',
SCHEDULE_HOURS = 'td[data-th="Hour"]>span',
SCHEDULE_ROOM = 'td[data-th="Room"]>span',
SCHEDULE_LOCATION = 'td[data-th="Room"]>span',
FLAGS = 'td[data-th="Flags"] ul li',
}
@@ -283,7 +283,7 @@ export class CourseCatalogScraper {
getSchedule(row: HTMLTableRowElement): CourseSchedule {
const dayLines = row.querySelectorAll(TableDataSelector.SCHEDULE_DAYS);
const hourLines = row.querySelectorAll(TableDataSelector.SCHEDULE_HOURS);
const roomLines = row.querySelectorAll(TableDataSelector.SCHEDULE_ROOM);
const locLines = row.querySelectorAll(TableDataSelector.SCHEDULE_LOCATION);
if (dayLines.length !== hourLines.length) {
throw new Error('Schedule data is malformed');
@@ -296,7 +296,7 @@ export class CourseCatalogScraper {
CourseSchedule.parse(
dayLines[i].textContent || '',
hourLines[i].textContent || '',
roomLines[i].textContent || ''
locLines[i].textContent || ''
)
);
}