From aa2649e6a7ac1af9c382d4b13e5cdec90769b118 Mon Sep 17 00:00:00 2001 From: Meredith Zhang Date: Thu, 15 May 2025 16:04:05 +1000 Subject: [PATCH 1/5] Resolve merge conflict :) Display latest selected term, otherwise latest term with timetable data to user --- client/src/App.tsx | 3 +- client/src/components/controls/TermSelect.tsx | 11 ++- client/src/constants/timetable.ts | 62 ++++++++--------- client/src/utils/getTermsWithClassData.ts | 67 +++++++++++++++++++ 4 files changed, 108 insertions(+), 35 deletions(-) create mode 100644 client/src/utils/getTermsWithClassData.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index c84dcd7e4..3c3b89e48 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -217,8 +217,9 @@ const App: React.FC = () => { * Retrieves the list of all courses from the scraper backend */ const fetchCoursesList = async () => { - const { courses } = await getCoursesList(term.substring(0, 2)); + const { courses } = await getCoursesList("T1"); setCoursesList(courses); + }; if (year !== invalidYearFormat) fetchReliably(fetchCoursesList); diff --git a/client/src/components/controls/TermSelect.tsx b/client/src/components/controls/TermSelect.tsx index f0e8a97dd..e48090e13 100644 --- a/client/src/components/controls/TermSelect.tsx +++ b/client/src/components/controls/TermSelect.tsx @@ -86,16 +86,25 @@ const TermSelect: React.FC = () => { const termValue = e.target.value; const termInfo = termValue.split(', '); + let termPrefix = ''; if (termInfo[0].includes('Summer')) { termPrefix = 'U1'; } else { termPrefix = 'T' + termInfo[0].split(' ')[1]; } - + + const newYear = termInfo[1]; const termName = (termPrefix + newYear) as Term; // To get a string like T12024 + + localStorage.setItem( + 'currSelectedTerm', + JSON.stringify({ + term: termName, + }), + ); setTerm(termName); setYear(newYear); setTermName(convertToTermName(termName!)); diff --git a/client/src/constants/timetable.ts b/client/src/constants/timetable.ts index 72009fee1..dbebeb4fd 100644 --- a/client/src/constants/timetable.ts +++ b/client/src/constants/timetable.ts @@ -4,6 +4,7 @@ import { isWithinInterval, parse } from 'date-fns'; import { client } from '../api/config'; import NetworkError from '../interfaces/NetworkError'; import { Term, TermDataList } from '../interfaces/Periods'; +import { getTermsWithClassData } from '../utils/getTermsWithClassData'; export function sortTerms(terms: Term[]): Term[] { const termOrder: Record = { U1: 0, T1: 1, T2: 2, T3: 3 }; @@ -66,29 +67,37 @@ const constructTermDetailsMap = async (): Promise> => * @param todaysDate The current date to retrieve the latest term from * @returns the term data for the latest term */ -const get_current_term = async ( +const getTermToDisplay = async ( termInfoMap: Map, - todaysDate: Date = new Date(), ): Promise => { - const keys_term = sortTerms(Array.from(termInfoMap.keys())); - for (let currTermIndex = 0; currTermIndex < keys_term.length; currTermIndex++) { - if (!keys_term[currTermIndex]) continue; - const currTermVal = termInfoMap.get(keys_term[currTermIndex])!; - if (isWithinInterval(todaysDate, { start: currTermVal.startDate, end: currTermVal.endDate })) { - return keys_term[currTermIndex]; - } - if ( - currTermIndex > 0 && - isWithinInterval(todaysDate, { - start: termInfoMap.get(keys_term[currTermIndex - 1])!.endDate, - end: currTermVal.startDate, - }) - ) { - return keys_term[currTermIndex]; + // Prioritise last selected term + if (localStorage.getItem('currSelectedTerm')) { + const storedTerm = localStorage.getItem('currSelectedTerm'); + + if (storedTerm) { + const currSelectedTerm = JSON.parse(storedTerm) + return currSelectedTerm.term } + + } + + // (TODO) If no selected term, display the latest term the user has timetable data for + + // If no class data, display the latest term with timetable data + + + const keys_term = sortTerms(Array.from(termInfoMap.keys())); + // Need to combine this with getavailabletermdetiails... + const termsWithData = await getTermsWithClassData(keys_term) + + if (termsWithData && termsWithData.length > 0) { + return termsWithData[termsWithData.length - 1] } - return keys_term[1]; // eg. default to Term 1 next year. + + // Default to T1 in case there are 0 terms with course data + // Index 1 skips over summer term + return keys_term[1] }; export const convertToTermName = (termId: string) => { @@ -115,30 +124,17 @@ export const getAvailableTermDetails = async () => { firstDayOfTerm: '', }; - if (localStorage.getItem('termData')) { - termData = JSON.parse(localStorage.getItem('termData')!); - } let firstDayOfTerm = termData.firstDayOfTerm || ``; try { const termMapInfo = await constructTermDetailsMap(); - const currTermId = await get_current_term(termMapInfo); + // this is what sets the term to display + const currTermId = await getTermToDisplay(termMapInfo); firstDayOfTerm = termMapInfo.get(currTermId)?.startDate?.toLocaleDateString().split('/').reverse().join('-') || 'default-date'; const termsSortedList: TermDataList = sortTerms(Array.from(termMapInfo.keys())); - // Store the term details in local storage. - localStorage.setItem( - 'termData', - JSON.stringify({ - year: currTermId?.substring(2), - term: currTermId, - termName: currTermId ? convertToTermName(currTermId.substring(0, 2)) : '', - firstDayOfTerm: firstDayOfTerm, - termsData: termsSortedList, - }), - ); return { year: currTermId?.substring(2), diff --git a/client/src/utils/getTermsWithClassData.ts b/client/src/utils/getTermsWithClassData.ts new file mode 100644 index 000000000..aa8be3b21 --- /dev/null +++ b/client/src/utils/getTermsWithClassData.ts @@ -0,0 +1,67 @@ +import { gql } from '@apollo/client'; +import { client } from '../api/config'; + +const GET_CLASSES_WITH_TIMES = gql` + query MyQuery($term: String!) { + times_aggregate( + where: { + classe: { + course: { course_code: { _eq: "COMP1511" } }, + term: { _eq: $term } + } + } + limit: 1 + ) { + aggregate { + count + } + } + } +`; + + +/** + * Checks if a given term has any classes with timetable data + * + * @param term The term we want to check (e.g. "T1") + * @returns True if the term has class data, else False + * @example + * bool exists = await existsClassDataInTerm("T1") + */ + +const existsClassDataInTerm = async (term: string): Promise => { + try { + const { data } = await client.query({ + query: GET_CLASSES_WITH_TIMES, + variables: { term }, + }); + + return data?.times_aggregate?.aggregate?.count > 0; + } catch (error) { + console.error('Error fetching class data:', error); + return false; + } +}; + + +/** + * Returns a list of terms with class data, with the terms ordered by + * their appearance in the year - [U1, T1, T2, T3] + * + * @returns List of terms that have class data + * @example + * const termsWithData = await getTermsWithClassData(); + */ +export const getTermsWithClassData = async (terms: string[]): Promise => { + + const termsWithData: string[] = []; + + for (const term of terms) { + const hasClassData = await existsClassDataInTerm(term.substring(0, 2)); + if (hasClassData) { + termsWithData.push(term); + } + } + + return termsWithData; +}; \ No newline at end of file From d52a7b7807cb41e7bda8f50b2907af578fd214da Mon Sep 17 00:00:00 2001 From: Meredith Zhang Date: Thu, 5 Jun 2025 18:19:16 +1000 Subject: [PATCH 2/5] Grey out classes with no data --- client/src/components/controls/TermSelect.tsx | 33 +++++++++++++++---- client/src/utils/getTermsWithClassData.ts | 2 +- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/client/src/components/controls/TermSelect.tsx b/client/src/components/controls/TermSelect.tsx index e48090e13..69e2d864c 100644 --- a/client/src/components/controls/TermSelect.tsx +++ b/client/src/components/controls/TermSelect.tsx @@ -8,13 +8,14 @@ import { useTheme, } from '@mui/material'; import { styled } from '@mui/system'; -import React, { useContext, useState } from 'react'; +import { useContext, useState, useEffect } from 'react'; import { ThemeType } from '../../constants/theme'; import { convertToTermName } from '../../constants/timetable'; import { AppContext } from '../../context/AppContext'; import { CourseContext } from '../../context/CourseContext'; import { Term } from '../../interfaces/Periods'; +import { getTermsWithClassData } from '../../utils/getTermsWithClassData'; const StyledInputLabel = styled(InputLabel)(({ theme }) => ({ color: theme.palette.primary.main, @@ -105,6 +106,7 @@ const TermSelect: React.FC = () => { term: termName, }), ); + setTerm(termName); setYear(newYear); setTermName(convertToTermName(termName!)); @@ -123,6 +125,23 @@ const TermSelect: React.FC = () => { const handleOpen = () => { setOpen(true); }; + + // Set up a list of terms with class data + const [termsWithClassData, setTermsWithClassData] = useState([]); + + useEffect(() => { + const loadTermsWithClassData = async () => { + const result = await getTermsWithClassData(termsData); + setTermsWithClassData(result); + }; + + if (termsData.length > 0) { + loadTermsWithClassData(); + } + }, [termsData]); // refresh list of terms if the year changes + + + const keysTerm = ["U1", "T1", "T2", "T3"].map(t => `${t}${year}`); return ( Select term @@ -138,11 +157,13 @@ const TermSelect: React.FC = () => { onChange={selectTerm} > {Array.from(termDataStrList).map((term, index) => { - return ( - - {term} - - ); + const isAvailable = termsWithClassData.includes(keysTerm[index]) + return ( + + {term} + + ) + })} diff --git a/client/src/utils/getTermsWithClassData.ts b/client/src/utils/getTermsWithClassData.ts index aa8be3b21..8aeaf34cc 100644 --- a/client/src/utils/getTermsWithClassData.ts +++ b/client/src/utils/getTermsWithClassData.ts @@ -6,7 +6,7 @@ const GET_CLASSES_WITH_TIMES = gql` times_aggregate( where: { classe: { - course: { course_code: { _eq: "COMP1511" } }, + course: { course_code: { _eq: "COMP2521" } }, term: { _eq: $term } } } From 557dcf2471e8288accf66fda779d4315cb40a202 Mon Sep 17 00:00:00 2001 From: Meredith Zhang Date: Thu, 26 Jun 2025 15:48:43 +1000 Subject: [PATCH 3/5] resolve merge conflictTerm timetable data desynced, weird deletion of timetable data happening if user switches tersm after adding a class --- client/src/App.tsx | 5 ++- client/src/constants/timetable.ts | 64 +++++++++++++++++++------------ 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 3c3b89e48..6dc828702 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -183,7 +183,8 @@ const App: React.FC = () => { * Retrieves term data from the scraper backend */ const fetchTermData = async () => { - const { term, termName, year, firstDayOfTerm, termsData } = await getAvailableTermDetails(); + const { term, termName, year, firstDayOfTerm, termsData } = await getAvailableTermDetails(); // term is + // switched after this. this be rpoblem. setTerm(term); setTermName(termName); setYear(year); @@ -217,7 +218,7 @@ const App: React.FC = () => { * Retrieves the list of all courses from the scraper backend */ const fetchCoursesList = async () => { - const { courses } = await getCoursesList("T1"); + const { courses } = await getCoursesList(term.substring(0, 2)); setCoursesList(courses); }; diff --git a/client/src/constants/timetable.ts b/client/src/constants/timetable.ts index dbebeb4fd..c28a72ceb 100644 --- a/client/src/constants/timetable.ts +++ b/client/src/constants/timetable.ts @@ -4,7 +4,6 @@ import { isWithinInterval, parse } from 'date-fns'; import { client } from '../api/config'; import NetworkError from '../interfaces/NetworkError'; import { Term, TermDataList } from '../interfaces/Periods'; -import { getTermsWithClassData } from '../utils/getTermsWithClassData'; export function sortTerms(terms: Term[]): Term[] { const termOrder: Record = { U1: 0, T1: 1, T2: 2, T3: 3 }; @@ -67,37 +66,41 @@ const constructTermDetailsMap = async (): Promise> => * @param todaysDate The current date to retrieve the latest term from * @returns the term data for the latest term */ -const getTermToDisplay = async ( +const get_current_term = async ( termInfoMap: Map, + todaysDate: Date = new Date(), ): Promise => { - - // Prioritise last selected term + const keys_term = sortTerms(Array.from(termInfoMap.keys())); if (localStorage.getItem('currSelectedTerm')) { const storedTerm = localStorage.getItem('currSelectedTerm'); if (storedTerm) { const currSelectedTerm = JSON.parse(storedTerm) - return currSelectedTerm.term + console.log("localstorage" + currSelectedTerm.term) + return(currSelectedTerm.term) } + } + for (let currTermIndex = 0; currTermIndex < keys_term.length; currTermIndex++) { + if (!keys_term[currTermIndex]) continue; + const currTermVal = termInfoMap.get(keys_term[currTermIndex])!; - } - - // (TODO) If no selected term, display the latest term the user has timetable data for - - // If no class data, display the latest term with timetable data - - - const keys_term = sortTerms(Array.from(termInfoMap.keys())); - // Need to combine this with getavailabletermdetiails... - const termsWithData = await getTermsWithClassData(keys_term) + if (isWithinInterval(todaysDate, { start: currTermVal.startDate, end: currTermVal.endDate })) { + console.log("thist ype of data" + keys_term[currTermIndex]) + return keys_term[currTermIndex]; + } - if (termsWithData && termsWithData.length > 0) { - return termsWithData[termsWithData.length - 1] + if ( + currTermIndex > 0 && + isWithinInterval(todaysDate, { + start: termInfoMap.get(keys_term[currTermIndex - 1])!.endDate, + end: currTermVal.startDate, + }) + ) { + console.log("thist ype of data ", keys_term[currTermIndex]) + return keys_term[currTermIndex]; + } } - - // Default to T1 in case there are 0 terms with course data - // Index 1 skips over summer term - return keys_term[1] + return keys_term[1]; // eg. default to Term 1 next year. }; export const convertToTermName = (termId: string) => { @@ -124,17 +127,30 @@ export const getAvailableTermDetails = async () => { firstDayOfTerm: '', }; + if (localStorage.getItem('termData')) { + termData = JSON.parse(localStorage.getItem('termData')!); + } let firstDayOfTerm = termData.firstDayOfTerm || ``; try { const termMapInfo = await constructTermDetailsMap(); - // this is what sets the term to display - const currTermId = await getTermToDisplay(termMapInfo); + const currTermId = await get_current_term(termMapInfo); firstDayOfTerm = termMapInfo.get(currTermId)?.startDate?.toLocaleDateString().split('/').reverse().join('-') || 'default-date'; const termsSortedList: TermDataList = sortTerms(Array.from(termMapInfo.keys())); + // Store the term details in local storage. + localStorage.setItem( + 'termData', + JSON.stringify({ + year: currTermId?.substring(2), + term: currTermId, + termName: currTermId ? convertToTermName(currTermId.substring(0, 2)) : '', + firstDayOfTerm: firstDayOfTerm, + termsData: termsSortedList, + }), + ); return { year: currTermId?.substring(2), @@ -233,4 +249,4 @@ export const weekdaysShort = ['Mo', 'Tu', 'We', 'Th', 'Fr']; export const unknownErrorMessage = 'An unknown error has occurred, please hard refresh the page'; -export const invalidYearFormat = '0000'; +export const invalidYearFormat = '0000'; \ No newline at end of file From 4919f599abfb199191996705c9f32c8a8d18f84f Mon Sep 17 00:00:00 2001 From: Meredith Zhang Date: Thu, 17 Jul 2025 17:59:52 +1000 Subject: [PATCH 4/5] Cleaned up comments and console.logs to put this issue on hiatus - current bug with switching ts around --- client/src/App.tsx | 3 +-- client/src/constants/timetable.ts | 37 ++++++++++++++++++++++++++++-- client/src/context/UserContext.tsx | 1 + 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 6dc828702..5e1632e8c 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -183,8 +183,7 @@ const App: React.FC = () => { * Retrieves term data from the scraper backend */ const fetchTermData = async () => { - const { term, termName, year, firstDayOfTerm, termsData } = await getAvailableTermDetails(); // term is - // switched after this. this be rpoblem. + const { term, termName, year, firstDayOfTerm, termsData } = await getAvailableTermDetails(); setTerm(term); setTermName(termName); setYear(year); diff --git a/client/src/constants/timetable.ts b/client/src/constants/timetable.ts index c28a72ceb..b0c167b87 100644 --- a/client/src/constants/timetable.ts +++ b/client/src/constants/timetable.ts @@ -1,6 +1,6 @@ import { gql } from '@apollo/client'; import { isWithinInterval, parse } from 'date-fns'; - +import { getTermsWithClassData } from '../utils/getTermsWithClassData'; import { client } from '../api/config'; import NetworkError from '../interfaces/NetworkError'; import { Term, TermDataList } from '../interfaces/Periods'; @@ -113,6 +113,38 @@ export const convertToTermName = (termId: string) => { } }; +const getTermToDisplay = async ( + termInfoMap: Map, + +): Promise => { + + // (CURRENTLY BUGGED) Prioritise last selected term + if (localStorage.getItem('currSelectedTerm')) { + const storedTerm = localStorage.getItem('currSelectedTerm'); + + if (storedTerm) { + const currSelectedTerm = JSON.parse(storedTerm) + return currSelectedTerm.term + + } + + } + + // (TODO) If no selected term, display the latest term the user has timetable data for + + // If no class data, display the latest term with timetable data + + const keys_term = sortTerms(Array.from(termInfoMap.keys())); + const termsWithData = await getTermsWithClassData(keys_term) + + if (termsWithData && termsWithData.length > 0) { + return termsWithData[termsWithData.length - 1] + } + + // Default to T1 in case there are 0 terms with course data + return keys_term[1] +}; + /** * @returns The details of the latest term there is data for */ @@ -135,7 +167,8 @@ export const getAvailableTermDetails = async () => { try { const termMapInfo = await constructTermDetailsMap(); - const currTermId = await get_current_term(termMapInfo); + const currTermId = await getTermToDisplay(termMapInfo); + console.log(" curr term id" + currTermId); firstDayOfTerm = termMapInfo.get(currTermId)?.startDate?.toLocaleDateString().split('/').reverse().join('-') || 'default-date'; diff --git a/client/src/context/UserContext.tsx b/client/src/context/UserContext.tsx index 7710bc0de..58b41917a 100644 --- a/client/src/context/UserContext.tsx +++ b/client/src/context/UserContext.tsx @@ -152,6 +152,7 @@ const UserContextProvider = ({ children }: UserContextProviderProps) => { throw new NetworkError("Couldn't get response for user information!"); } } catch (error) { + setUser(undefinedUser); console.log(error); } }; From 7da51734642765cc2950869bf459474b3ea7ffbb Mon Sep 17 00:00:00 2001 From: Meredith Zhang Date: Thu, 17 Jul 2025 18:46:37 +1000 Subject: [PATCH 5/5] Removed one last console.log --- client/src/constants/timetable.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/constants/timetable.ts b/client/src/constants/timetable.ts index b0c167b87..7443621cc 100644 --- a/client/src/constants/timetable.ts +++ b/client/src/constants/timetable.ts @@ -168,7 +168,6 @@ export const getAvailableTermDetails = async () => { try { const termMapInfo = await constructTermDetailsMap(); const currTermId = await getTermToDisplay(termMapInfo); - console.log(" curr term id" + currTermId); firstDayOfTerm = termMapInfo.get(currTermId)?.startDate?.toLocaleDateString().split('/').reverse().join('-') || 'default-date';