183 lines
7.9 KiB
TypeScript
183 lines
7.9 KiB
TypeScript
import { background } from '@shared/messages';
|
|
import type { Course } from '@shared/types/Course';
|
|
import { Status } from '@shared/types/Course';
|
|
import type Instructor from '@shared/types/Instructor';
|
|
import type { UserSchedule } from '@shared/types/UserSchedule';
|
|
import { Button } from '@views/components/common/Button/Button';
|
|
import { Chip, flagMap } from '@views/components/common/Chip/Chip';
|
|
import Divider from '@views/components/common/Divider/Divider';
|
|
import Text from '@views/components/common/Text/Text';
|
|
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
|
import React, { useState } from 'react';
|
|
|
|
import Add from '~icons/material-symbols/add';
|
|
import CalendarMonth from '~icons/material-symbols/calendar-month';
|
|
import CloseIcon from '~icons/material-symbols/close';
|
|
import Copy from '~icons/material-symbols/content-copy';
|
|
import Description from '~icons/material-symbols/description';
|
|
import Mood from '~icons/material-symbols/mood';
|
|
import Remove from '~icons/material-symbols/remove';
|
|
import Reviews from '~icons/material-symbols/reviews';
|
|
|
|
const { openNewTab, addCourse, removeCourse, openCESPage } = background;
|
|
|
|
interface HeadingAndActionProps {
|
|
/* The course to display */
|
|
course: Course;
|
|
/* The active schedule */
|
|
activeSchedule?: UserSchedule;
|
|
/* The function to call when the popup should be closed */
|
|
onClose: () => void;
|
|
}
|
|
|
|
/**
|
|
* Opens the calendar in a new tab.
|
|
* @returns {Promise<void>} A promise that resolves when the tab is opened.
|
|
*/
|
|
export const handleOpenCalendar = async (): Promise<void> => {
|
|
const url = chrome.runtime.getURL('calendar.html');
|
|
openNewTab({ url });
|
|
};
|
|
|
|
const capitalizeString = (str: string) => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
|
|
/**
|
|
* Renders the heading component for the CoursePopup component.
|
|
*
|
|
* @param {HeadingAndActionProps} props - The component props.
|
|
* @param {Course} props.course - The course object containing course details.
|
|
* @param {Schedule} props.activeSchedule - The active schedule object.
|
|
* @param {Function} props.onClose - The function to close the popup.
|
|
* @returns {JSX.Element} The rendered component.
|
|
*/
|
|
const HeadingAndActions: React.FC<HeadingAndActionProps> = ({
|
|
course,
|
|
activeSchedule,
|
|
onClose,
|
|
}: HeadingAndActionProps): JSX.Element => {
|
|
const { courseName, department, number: courseNumber, uniqueId, instructors, flags, schedule } = course;
|
|
const [courseAdded, setCourseAdded] = useState<boolean>(
|
|
activeSchedule !== undefined ? activeSchedule.courses.some(course => course.uniqueId === uniqueId) : false
|
|
);
|
|
|
|
const getInstructorFullName = (instructor: Instructor) => {
|
|
const { firstName, lastName } = instructor;
|
|
if (firstName === '') return capitalizeString(lastName);
|
|
return `${capitalizeString(firstName)} ${capitalizeString(lastName)}`;
|
|
};
|
|
|
|
const instructorString = instructors.map(getInstructorFullName).join(', ');
|
|
|
|
const handleCopy = () => {
|
|
navigator.clipboard.writeText(uniqueId.toString());
|
|
};
|
|
|
|
const handleOpenRateMyProf = async () => {
|
|
const openTabs = instructors.map(instructor => {
|
|
const instructorSearchTerm = getInstructorFullName(instructor);
|
|
instructorSearchTerm.replace(' ', '+');
|
|
const url = `https://www.ratemyprofessors.com/search/professors/1255?q=${instructorSearchTerm}`;
|
|
return openNewTab({ url });
|
|
});
|
|
await Promise.all(openTabs);
|
|
};
|
|
|
|
const handleOpenCES = async () => {
|
|
const openTabs = instructors.map(instructor => {
|
|
let { firstName, lastName } = instructor;
|
|
firstName = capitalizeString(firstName);
|
|
lastName = capitalizeString(lastName);
|
|
return openCESPage({ instructorFirstName: firstName, instructorLastName: lastName });
|
|
});
|
|
await Promise.all(openTabs);
|
|
};
|
|
|
|
const handleOpenPastSyllabi = async () => {
|
|
// not specific to professor
|
|
const url = `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${courseNumber}&course_title=${courseName}&unique=&instructor_first=&instructor_last=&course_type=In+Residence&search=Search`;
|
|
openNewTab({ url });
|
|
};
|
|
|
|
const handleAddOrRemoveCourse = async () => {
|
|
if (!activeSchedule) return;
|
|
if (!courseAdded) {
|
|
addCourse({ course, scheduleName: activeSchedule.name });
|
|
} else {
|
|
removeCourse({ course, scheduleName: activeSchedule.name });
|
|
}
|
|
setCourseAdded(prev => !prev);
|
|
};
|
|
|
|
return (
|
|
<div className='w-full pb-3 pt-6'>
|
|
<div className='flex flex-col'>
|
|
<div className='flex items-center gap-1'>
|
|
<Text variant='h1' className='truncate'>
|
|
{courseName}
|
|
</Text>
|
|
<Text variant='h1' className='flex-1 whitespace-nowrap'>
|
|
{' '}
|
|
({department} {courseNumber})
|
|
</Text>
|
|
<Button color='ut-burntorange' variant='single' icon={Copy} onClick={handleCopy}>
|
|
{uniqueId}
|
|
</Button>
|
|
<button className='bg-transparent p-0 btn' onClick={onClose}>
|
|
<CloseIcon className='h-7 w-7' />
|
|
</button>
|
|
</div>
|
|
<div className='flex gap-2 flex-content-center'>
|
|
{instructorString.length > 0 && (
|
|
<Text variant='h4' className='inline-flex items-center justify-center'>
|
|
with {instructorString}
|
|
</Text>
|
|
)}
|
|
<div className='flex-content-centr flex gap-1'>
|
|
{flags.map(flag => (
|
|
<Chip key={flagMap[flag]} label={flagMap[flag]} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className='flex flex-col'>
|
|
{schedule.meetings.map(meeting => {
|
|
const daysString = meeting.getDaysString({ format: 'long', separator: 'long' });
|
|
const timeString = meeting.getTimeString({ separator: ' to ', capitalize: false });
|
|
const locationString = meeting.location ? ` in ${meeting.location.building}` : '';
|
|
return (
|
|
<Text key={daysString + timeString + locationString} variant='h4'>
|
|
{daysString} {timeString}
|
|
{locationString}
|
|
</Text>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
<div className='my-3 flex flex-wrap items-center gap-[15px]'>
|
|
<Button variant='filled' color='ut-burntorange' icon={CalendarMonth} onClick={handleOpenCalendar} />
|
|
<Divider size='1.75rem' orientation='vertical' />
|
|
<Button variant='outline' color='ut-blue' icon={Reviews} onClick={handleOpenRateMyProf}>
|
|
RateMyProf
|
|
</Button>
|
|
<Button variant='outline' color='ut-teal' icon={Mood} onClick={handleOpenCES}>
|
|
CES
|
|
</Button>
|
|
<Button variant='outline' color='ut-orange' icon={Description} onClick={handleOpenPastSyllabi}>
|
|
Past Syllabi
|
|
</Button>
|
|
<Button
|
|
variant='filled'
|
|
disabled={course.status !== Status.OPEN}
|
|
color={!courseAdded ? 'ut-green' : 'ut-red'}
|
|
icon={!courseAdded ? Add : Remove}
|
|
onClick={handleAddOrRemoveCourse}
|
|
>
|
|
{!courseAdded ? 'Add Course' : 'Remove Course'}
|
|
</Button>
|
|
</div>
|
|
<Divider orientation='horizontal' size='100%' />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default HeadingAndActions;
|