Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ 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();
setTerm(term);
setTermName(termName);
setYear(year);
Expand Down Expand Up @@ -219,6 +219,7 @@ const App: React.FC = () => {
const fetchCoursesList = async () => {
const { courses } = await getCoursesList(term.substring(0, 2));
setCoursesList(courses);

};

if (year !== invalidYearFormat) fetchReliably(fetchCoursesList);
Expand Down
44 changes: 37 additions & 7 deletions client/src/components/controls/TermSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -86,16 +87,26 @@ const TermSelect: React.FC<TermSelectProps> = () => {
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!));
Expand All @@ -114,6 +125,23 @@ const TermSelect: React.FC<TermSelectProps> = () => {
const handleOpen = () => {
setOpen(true);
};

// Set up a list of terms with class data
const [termsWithClassData, setTermsWithClassData] = useState<string[]>([]);

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 (
<FormControl >
<StyledInputLabel id="select-term-label">Select term</StyledInputLabel>
Expand All @@ -129,11 +157,13 @@ const TermSelect: React.FC<TermSelectProps> = () => {
onChange={selectTerm}
>
{Array.from(termDataStrList).map((term, index) => {
return (
<MenuItem key={index} value={term}>
{term}
</MenuItem>
);
const isAvailable = termsWithClassData.includes(keysTerm[index])
return (
<MenuItem key={index} value={term} disabled={!isAvailable}>
{term}
</MenuItem>
)

})}
</CustomStyledSelect>
</FormControl>
Expand Down
50 changes: 47 additions & 3 deletions client/src/constants/timetable.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -71,10 +71,21 @@ const get_current_term = async (
todaysDate: Date = new Date(),
): Promise<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)
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])!;

if (isWithinInterval(todaysDate, { start: currTermVal.startDate, end: currTermVal.endDate })) {
console.log("thist ype of data" + keys_term[currTermIndex])
return keys_term[currTermIndex];
}

Expand All @@ -85,6 +96,7 @@ const get_current_term = async (
end: currTermVal.startDate,
})
) {
console.log("thist ype of data ", keys_term[currTermIndex])
return keys_term[currTermIndex];
}
}
Expand All @@ -101,6 +113,38 @@ export const convertToTermName = (termId: string) => {
}
};

const getTermToDisplay = async (
termInfoMap: Map<Term, TermDateDetails>,

): Promise<Term> => {

// (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
*/
Expand All @@ -123,7 +167,7 @@ export const getAvailableTermDetails = async () => {

try {
const termMapInfo = await constructTermDetailsMap();
const currTermId = await get_current_term(termMapInfo);
const currTermId = await getTermToDisplay(termMapInfo);
firstDayOfTerm =
termMapInfo.get(currTermId)?.startDate?.toLocaleDateString().split('/').reverse().join('-') || 'default-date';

Expand Down Expand Up @@ -237,4 +281,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';
1 change: 1 addition & 0 deletions client/src/context/UserContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};
Expand Down
67 changes: 67 additions & 0 deletions client/src/utils/getTermsWithClassData.ts
Original file line number Diff line number Diff line change
@@ -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: "COMP2521" } },
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<boolean> => {
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<string[]> => {

const termsWithData: string[] = [];

for (const term of terms) {
const hasClassData = await existsClassDataInTerm(term.substring(0, 2));
if (hasClassData) {
termsWithData.push(term);
}
}

return termsWithData;
};