From 4c1d99d3f26c254c2b18e09bde471a8f1218fa87 Mon Sep 17 00:00:00 2001 From: Ali Vayani Date: Wed, 20 Nov 2024 13:55:32 -0600 Subject: [PATCH] feat: ts error --- src/assets/insideJokes.tsx | 1 + .../background/handler/courseStatusChange.ts | 67 +++++++++++ .../background/handler/userScheduleHandler.ts | 108 ++++++++++++++++++ src/shared/storage/OptionsStore.ts | 2 +- .../calendar/CalendarCourseCell.tsx | 7 +- .../components/calendar/CalenderHeader.tsx | 12 +- src/views/components/settings/Settings.tsx | 25 +++- src/views/lib/CourseCatalogScraper.ts | 2 +- 8 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 src/pages/background/handler/courseStatusChange.ts diff --git a/src/assets/insideJokes.tsx b/src/assets/insideJokes.tsx index 5b707470a..c5a75c808 100644 --- a/src/assets/insideJokes.tsx +++ b/src/assets/insideJokes.tsx @@ -115,6 +115,7 @@ const splashText: string[] = [ `It's ${new Date().toLocaleString('en-US', { month: 'long', day: 'numeric' })} and OU still sucks`, 'As seen on TV! ', "Should you major in Compsci? well, here's a better question. do you wanna have a bad time?", + "https://i.redd.it/4y3fc2bzva1e1.gif", ]; export default splashText; diff --git a/src/pages/background/handler/courseStatusChange.ts b/src/pages/background/handler/courseStatusChange.ts new file mode 100644 index 000000000..34e04a801 --- /dev/null +++ b/src/pages/background/handler/courseStatusChange.ts @@ -0,0 +1,67 @@ +import { UserScheduleStore } from "src/shared/storage/UserScheduleStore"; +import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper'; +import { Course, StatusType } from '@shared/types/Course'; +import { resolve } from "path"; + + +export default async function checkCourseStatusChanges() { + const activeIndex = await UserScheduleStore.get('activeIndex'); + const schedules = await UserScheduleStore.get('schedules'); + const currentSchedule = schedules[activeIndex]?.courses; + + + if (!currentSchedule || currentSchedule.length === 0) return; + + let newStatus = currentSchedule[0]?.status; + for (const course of currentSchedule) { + // Get the latest status from the course catalog or API + // You might need to import your CourseCatalogScraper here + const uniqueId = course.uniqueId; + // gets the new status + // console.log(uniqueId, 'UNIQUE ID'); + try { + const response = await fetch(`https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/${uniqueId}/`); + const html = await response.text(); + + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const scraper = new CourseCatalogScraper('COURSE_CATALOG_LIST', doc); + const rows = doc.querySelectorAll('tr'); + rows.forEach(row => { + try { + const id = scraper.getUniqueId(row); + if (id && id === uniqueId) { + const scrapedStatus = scraper.getStatus(row)[0]; + newStatus = scrapedStatus as StatusType; + } + console.log('Unique ID:', uniqueId); + } catch (error) { + console.error(error); + } + }); + + } + catch (error) { + console.error(`Failed to check status for course ${uniqueId}:`, error); + } + + if (newStatus && newStatus !== currentSchedule[0]?.status) { + const updatedCourses = currentSchedule.map(c => { + if (c.uniqueId === uniqueId) { + return { ...c, status: newStatus } as Course; + } + return c as Course; + }); + console.log(updatedCourses); + const updatedSchedules = [...schedules]; + console.log("UPDATED", updatedSchedules) + { + ...updatedSchedules[activeIndex], + courses: updatedCourses, + }; + + + await UserScheduleStore.set('schedules', updatedSchedules); + } + } +} \ No newline at end of file diff --git a/src/pages/background/handler/userScheduleHandler.ts b/src/pages/background/handler/userScheduleHandler.ts index 5c53c4281..0ae1475e5 100644 --- a/src/pages/background/handler/userScheduleHandler.ts +++ b/src/pages/background/handler/userScheduleHandler.ts @@ -8,29 +8,137 @@ import switchSchedule from '@pages/background/lib/switchSchedule'; import type { UserScheduleMessages } from '@shared/messages/UserScheduleMessages'; import { Course } from '@shared/types/Course'; import type { MessageHandler } from 'chrome-extension-toolkit'; +import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore'; +import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper'; +import { StatusType } from '@shared/types/Course'; +import { Serialized } from 'chrome-extension-toolkit'; + const userScheduleHandler: MessageHandler = { addCourse({ data, sendResponse }) { + checkCourseStatusChanges(); addCourse(data.scheduleId, new Course(data.course)).then(sendResponse); }, removeCourse({ data, sendResponse }) { + console.log("TEST") + checkCourseStatusChanges(); removeCourse(data.scheduleId, new Course(data.course)).then(sendResponse); }, clearCourses({ data, sendResponse }) { + checkCourseStatusChanges(); clearCourses(data.scheduleId).then(sendResponse); }, switchSchedule({ data, sendResponse }) { + checkCourseStatusChanges(); switchSchedule(data.scheduleId).then(sendResponse); }, createSchedule({ data, sendResponse }) { + checkCourseStatusChanges(); createSchedule(data.scheduleName).then(sendResponse); }, deleteSchedule({ data, sendResponse }) { + checkCourseStatusChanges(); deleteSchedule(data.scheduleId).then(sendResponse); }, renameSchedule({ data, sendResponse }) { + checkCourseStatusChanges(); renameSchedule(data.scheduleId, data.newName).then(sendResponse); }, }; +async function checkCourseStatusChanges() { + const activeIndex = await UserScheduleStore.get('activeIndex'); + const schedules = await UserScheduleStore.get('schedules'); + const currentSchedule = schedules[activeIndex]?.courses; + + + if (!currentSchedule || currentSchedule.length === 0) return; + + let newStatus = currentSchedule[0]?.status; + for (const course of currentSchedule) { + // Get the latest status from the course catalog or API + // You might need to import your CourseCatalogScraper here + const uniqueId = course.uniqueId; + // gets the new status + try { + const response = await fetch(`https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/${uniqueId}/`); + const html = await response.text(); + + // Parse the HTML using DOMParser + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + + const scraper = new CourseCatalogScraper('COURSE_CATALOG_LIST', doc); + const rows = Array.from(doc.querySelectorAll('tr')); + + for (const row of rows) { + const id = scraper.getUniqueId(row); + if (id === uniqueId) { + const scrapedStatus = scraper.getStatus(row)[0]; + newStatus = scrapedStatus as StatusType; + } + } + + } + catch (error) { + console.error(`Failed to check status for course ${uniqueId}:`, error); + } + + if (newStatus && newStatus !== currentSchedule[0]?.status) { + const updatedCourses = currentSchedule.map(c => { + if (c.uniqueId === uniqueId) { + return { ...c, status: newStatus } as Serialized; // Explicitly cast here + } + return c as Serialized; // Ensure the rest match expected type + }); + + const updatedSchedules = [...schedules]; + // updatedSchedules[activeIndex] = { + // ...updatedSchedules[activeIndex], + // courses: updatedCourses, + // }; + + + await UserScheduleStore.set('schedules', updatedSchedules); + } + + + // if (updatedCourseInfo && updatedCourseInfo.status !== course.status) { + // // Status has changed + // const updatedCourses = currentSchedule.map(c => + // c.uniqueNumber === course.uniqueNumber + // ? { ...c, status: updatedCourseInfo.status } + // : c + // ); + + // // Update the store with new course status + // const updatedSchedules = [...schedules]; + // updatedSchedules[activeIndex] = { + // ...schedules[activeIndex], + // courses: updatedCourses + // }; + + // await UserScheduleStore.set('schedules', updatedSchedules); + + // // Notify user of status change + // chrome.notifications.create({ + // type: 'basic', + // title: 'Course Status Change', + // message: `${course.courseName} status changed from ${course.status} to ${updatedCourseInfo.status}`, + // iconUrl: '/icons/icon48.png' // Make sure this path is correct + //}); + } +} + +// You'll need to implement this function to fetch the latest course status +async function fetchLatestCourseStatus(course: Course) { + // Implement the logic to fetch the latest course status + // This might involve using your CourseCatalogScraper or making an API call + // Return the updated course information + return null; // Replace with actual implementation +} + + + + export default userScheduleHandler; diff --git a/src/shared/storage/OptionsStore.ts b/src/shared/storage/OptionsStore.ts index 50e614eba..38a9eedcc 100644 --- a/src/shared/storage/OptionsStore.ts +++ b/src/shared/storage/OptionsStore.ts @@ -21,7 +21,7 @@ export interface IOptionsStore { } export const OptionsStore = createSyncStore({ - enableCourseStatusChips: false, + enableCourseStatusChips: true, // true for dev purposes, will make switch in settings - ali v enableTimeAndLocationInPopup: false, enableHighlightConflicts: true, enableScrollToLoad: true, diff --git a/src/views/components/calendar/CalendarCourseCell.tsx b/src/views/components/calendar/CalendarCourseCell.tsx index 1f7cdd9ac..33aa52b63 100644 --- a/src/views/components/calendar/CalendarCourseCell.tsx +++ b/src/views/components/calendar/CalendarCourseCell.tsx @@ -47,7 +47,10 @@ export default function CalendarCourseCell({ useEffect(() => { initSettings().then(({ enableCourseStatusChips }) => setEnableCourseStatusChips(enableCourseStatusChips)); - + console.log("useEffect"); + initSettings().then((res) => { + console.log(res); + }) const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => { setEnableCourseStatusChips(newValue); // console.log('enableCourseStatusChips', newValue); @@ -59,6 +62,8 @@ export default function CalendarCourseCell({ }, []); let rightIcon: React.ReactNode | null = null; + console.log("enabledCourseStatusChips", enableCourseStatusChips); + console.log("status", status) if (enableCourseStatusChips) { if (status === Status.WAITLISTED) { rightIcon = ; diff --git a/src/views/components/calendar/CalenderHeader.tsx b/src/views/components/calendar/CalenderHeader.tsx index c16f71f22..da05a22e9 100644 --- a/src/views/components/calendar/CalenderHeader.tsx +++ b/src/views/components/calendar/CalenderHeader.tsx @@ -7,12 +7,15 @@ import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotal import Text from '@views/components/common/Text/Text'; import useSchedules from '@views/hooks/useSchedules'; import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString'; + import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript'; + import React, { useEffect, useState } from 'react'; import MenuIcon from '~icons/material-symbols/menu'; -// import RefreshIcon from '~icons/material-symbols/refresh'; +import RefreshIcon from '~icons/material-symbols/refresh'; import SettingsIcon from '~icons/material-symbols/settings'; +import checkCourseStatusChanges from 'src/pages/background/handler/courseStatusChange'; /** * Opens the options page in a new tab. @@ -81,9 +84,12 @@ export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps) LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)} - {/* */} + )} diff --git a/src/views/components/settings/Settings.tsx b/src/views/components/settings/Settings.tsx index 8ccec15b5..296185514 100644 --- a/src/views/components/settings/Settings.tsx +++ b/src/views/components/settings/Settings.tsx @@ -79,7 +79,7 @@ const useDevMode = (targetCount: number): [boolean, () => void] => { * @returns The Settings component. */ export default function Settings(): JSX.Element { - const [_enableCourseStatusChips, setEnableCourseStatusChips] = useState(false); + const [enableCourseStatusChips, setEnableCourseStatusChips] = useState(false); const [_showTimeLocation, setShowTimeLocation] = useState(false); const [highlightConflicts, setHighlightConflicts] = useState(false); const [loadAllCourses, setLoadAllCourses] = useState(false); @@ -99,6 +99,9 @@ export default function Settings(): JSX.Element { const showDialog = usePrompt(); const handleChangelogOnClick = useChangelog(); + useEffect(() => { + console.log(enableCourseStatusChips, "settings"); + },[enableCourseStatusChips]) useEffect(() => { const fetchGitHubStats = async () => { try { @@ -138,7 +141,7 @@ export default function Settings(): JSX.Element { // Listen for changes in the settings const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => { setEnableCourseStatusChips(newValue); - // console.log('enableCourseStatusChips', newValue); + console.log('enableCourseStatusChips', newValue); }); const l2 = OptionsStore.listen('enableTimeAndLocationInPopup', async ({ newValue }) => { @@ -400,6 +403,24 @@ export default function Settings(): JSX.Element { +
+
+

Show Course Status

+

+ Shows an indicator for waitlisted, cancelled, and closed courses. +

+
+ { + setEnableCourseStatusChips(!enableCourseStatusChips); + OptionsStore.set('enableCourseStatusChips', !enableCourseStatusChips); + }} + /> +
+ + +
diff --git a/src/views/lib/CourseCatalogScraper.ts b/src/views/lib/CourseCatalogScraper.ts index 23f4d4963..702f7ac83 100644 --- a/src/views/lib/CourseCatalogScraper.ts +++ b/src/views/lib/CourseCatalogScraper.ts @@ -211,7 +211,7 @@ export class CourseCatalogScraper { */ getInstructionMode(row: HTMLTableRowElement): InstructionMode { const text = (row.querySelector(TableDataSelector.INSTRUCTION_MODE)?.textContent || '').toLowerCase(); - + if (text.includes('internet')) { return 'Online'; }