Skip to content

Commit 2009d61

Browse files
ShzmjhhuoluRayahhhmeddlynzDylan Zhang
authored
Sync timetables merge dev (#916)
* 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 <raiyan.ahmed@gmail.com> Co-authored-by: Dylan Zhang <dylan.zhang15@gmail.com> * 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 <qinnikki2002@gmail.com> * 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 <chhuolucy@gmail.com> Co-authored-by: ray <raiyan.ahmed@gmail.com> Co-authored-by: Raiyan Ahmed <80839724+Rayahhhmed@users.noreply.github.com> Co-authored-by: dlyn <100419289+dlynz@users.noreply.github.com> Co-authored-by: Dylan Zhang <dylan.zhang15@gmail.com> Co-authored-by: Jasmine Tran <z5394841@ad.unsw.edu.au> Co-authored-by: dylan <robouser51@gmail.com> Co-authored-by: Dylan Zhang <z5478419@ad.unsw.edu.au> Co-authored-by: Michael Siu <michaelsiu01@gmail.com> Co-authored-by: nikkichins <qinnikki2002@gmail.com>
1 parent c4c1fd2 commit 2009d61

33 files changed

+729
-82
lines changed

client/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
"style": "prettier --write 'src/**/*.{ts,tsx}' && eslint --fix 'src/**/*.{ts,tsx}'"
1717
},
1818
"dependencies": {
19-
"@apollo/client": "3.11.8",
2019
"@date-io/date-fns": "2.17.0",
2120
"@emotion/react": "11.11.1",
2221
"@emotion/styled": "11.11.0",
@@ -33,6 +32,7 @@
3332
"@sentry/tracing": "7.84.0",
3433
"@uiw/react-color": "2.1.1",
3534
"clsx": "2.1.1",
35+
"@apollo/client": "3.11.8",
3636
"date-fns": "2.30.0",
3737
"dayjs": "1.11.12",
3838
"file-saver": "2.0.5",
@@ -88,10 +88,10 @@
8888
"@types/react-transition-group": "4.4.10",
8989
"@types/react-window": "1.8.8",
9090
"@types/uuid": "9.0.8",
91+
"autoprefixer": "10.4.19",
9192
"@typescript-eslint/eslint-plugin": "7.16.1",
9293
"@typescript-eslint/parser": "7.16.1",
9394
"@vitejs/plugin-react-swc": "3.7.0",
94-
"autoprefixer": "10.4.19",
9595
"eslint": "8.57.0",
9696
"eslint-config-prettier": "9.1.0",
9797
"eslint-plugin-react": "7.35.0",
@@ -107,5 +107,6 @@
107107
"vite-plugin-svgr": "4.2.0",
108108
"vite-tsconfig-paths": "5.0.1",
109109
"vitest": "2.1.2"
110-
}
110+
},
111+
"packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a"
111112
}

client/src/App.tsx

+20-2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
import { setDropzoneRange, useDrag } from './utils/Drag';
4646
import { downloadIcsFile } from './utils/generateICS';
4747
import storage from './utils/storage';
48+
import { runSync } from './utils/syncTimetables';
4849
import { createDefaultTimetable } from './utils/timetableHelpers';
4950

5051
const StyledApp = styled(Box)`
@@ -134,7 +135,7 @@ const App: React.FC = () => {
134135
setAssignedColors,
135136
} = useContext(CourseContext);
136137

137-
const { groupsSidebarCollapsed, setGroupsSidebarCollapsed } = useContext(UserContext);
138+
const { user, setUser, groupsSidebarCollapsed, setGroupsSidebarCollapsed } = useContext(UserContext);
138139

139140
setDropzoneRange(days.length, earliestStartTime, latestEndTime);
140141

@@ -186,7 +187,7 @@ const App: React.FC = () => {
186187
...{
187188
[termId as string]: oldData.hasOwnProperty(termId as string)
188189
? oldData[termId as string]
189-
: createDefaultTimetable(),
190+
: createDefaultTimetable(user.userID),
190191
},
191192
};
192193
}
@@ -429,36 +430,53 @@ const App: React.FC = () => {
429430
updateTimetableEvents();
430431
}, [year, isConvertToLocalTimezone]);
431432

433+
const syncTimetables = () => {
434+
if (!user.userID) {
435+
return;
436+
}
437+
438+
runSync(user, setUser, displayTimetables, setDisplayTimetables);
439+
};
440+
432441
// The following three useUpdateEffects update local storage whenever a change is made to the timetable
433442
useUpdateEffect(() => {
434443
displayTimetables[term][selectedTimetable].selectedCourses = selectedCourses;
435444
const newCourseData = courseData;
436445
storage.set('courseData', newCourseData);
446+
437447
storage.set('timetables', displayTimetables);
438448
setDisplayTimetables(displayTimetables);
449+
syncTimetables();
439450
}, [selectedCourses]);
440451

441452
useUpdateEffect(() => {
442453
displayTimetables[term][selectedTimetable].selectedClasses = selectedClasses;
454+
443455
storage.set('timetables', displayTimetables);
444456
setDisplayTimetables(displayTimetables);
457+
syncTimetables();
445458
}, [selectedClasses]);
446459

447460
useUpdateEffect(() => {
448461
displayTimetables[term][selectedTimetable].createdEvents = createdEvents;
462+
449463
storage.set('timetables', displayTimetables);
450464
setDisplayTimetables(displayTimetables);
465+
syncTimetables();
451466
}, [createdEvents]);
452467

453468
useUpdateEffect(() => {
454469
displayTimetables[term][selectedTimetable].assignedColors = assignedColors;
470+
455471
storage.set('timetables', displayTimetables);
456472
setDisplayTimetables(displayTimetables);
473+
syncTimetables();
457474
}, [assignedColors]);
458475

459476
// Update storage when dragging timetables
460477
useUpdateEffect(() => {
461478
storage.set('timetables', displayTimetables);
479+
syncTimetables();
462480
}, [displayTimetables]);
463481

464482
/**

client/src/api/getCourseInfo.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const GET_COURSE_INFO = gql`
1717
activity
1818
status
1919
course_enrolment
20+
class_id
21+
term
2022
section
2123
times {
2224
day
@@ -113,7 +115,6 @@ const getCourseInfo = async (
113115
});
114116

