Skip to content

Commit

Permalink
Sync timetables merge dev (#916)
Browse files Browse the repository at this point in the history
* feat(client): dialog for when user logs out

* added auth cleanup and user addition to db

* lint

* changed ci

* Update auth.service.ts

* Feature: Changed dark mode settings toggle to icon button in sidebar (Sidebar Dark Mode Icon #850) (#850)

* adding redirect link to devsoc

* made dark mode button component

* removed dark mode toggle

* removed darkmode toggle from settings

* made sidebar footer icons padding consistent

* added prop type for dark mode button

* sync dark mode button updates

* fully implemented dark mode button

---------

Co-authored-by: ray <[email protected]>
Co-authored-by: Dylan Zhang <[email protected]>

* docs: added our latest 2024 subcom member

* renamed migrations

* testing out db migration changes

* changing script

* Created Landing Page

* setup tailwind

* updated packages for tailwind

* added white devsoc svg

* started hero section

* added hero section

* changed to black svg

* added lib utils

* added hovering animation

* new component

* moved out of herosection

* added sponsor section

* changed packes

* slight changes to colours and imports

* added macquarie

* sync slight changes

* made bigger added animations

* changing host name redirect handler

* fixed redirect details

* docker file fixed

* docker file fixed

* cleaned conditional

* cleaned conditional

* testing migrations changes

* changing migrations stuff

* removing migrations folder

* sync

* sync from macbook

* sync for cooperative work

* feat: year, term and classNo added to class data

* feat: add sync functions for add/remove/edit timetables. Create/duplicate/delete one timetable synced, and history sync started

* finished scrolling features section except for gifs

* slightly changed sponsors

* Merge divergent branches nikki -> main landing page branch (#888)

* tried some stuff for key features

* added features blocks

* completed key features component

* added footer

* deleted unecessary file

---------

Co-authored-by: nikkichins <[email protected]>

* added blob image

* added how it works text

* hacky solution for changing page

* make further above

* chore: revert addition of syncing fuctions to individual components

* feat: sync timetables function refactored to use setInterval

* fix: logic for sync timetables

* fix: bugs with typing w/ course codes and class no

* temp fix: change events back to old schema:

* added gifs

* removed unnecessary bg

* removed unnecessary bg colour

* added gifs

* removed 0 margin

* feat: add database dto to frontend object parsing. Also, add event to eventDTO parsing.

* feat: add subtype to event in schema

* fix: class info is correctly extracted from map

* feat: course code returned in scraped-class object from backend

* feat: add default assigned colors for courses when reconstructing timetable from db

* feat: syncing timetable updates user context

* fix: updated type in runsync arg

* fix: reverted unwanted regression in timetable creation logic

* feat: store timetable logic moved to usercontext

* fix: bug fixes

* feat: add mapkey/termkey to schema

* feat: add term key logic to frontend

* fixed header bug

* sent request to BE when creating a default timetable

* removing logout problems

* Changed createDefaultTimetable to accept userID and make a call to BE when setting default timetable.
In convertClassToDTO, fixed reduce to have initial empty [] otherwise it breaks.
Sent missing mapKey from FE to BE in createNewTimetable

* reduced four useEffects in App.tsx into one

* added fix for logout bugs

* checked for valid term, before gettingUserInfo and creating default timetableg

* removed timeout and fixed delete timetable by including header

* separated useEffect in App.tsx so that displayTimetables is its own to avoid circular calls

* fix: user display timetables are deep copies, and other course/app context variables set during init fetch of timetables

* fix: remove hard-coding of term in add timetable, and added back interval/altered logic to handle duplication of new timetables bug

* fix: first user timetable not being saved to backend fix

* added types for tt dto

* fix: DTO structure fixed, and cleaned up code

* Added Feedback section to landing page and cleaned up code in the Features section

* changed to accommodate snap scrolling

* change so default is landing page then after visiting becomes normal app

* renamed hero section

* changed duplicateClasses in timetableHelpers to generate a new uuid for duplicated classes

* Made hero section responsive

* made features and sponsors responsive

* made footer and feedback responsive

* made scrolling features section responsive

* made landing page responsive

* Merge conflicts resoplved

* fixing merge conflicts

* http change

* user context put in index

* added activity to be saved in the backend for classes

* unscheduled items are saved to BE

* fixed interface typing

* fix: revert compaction of useUpdateEffects to fix local storage on load bug

* init fix for gql

* added fix for uris

* fix: add term and classid fields in gql query

* fix: use unique classID in backend class cache; and fix bug with term parameter for getCourseInfo

* working fe hero lp

* fixed type error bugs

* fixed more type errors bugs

* gql added and working

* fix: remove mutation of classID

* fix: attempted fix of scraped class DTO construction from gql

* fix: fix type issues

* fix: time in right form

* fix: map key is properly convered to term

* final checks :)

* running lint

---------

Co-authored-by: hhuolu <[email protected]>
Co-authored-by: ray <[email protected]>
Co-authored-by: Raiyan Ahmed <[email protected]>
Co-authored-by: dlyn <[email protected]>
Co-authored-by: Dylan Zhang <[email protected]>
Co-authored-by: Jasmine Tran <[email protected]>
Co-authored-by: dylan <[email protected]>
Co-authored-by: Dylan Zhang <[email protected]>
Co-authored-by: Michael Siu <[email protected]>
Co-authored-by: nikkichins <[email protected]>
  • Loading branch information
11 people authored Nov 15, 2024
1 parent c4c1fd2 commit 2009d61
Show file tree
Hide file tree
Showing 33 changed files with 729 additions and 82 deletions.
7 changes: 4 additions & 3 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"style": "prettier --write 'src/**/*.{ts,tsx}' && eslint --fix 'src/**/*.{ts,tsx}'"
},
"dependencies": {
"@apollo/client": "3.11.8",
"@date-io/date-fns": "2.17.0",
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
Expand All @@ -33,6 +32,7 @@
"@sentry/tracing": "7.84.0",
"@uiw/react-color": "2.1.1",
"clsx": "2.1.1",
"@apollo/client": "3.11.8",
"date-fns": "2.30.0",
"dayjs": "1.11.12",
"file-saver": "2.0.5",
Expand Down Expand Up @@ -88,10 +88,10 @@
"@types/react-transition-group": "4.4.10",
"@types/react-window": "1.8.8",
"@types/uuid": "9.0.8",
"autoprefixer": "10.4.19",
"@typescript-eslint/eslint-plugin": "7.16.1",
"@typescript-eslint/parser": "7.16.1",
"@vitejs/plugin-react-swc": "3.7.0",
"autoprefixer": "10.4.19",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-react": "7.35.0",
Expand All @@ -107,5 +107,6 @@
"vite-plugin-svgr": "4.2.0",
"vite-tsconfig-paths": "5.0.1",
"vitest": "2.1.2"
}
},
"packageManager": "[email protected]+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a"
}
22 changes: 20 additions & 2 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
import { setDropzoneRange, useDrag } from './utils/Drag';
import { downloadIcsFile } from './utils/generateICS';
import storage from './utils/storage';
import { runSync } from './utils/syncTimetables';
import { createDefaultTimetable } from './utils/timetableHelpers';

