feat: show warning for courses of different semesters (#570)

* chore: removed extra space at calendar footer

* chore: fixed eslint issues

* chore: changed return type to react node

* chore: displaycourses true fixes and checks fixed

* chore: prettier fix

* feat: not working same semester course issue

* feat: modifying components to use the new hook

* feat: small fixes

* fix: remove comments and spaces

* fix: dialog error solved

* fix: add to new schedule

* fix: prettier

* fix: delete unnecessary custom hook and p[rettier

* fix: checks all passing

* fix: added requested changes

* fix: added new conditions

* fix: description fixed

* style: fix Roboto Flex not being used as font in dialog

* fix: made requested changes

---------

Co-authored-by: doprz <52579214+doprz@users.noreply.github.com>
Co-authored-by: Razboy20 <razboy20@gmail.com>
Co-authored-by: Samuel Gunter <29130894+Samathingamajig@users.noreply.github.com>
This commit is contained in:
ishita778
2025-08-27 13:41:36 -05:00
committed by GitHub
parent 7bea23a655
commit 2e7dac1e3e
3 changed files with 105 additions and 24 deletions

View File

@@ -15,6 +15,8 @@ import type { SiteSupportType } from '@views/lib/getSiteSupport';
import { populateSearchInputs } from '@views/lib/populateSearchInputs'; import { populateSearchInputs } from '@views/lib/populateSearchInputs';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import DialogProvider from './common/DialogProvider/DialogProvider';
interface Props { interface Props {
support: Extract<SiteSupportType, 'COURSE_CATALOG_DETAILS' | 'COURSE_CATALOG_LIST'>; support: Extract<SiteSupportType, 'COURSE_CATALOG_DETAILS' | 'COURSE_CATALOG_LIST'>;
} }
@@ -82,6 +84,7 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element | nul
return ( return (
<ExtensionRoot> <ExtensionRoot>
<DialogProvider>
<NewSearchLink /> <NewSearchLink />
<RecruitmentBanner /> <RecruitmentBanner />
<TableHead>Plus</TableHead> <TableHead>Plus</TableHead>
@@ -104,6 +107,7 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element | nul
afterLeave={() => setSelectedCourse(null)} afterLeave={() => setSelectedCourse(null)}
/> />
{enableScrollToLoad && <AutoLoad addRows={addRows} />} {enableScrollToLoad && <AutoLoad addRows={addRows} />}
</DialogProvider>
</ExtensionRoot> </ExtensionRoot>
); );
} }

View File

@@ -15,6 +15,11 @@
@apply font-sans; @apply font-sans;
color: #303030; color: #303030;
// fix font-family on injected pages
* {
@apply font-sans;
}
[data-rfd-drag-handle-context-id=':r1:'] { [data-rfd-drag-handle-context-id=':r1:'] {
cursor: move; cursor: move;
} }

View File

@@ -1,3 +1,5 @@
import createSchedule from '@pages/background/lib/createSchedule';
import switchSchedule from '@pages/background/lib/switchSchedule';
import { import {
ArrowUpRight, ArrowUpRight,
CalendarDots, CalendarDots,
@@ -14,8 +16,10 @@ import { background } from '@shared/messages';
import type { Course } from '@shared/types/Course'; import type { Course } from '@shared/types/Course';
import type Instructor from '@shared/types/Instructor'; import type Instructor from '@shared/types/Instructor';
import type { UserSchedule } from '@shared/types/UserSchedule'; import type { UserSchedule } from '@shared/types/UserSchedule';
import { englishStringifyList } from '@shared/util/string';
import { Button } from '@views/components/common/Button'; import { Button } from '@views/components/common/Button';
import { Chip, coreMap, flagMap } from '@views/components/common/Chip'; import { Chip, coreMap, flagMap } from '@views/components/common/Chip';
import { usePrompt } from '@views/components/common/DialogProvider/DialogProvider';
import Divider from '@views/components/common/Divider'; import Divider from '@views/components/common/Divider';
import Link from '@views/components/common/Link'; import Link from '@views/components/common/Link';
import Text from '@views/components/common/Text/Text'; import Text from '@views/components/common/Text/Text';
@@ -60,7 +64,7 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
const [isCopied, setIsCopied] = useState<boolean>(false); const [isCopied, setIsCopied] = useState<boolean>(false);
const lastCopyTime = useRef<number>(0); const lastCopyTime = useRef<number>(0);
const showDialog = usePrompt();
const getInstructorFullName = (instructor: Instructor) => instructor.toString({ format: 'first_last' }); const getInstructorFullName = (instructor: Instructor) => instructor.toString({ format: 'first_last' });
const handleCopy = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { const handleCopy = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
@@ -112,10 +116,78 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
} }
}; };
const handleAddToNewSchedule = async (close: () => void) => {
const newScheduleId = await createSchedule(`${course.semester.season} ${course.semester.year}`);
switchSchedule(newScheduleId);
addCourse({ course, scheduleId: newScheduleId });
close();
};
const handleAddOrRemoveCourse = async () => { const handleAddOrRemoveCourse = async () => {
const uniqueSemesterCodes = [
...new Set(
activeSchedule.courses
.map(course => course.semester.code)
.filter((code): code is string => code !== undefined)
),
];
uniqueSemesterCodes.sort();
const codeToReadableMap: Record<string, string> = {};
activeSchedule.courses.forEach(course => {
const { code } = course.semester;
if (code) {
const readable = `${course.semester.season} ${course.semester.year}`;
codeToReadableMap[code] = readable;
}
});
const sortedSemesters = uniqueSemesterCodes
.map(code => codeToReadableMap[code])
.filter((value): value is string => value !== undefined);
const activeSemesters = englishStringifyList(sortedSemesters);
if (!activeSchedule) return; if (!activeSchedule) return;
if (!courseAdded) { if (!courseAdded) {
const currentSemesterCode = course.semester.code;
// Show warning if this course is for a different semester than the selected schedule
if (
activeSchedule.courses.length > 0 &&
activeSchedule.courses.every(otherCourse => otherCourse.semester.code !== currentSemesterCode)
) {
const dialogButtons = (close: () => void) => (
<>
<Button variant='minimal' color='ut-black' onClick={close}>
Cancel
</Button>
<Button
variant='filled'
color='ut-burntorange'
onClick={() => {
handleAddToNewSchedule(close);
}}
>
Start a new schedule
</Button>
</>
);
showDialog({
title: 'This course section is from a different semester!',
description: (
<>
The section you&apos;re adding is for{' '}
<span className='text-ut-burntorange'>
{course.semester.season} {course.semester.year}
</span>
, but your current schedule contains sections in{' '}
<span className='text-ut-burntorange'>{activeSemesters}</span>. Mixing semesters in one
schedule may cause confusion.
</>
),
buttons: dialogButtons,
});
} else {
addCourse({ course, scheduleId: activeSchedule.id }); addCourse({ course, scheduleId: activeSchedule.id });
}
} else { } else {
removeCourse({ course, scheduleId: activeSchedule.id }); removeCourse({ course, scheduleId: activeSchedule.id });
} }