115117
const json: DbCourse = graphQLCourseToDbCourse(data);
116-
117118
json.classes.forEach((dbClass) => {
118119
// Some courses split up a single class into two separate classes. e.g. CHEM1011 does it (as of 22T3)
119120
// because one half of the course is taught by one lecturer and the other half is taught by another.
@@ -173,7 +174,6 @@ const getCourseInfo = async (
173174
});
174175

175176
if (!json) throw new NetworkError('Internal server error');
176-
177177
return dbCourseToCourseData(json, isConvertToLocalTimezone);
178178
} catch (error) {
179179
console.log(error);

client/src/components/controls/History.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react';
44

55
import { AppContext } from '../../context/AppContext';
66
import { CourseContext } from '../../context/CourseContext';
7+
import { UserContext } from '../../context/UserContext';
78
import { CourseData, CreatedEvents, DisplayTimetablesMap, SelectedClasses } from '../../interfaces/Periods';
89
import {
910
ActionsPointer,
@@ -30,6 +31,7 @@ const History: React.FC = () => {
3031
useContext(CourseContext);
3132
const { isDrag, setIsDrag, selectedTimetable, setSelectedTimetable, displayTimetables, setDisplayTimetables, term } =
3233
useContext(AppContext);
34+
const { user } = useContext(UserContext);
3335

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

210213
const newDisplayTimetables: DisplayTimetablesMap = {
211214
...displayTimetables,
212-
[term]: createDefaultTimetable(),
215+
[term]: newTimetables,
213216
};
214217
setTimetableState([], {}, {}, newDisplayTimetables, 0);
215218
};

client/src/components/sidebar/UserAccount.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import React, { useContext, useState } from 'react';
55

66
import { API_URL } from '../../api/config';
77
import { undefinedUser, UserContext } from '../../context/UserContext';
8-
import { TimetableData } from '../../interfaces/Periods';
8+
import { DisplayTimetablesMap } from '../../interfaces/Periods';
9+
import storage from '../../utils/storage';
10+
import { createDefaultTimetable } from '../../utils/timetableHelpers';
911
import StyledDialog from '../StyledDialog';
1012
import UserProfile from './groupsSidebar/friends/UserProfile';
1113

@@ -55,24 +57,21 @@ export interface User {
5557
friends: User[];
5658
incoming: User[];
5759
outgoing: User[];
58-
timetables: TimetableData[];
60+
timetables: DisplayTimetablesMap;
5961
}
6062

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

6567
const { user, setUser } = useContext(UserContext);
66-
6768
const loginCall = async () => {
6869
setWindowLocation(window.location.href);
6970
try {
7071
window.location.href = `${API_URL.server}/auth/login`;
7172
} catch (error) {
7273
console.log(error);
7374
}
74-
// Replaces current history item rather than adding item to history
75-
// window.location.replace(`${API_URL.server}/auth/login`);
7675
};
7776

7877
const logoutCall = async () => {
@@ -85,6 +84,7 @@ const UserAccount: React.FC<UserAccountProps> = ({ collapsed }) => {
8584
}
8685
window.location.replace(windowLocation);
8786
setUser(undefinedUser);
87+
storage.set('timetables', createDefaultTimetable(undefined));
8888
};
8989
if (!user.userID) {
9090
return collapsed ? (

client/src/components/timetableTabs/TimetableTabs.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ const TimetableTabs: React.FC = () => {
101101
};
102102
storage.set('timetables', addingNewTimetables);
103103
setDisplayTimetables(addingNewTimetables);
104-
105104
// Clearing the selected courses, classes and created events for the new timetable
106105
setTimetableState([], {}, {}, {}, nextIndex);
107106
}

client/src/constants/defaults.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const defaults: Record<string, any> = {
1010
isHideExamClasses: false,
1111
isConvertToLocalTimezone: true,
1212
courseData: { map: [] },
13-
timetables: { T0: createDefaultTimetable() },
13+
timetables: { T0: createDefaultTimetable('') },
1414
};
1515

1616
export default defaults;

client/src/context/UserContext.tsx

+48-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
import { createContext, useEffect, useMemo, useState } from 'react';
1+
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
22

33
import { API_URL } from '../api/config';
44
import { User } from '../components/sidebar/UserAccount';
55
import { Group } from '../interfaces/Group';
66
import NetworkError from '../interfaces/NetworkError';
7+
import { DisplayTimetablesMap, TimetableDTO } from '../interfaces/Periods';
78
import { UserContextProviderProps } from '../interfaces/PropTypes';
9+
import { parseTimetableDTO } from '../utils/syncTimetables';
10+
import { createDefaultTimetable } from '../utils/timetableHelpers';
11+
import { AppContext } from './AppContext';
12+
import { CourseContext } from './CourseContext';
813

914
export const undefinedUser = {
1015
userID: '',
@@ -18,7 +23,7 @@ export const undefinedUser = {
1823
friends: [],
1924
incoming: [],
2025
outgoing: [],
21-
timetables: [],
26+
timetables: {},
2227
};
2328

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

5461
const getUserInfo = async (userID: string) => {
5562
try {
@@ -60,10 +67,41 @@ const UserContextProvider = ({ children }: UserContextProviderProps) => {
6067
'Content-Type': 'application/json',
6168
},
6269
});
63-
const userResponse = await response.text();
64-
console.log(userResponse);
70+
const res = await response.json();
71+
const timetables = await Promise.all(
72+
res.data.timetables.map((timetable: TimetableDTO) => parseTimetableDTO(timetable, year)),
73+
);
6574

66-
if (userResponse !== '') setUser(JSON.parse(userResponse).data);
75+
// Unpack timetables based on key
76+
const timetableMap: DisplayTimetablesMap = {};
77+
78+
timetables.forEach(({ mapKey, timetable }) => {
79+
if (!timetableMap[mapKey]) {
80+
timetableMap[mapKey] = [];
81+
}
82+
timetableMap[mapKey].push(timetable);
83+
});
84+
85+
const userResponse = { ...res.data, timetables: structuredClone(timetableMap) };
86+
setUser(userResponse);
87+
88+
// Check current term exists. If not, create default timetable for this term
89+
// NOTE: This is AFTER setting the timetableMap for user.timetable. By doing this, we allow the runSync
90+
// function to pick up that there's a difference, and sync the default timetable with the backend.
91+
if (!Object.keys(timetableMap).includes(term)) {
92+
timetableMap[term] = createDefaultTimetable(res.data.userID);
93+
}
94+
setDisplayTimetables({ ...timetableMap });
95+
96+
// TODO: check if this conditional is necessary
97+
if (timetableMap[term] && timetableMap[term][0]) {
98+
const { selectedCourses, selectedClasses, createdEvents, assignedColors } = timetableMap[term][0];
99+
setSelectedCourses(selectedCourses);
100+
setSelectedClasses(selectedClasses);
101+
setCreatedEvents(createdEvents);
102+
setAssignedColors(assignedColors);
103+
setSelectedTimetable(0);
104+
}
67105
} catch (error) {
68106
console.log(error);
69107
}
@@ -89,7 +127,7 @@ const UserContextProvider = ({ children }: UserContextProviderProps) => {
89127
};
90128

91129
const fetchUserInfo = (userID: string) => {
92-
getUserInfo(userID);
130+
if (term !== 'T0') getUserInfo(userID);
93131
getGroups(userID);
94132
};
95133

@@ -104,15 +142,16 @@ const UserContextProvider = ({ children }: UserContextProviderProps) => {
104142
const userID = JSON.parse(userResponse);
105143
fetchUserInfo(userID);
106144
} else {
107-
console.error("Couldn't get response for user information!");
108-
// throw new NetworkError("Couldn't get response");
145+
setUser(undefinedUser);
146+
console.log('user is not logged in');
147+
throw new NetworkError("Couldn't get response for user information!");
109148
}
110149
} catch (error) {
111150
console.log(error);
112151
}
113152
};
114153
getZid();
115-
}, []);
154+
}, [term]);
116155

117156
const initialContext = useMemo(
118157
() => ({

client/src/interfaces/Database.ts

+2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ export interface DbCourse {
99
export interface DbClass {
1010
activity: Activity;
1111
times: DbTimes[];
12+
classID: string;
1213
status: Status;
1314
courseEnrolment: DbCourseEnrolment;
1415
section: Section;
16+
term: string;
1517
}
1618

1719
export interface DbCourseEnrolment {

client/src/interfaces/GraphQLCourseInfo.ts

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ interface Class {
1313
course_enrolment: string;
1414
section: string;
1515
times: Time[];
16+
term: string;
17+
class_id: string;
1618
}
1719

1820
interface Course {

0 commit comments

Comments
 (0)