const StyledApp = styled(Box)`
Expand Down Expand Up @@ -134,7 +135,7 @@ const App: React.FC = () => {
setAssignedColors,
} = useContext(CourseContext);

const { groupsSidebarCollapsed, setGroupsSidebarCollapsed } = useContext(UserContext);
const { user, setUser, groupsSidebarCollapsed, setGroupsSidebarCollapsed } = useContext(UserContext);

setDropzoneRange(days.length, earliestStartTime, latestEndTime);

Expand Down Expand Up @@ -186,7 +187,7 @@ const App: React.FC = () => {
...{
[termId as string]: oldData.hasOwnProperty(termId as string)
? oldData[termId as string]
: createDefaultTimetable(),
: createDefaultTimetable(user.userID),
},
};
}
Expand Down Expand Up @@ -429,36 +430,53 @@ const App: React.FC = () => {
updateTimetableEvents();
}, [year, isConvertToLocalTimezone]);

const syncTimetables = () => {
if (!user.userID) {
return;
}

runSync(user, setUser, displayTimetables, setDisplayTimetables);
};

// The following three useUpdateEffects update local storage whenever a change is made to the timetable
useUpdateEffect(() => {
displayTimetables[term][selectedTimetable].selectedCourses = selectedCourses;
const newCourseData = courseData;
storage.set('courseData', newCourseData);

storage.set('timetables', displayTimetables);
setDisplayTimetables(displayTimetables);
syncTimetables();
}, [selectedCourses]);

useUpdateEffect(() => {
displayTimetables[term][selectedTimetable].selectedClasses = selectedClasses;

storage.set('timetables', displayTimetables);
setDisplayTimetables(displayTimetables);
syncTimetables();
}, [selectedClasses]);

useUpdateEffect(() => {
displayTimetables[term][selectedTimetable].createdEvents = createdEvents;

storage.set('timetables', displayTimetables);
setDisplayTimetables(displayTimetables);
syncTimetables();
}, [createdEvents]);

useUpdateEffect(() => {
displayTimetables[term][selectedTimetable].assignedColors = assignedColors;

storage.set('timetables', displayTimetables);
setDisplayTimetables(displayTimetables);
syncTimetables();
}, [assignedColors]);

// Update storage when dragging timetables
useUpdateEffect(() => {
storage.set('timetables', displayTimetables);
syncTimetables();
}, [displayTimetables]);

/**
Expand Down
4 changes: 2 additions & 2 deletions client/src/api/getCourseInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const GET_COURSE_INFO = gql`
activity
status
course_enrolment
class_id
term
section
times {
day
Expand Down Expand Up @@ -113,7 +115,6 @@ const getCourseInfo = async (
});

const json: DbCourse = graphQLCourseToDbCourse(data);

json.classes.forEach((dbClass) => {
// Some courses split up a single class into two separate classes. e.g. CHEM1011 does it (as of 22T3)
// because one half of the course is taught by one lecturer and the other half is taught by another.
Expand Down Expand Up @@ -173,7 +174,6 @@ const getCourseInfo = async (
});

if (!json) throw new NetworkError('Internal server error');

return dbCourseToCourseData(json, isConvertToLocalTimezone);
} catch (error) {
console.log(error);
Expand Down
5 changes: 4 additions & 1 deletion client/src/components/controls/History.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react';

import { AppContext } from '../../context/AppContext';
import { CourseContext } from '../../context/CourseContext';
import { UserContext } from '../../context/UserContext';
import { CourseData, CreatedEvents, DisplayTimetablesMap, SelectedClasses } from '../../interfaces/Periods';
import {
ActionsPointer,
Expand All @@ -30,6 +31,7 @@ const History: React.FC = () => {
useContext(CourseContext);
const { isDrag, setIsDrag, selectedTimetable, setSelectedTimetable, displayTimetables, setDisplayTimetables, term } =
useContext(AppContext);
const { user } = useContext(UserContext);

const timetableActions = useRef<TimetableActions>({});
const actionsPointer = useRef<ActionsPointer>({});
Expand Down Expand Up @@ -205,11 +207,12 @@ const History: React.FC = () => {
* Resets all timetables - leave one as default
*/
const clearAll = () => {
const newTimetables = createDefaultTimetable(user.userID);
if (!term) return;

const newDisplayTimetables: DisplayTimetablesMap = {
...displayTimetables,
[term]: createDefaultTimetable(),
[term]: newTimetables,
};
setTimetableState([], {}, {}, newDisplayTimetables, 0);
};
Expand Down
10 changes: 5 additions & 5 deletions client/src/components/sidebar/UserAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import React, { useContext, useState } from 'react';

import { API_URL } from '../../api/config';
import { undefinedUser, UserContext } from '../../context/UserContext';
import { TimetableData } from '../../interfaces/Periods';
import { DisplayTimetablesMap } from '../../interfaces/Periods';
import storage from '../../utils/storage';
import { createDefaultTimetable } from '../../utils/timetableHelpers';
import StyledDialog from '../StyledDialog';
import UserProfile from './groupsSidebar/friends/UserProfile';

Expand Down Expand Up @@ -55,24 +57,21 @@ export interface User {
friends: User[];
incoming: User[];
outgoing: User[];
timetables: TimetableData[];
timetables: DisplayTimetablesMap;
}

const UserAccount: React.FC<UserAccountProps> = ({ collapsed }) => {
const [windowLocation, setWindowLocation] = useState('');
const [logoutDialog, setLogoutDialog] = useState(false);

const { user, setUser } = useContext(UserContext);

const loginCall = async () => {
setWindowLocation(window.location.href);
try {
window.location.href = `${API_URL.server}/auth/login`;
} catch (error) {
console.log(error);
}
// Replaces current history item rather than adding item to history
// window.location.replace(`${API_URL.server}/auth/login`);
};

const logoutCall = async () => {
Expand All @@ -85,6 +84,7 @@ const UserAccount: React.FC<UserAccountProps> = ({ collapsed }) => {
}
window.location.replace(windowLocation);
setUser(undefinedUser);
storage.set('timetables', createDefaultTimetable(undefined));
};
if (!user.userID) {
return collapsed ? (
Expand Down
1 change: 0 additions & 1 deletion client/src/components/timetableTabs/TimetableTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ const TimetableTabs: React.FC = () => {
};
storage.set('timetables', addingNewTimetables);
setDisplayTimetables(addingNewTimetables);

// Clearing the selected courses, classes and created events for the new timetable
setTimetableState([], {}, {}, {}, nextIndex);
}
Expand Down
2 changes: 1 addition & 1 deletion client/src/constants/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const defaults: Record<string, any> = {
isHideExamClasses: false,
isConvertToLocalTimezone: true,
courseData: { map: [] },
timetables: { T0: createDefaultTimetable() },
timetables: { T0: createDefaultTimetable('') },
};

export default defaults;
57 changes: 48 additions & 9 deletions client/src/context/UserContext.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { createContext, useEffect, useMemo, useState } from 'react';
import { createContext, useContext, useEffect, useMemo, useState } from 'react';

import { API_URL } from '../api/config';
import { User } from '../components/sidebar/UserAccount';
import { Group } from '../interfaces/Group';
import NetworkError from '../interfaces/NetworkError';
import { DisplayTimetablesMap, TimetableDTO } from '../interfaces/Periods';
import { UserContextProviderProps } from '../interfaces/PropTypes';
import { parseTimetableDTO } from '../utils/syncTimetables';
import { createDefaultTimetable } from '../utils/timetableHelpers';
import { AppContext } from './AppContext';
import { CourseContext } from './CourseContext';

export const undefinedUser = {
userID: '',
Expand All @@ -18,7 +23,7 @@ export const undefinedUser = {
friends: [],
incoming: [],
outgoing: [],
timetables: [],
timetables: {},
};

export interface IUserContext {
Expand Down Expand Up @@ -50,6 +55,8 @@ const UserContextProvider = ({ children }: UserContextProviderProps) => {
const [groups, setGroups] = useState<Group[]>([]);
const [selectedGroupIndex, setSelectedGroupIndex] = useState<number>(-1);
const [groupsSidebarCollapsed, setGroupsSidebarCollapsed] = useState<boolean>(true);
const { setDisplayTimetables, setSelectedTimetable, term, year } = useContext(AppContext);
const { setSelectedClasses, setSelectedCourses, setCreatedEvents, setAssignedColors } = useContext(CourseContext);

const getUserInfo = async (userID: string) => {
try {
Expand All @@ -60,10 +67,41 @@ const UserContextProvider = ({ children }: UserContextProviderProps) => {
'Content-Type': 'application/json',
},
});
const userResponse = await response.text();
console.log(userResponse);
const res = await response.json();
const timetables = await Promise.all(
res.data.timetables.map((timetable: TimetableDTO) => parseTimetableDTO(timetable, year)),
);

if (userResponse !== '') setUser(JSON.parse(userResponse).data);
// Unpack timetables based on key
const timetableMap: DisplayTimetablesMap = {};

timetables.forEach(({ mapKey, timetable }) => {
if (!timetableMap[mapKey]) {
timetableMap[mapKey] = [];
}
timetableMap[mapKey].push(timetable);
});

const userResponse = { ...res.data, timetables: structuredClone(timetableMap) };
setUser(userResponse);

// Check current term exists. If not, create default timetable for this term
// NOTE: This is AFTER setting the timetableMap for user.timetable. By doing this, we allow the runSync
// function to pick up that there's a difference, and sync the default timetable with the backend.
if (!Object.keys(timetableMap).includes(term)) {
timetableMap[term] = createDefaultTimetable(res.data.userID);
}
setDisplayTimetables({ ...timetableMap });

// TODO: check if this conditional is necessary
if (timetableMap[term] && timetableMap[term][0]) {
const { selectedCourses, selectedClasses, createdEvents, assignedColors } = timetableMap[term][0];
setSelectedCourses(selectedCourses);
setSelectedClasses(selectedClasses);
setCreatedEvents(createdEvents);
setAssignedColors(assignedColors);
setSelectedTimetable(0);
}
} catch (error) {
console.log(error);
}
Expand All @@ -89,7 +127,7 @@ const UserContextProvider = ({ children }: UserContextProviderProps) => {
};

const fetchUserInfo = (userID: string) => {
getUserInfo(userID);
if (term !== 'T0') getUserInfo(userID);
getGroups(userID);
};

Expand All @@ -104,15 +142,16 @@ const UserContextProvider = ({ children }: UserContextProviderProps) => {
const userID = JSON.parse(userResponse);
fetchUserInfo(userID);
} else {
console.error("Couldn't get response for user information!");
// throw new NetworkError("Couldn't get response");
setUser(undefinedUser);
console.log('user is not logged in');
throw new NetworkError("Couldn't get response for user information!");
}
} catch (error) {
console.log(error);
}
};
getZid();
}, []);
}, [term]);

const initialContext = useMemo(
() => ({
Expand Down
2 changes: 2 additions & 0 deletions client/src/interfaces/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ export interface DbCourse {
export interface DbClass {
activity: Activity;
times: DbTimes[];
classID: string;
status: Status;
courseEnrolment: DbCourseEnrolment;
section: Section;
term: string;
}

export interface DbCourseEnrolment {
Expand Down
2 changes: 2 additions & 0 deletions client/src/interfaces/GraphQLCourseInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ interface Class {
course_enrolment: string;
section: string;
times: Time[];
term: string;
class_id: string;
}

interface Course {
Expand Down
Loading

0 comments on commit 2009d61

Please sign in to comment.