Skip to content

Commit 543ca10

Browse files
committed
Integrate Shared Timetables into the Timetable Compare Feature
1 parent 1230a30 commit 543ca10

File tree

5 files changed

+139
-179
lines changed

5 files changed

+139
-179
lines changed

course-matrix/frontend/src/pages/Compare/CompareTimetables.tsx

Lines changed: 77 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
1-
import {
2-
useGetTimetableQuery,
3-
useGetTimetablesQuery,
4-
} from "@/api/timetableApiSlice";
1+
import { useGetTimetablesQuery } from "@/api/timetableApiSlice";
52
import { Button } from "@/components/ui/button";
6-
import { Timetable, TimetableEvents } from "@/utils/type-utils";
7-
import { useEffect, useState } from "react";
3+
import { Timetable } from "@/utils/type-utils";
4+
import { useCallback, useEffect, useState } from "react";
85
import { Link, useSearchParams } from "react-router-dom";
9-
import Calendar from "../TimetableBuilder/Calendar";
10-
import { useGetEventsQuery } from "@/api/eventsApiSlice";
11-
import { Spinner } from "@/components/ui/spinner";
126
import { zodResolver } from "@hookform/resolvers/zod";
137
import { useForm } from "react-hook-form";
148
import { z } from "zod";
159
import { CompareFormSchema } from "../Home/TimetableCompareButton";
16-
import { format } from "path";
1710
import {
1811
Form,
1912
FormControl,
2013
FormField,
2114
FormItem,
22-
FormLabel,
2315
FormMessage,
2416
} from "@/components/ui/form";
2517
import {
@@ -31,99 +23,73 @@ import {
3123
} from "@/components/ui/select";
3224
import { SemesterIcon } from "@/components/semester-icon";
3325
import { GitCompareArrows } from "lucide-react";
26+
import { useGetTimetablesSharedWithMeQuery } from "@/api/sharedApiSlice";
27+
import { TimetableShare } from "../Home/Home";
28+
import ViewCalendar from "../TimetableBuilder/ViewCalendar";
29+
import { sortTimetablesComparator } from "@/utils/calendar-utils";
3430

3531
export const CompareTimetables = () => {
3632
const [timetable1, setTimetable1] = useState<Timetable>();
3733
const [timetable2, setTimetable2] = useState<Timetable>();
38-
const [offeringIds1, setOfferingIds1] = useState<number[]>();
39-
const [offeringIds2, setOfferingIds2] = useState<number[]>();
4034
const [queryParams] = useSearchParams();
35+
const [loadedPreselectedTimetables, setLoadedPreselectedTimetables] = useState(false);
36+
37+
const preselectedTimetableId1 = queryParams.get("id1");
38+
const preselectedTimetableId2 = queryParams.get("id2");
39+
const preselectedUserId1 = queryParams.get("userId1");
40+
const preselectedUserId2 = queryParams.get("userId2");
4141

4242
const compareForm = useForm<z.infer<typeof CompareFormSchema>>({
4343
resolver: zodResolver(CompareFormSchema),
4444
defaultValues: {
4545
timetable1: queryParams.has("id1")
46-
? parseInt(queryParams.get("id1") ?? "0")
46+
? `timetable1/${preselectedTimetableId1}/${preselectedUserId1}`
4747
: undefined,
4848
timetable2: queryParams.has("id2")
49-
? parseInt(queryParams.get("id2") ?? "0")
49+
? `timetable2/${preselectedTimetableId2}/${preselectedUserId2}`
5050
: undefined,
5151
},
5252
});
5353

54-
const onSubmit = (values: z.infer<typeof CompareFormSchema>) => {
55-
console.log("Compare Form submitted:", values);
56-
const timetableId1 = compareForm.getValues("timetable1");
57-
const timetableId2 = compareForm.getValues("timetable2");
58-
setTimetable1(timetables.find((t) => t.id === timetableId1));
59-
refetchEvents1();
60-
setTimetable2(timetables.find((t) => t.id === timetableId2));
61-
refetchEvents2();
62-
};
63-
64-
const {
65-
data: timetables,
66-
isLoading,
67-
refetch,
68-
} = useGetTimetablesQuery() as {
69-
data: Timetable[];
70-
isLoading: boolean;
71-
refetch: () => void;
72-
};
54+
const { data: myTimetablesData } = useGetTimetablesQuery() as { data: Timetable[] };
55+
const { data: sharedWithMeTimetablesData } = useGetTimetablesSharedWithMeQuery() as { data: TimetableShare[] };
7356

74-
const { data: timetableEventsData1, refetch: refetchEvents1 } =
75-
useGetEventsQuery(compareForm.getValues("timetable1") ?? undefined, {
76-
skip: compareForm.getValues("timetable1") === undefined,
77-
}) as {
78-
data: TimetableEvents;
79-
refetch: () => void;
80-
};
81-
const { data: timetableEventsData2, refetch: refetchEvents2 } =
82-
useGetEventsQuery(compareForm.getValues("timetable2"), {
83-
skip: compareForm.getValues("timetable2") === undefined,
84-
}) as {
85-
data: TimetableEvents;
86-
refetch: () => void;
87-
};
57+
const myOwningTimetables = [...(myTimetablesData ?? [])].sort(sortTimetablesComparator);
58+
const sharedWithMeTimetables = [...(sharedWithMeTimetablesData ?? [])]
59+
.flatMap((share) => share.timetables)
60+
.sort(sortTimetablesComparator);
61+
const allTimetables = [...myOwningTimetables, ...sharedWithMeTimetables]
62+
.map((timetable, index) => ({
63+
...timetable,
64+
isShared: index >= myOwningTimetables.length,
65+
}))
66+
.sort(sortTimetablesComparator);
8867

8968
useEffect(() => {
90-
if (queryParams.has("id1") && timetables) {
91-
const id = parseInt(queryParams.get("id1") || "0");
92-
compareForm.setValue("timetable1", id);
93-
setTimetable1(timetables.find((t) => t.id === id));
69+
if (
70+
preselectedTimetableId1 &&
71+
preselectedUserId1 &&
72+
preselectedTimetableId2 &&
73+
preselectedUserId2 &&
74+
!loadedPreselectedTimetables
75+
) {
76+
setTimetable1(allTimetables.find((t) => t.id === parseInt(preselectedTimetableId1) && t.user_id === preselectedUserId1));
77+
setTimetable2(allTimetables.find((t) => t.id === parseInt(preselectedTimetableId2) && t.user_id === preselectedUserId2));
78+
setLoadedPreselectedTimetables(true);
9479
}
95-
}, [timetables]);
80+
}, [preselectedTimetableId1, preselectedUserId1, preselectedTimetableId2, preselectedUserId2, allTimetables, loadedPreselectedTimetables]);
9681

97-
useEffect(() => {
98-
if (queryParams.has("id2") && timetables) {
99-
const id = parseInt(queryParams.get("id2") || "0");
100-
compareForm.setValue("timetable2", id);
101-
setTimetable2(timetables.find((t) => t.id === id));
102-
}
103-
}, [timetables]);
82+
const onSubmit = useCallback((values: z.infer<typeof CompareFormSchema>) => {
83+
console.log("Compare Form submitted:", values);
10484

105-
// get unique offeringIds for calendar
106-
useEffect(() => {
107-
if (timetableEventsData1) {
108-
const uniqueOfferingIds = new Set<number>();
109-
for (const event of timetableEventsData1.courseEvents) {
110-
if (!uniqueOfferingIds.has(event.offering_id))
111-
uniqueOfferingIds.add(event.offering_id);
112-
}
113-
setOfferingIds1(Array.from(uniqueOfferingIds));
114-
}
115-
}, [timetableEventsData1]);
85+
const timetableId1 = parseInt(values.timetable1.split("/")[1]);
86+
const timetableId2 = parseInt(values.timetable2.split("/")[1]);
87+
const timetableUserId1 = values.timetable1.split("/")[2];
88+
const timetableUserId2 = values.timetable2.split("/")[2];
11689

117-
useEffect(() => {
118-
if (timetableEventsData2) {
119-
const uniqueOfferingIds = new Set<number>();
120-
for (const event of timetableEventsData2.courseEvents) {
121-
if (!uniqueOfferingIds.has(event.offering_id))
122-
uniqueOfferingIds.add(event.offering_id);
123-
}
124-
setOfferingIds2(Array.from(uniqueOfferingIds));
125-
}
126-
}, [timetableEventsData2]);
90+
setTimetable1(allTimetables.find((t) => t.id === timetableId1 && t.user_id === timetableUserId1));
91+
setTimetable2(allTimetables.find((t) => t.id === timetableId2 && t.user_id === timetableUserId2));
92+
}, [allTimetables]);
12793

12894
return (
12995
<>
@@ -146,10 +112,10 @@ export const CompareTimetables = () => {
146112
render={({ field }) => (
147113
<FormItem>
148114
<Select
149-
onValueChange={(value) => field.onChange(Number(value))}
115+
onValueChange={(value) => field.onChange(value)}
150116
defaultValue={
151117
queryParams.has("id1")
152-
? (queryParams.get("id1") ?? "")
118+
? `timetable1/${preselectedTimetableId1}/${preselectedUserId1}`
153119
: undefined
154120
}
155121
>
@@ -159,11 +125,11 @@ export const CompareTimetables = () => {
159125
</SelectTrigger>
160126
</FormControl>
161127
<SelectContent>
162-
{timetables &&
163-
timetables.map((timetable) => (
128+
{allTimetables &&
129+
allTimetables.map((timetable) => (
164130
<SelectItem
165-
key={timetable.id}
166-
value={timetable.id.toString()}
131+
key={`timetable1/${timetable.id}/${timetable.user_id}`}
132+
value={`timetable1/${timetable.id}/${timetable.user_id}`}
167133
>
168134
<div className="flex items-center gap-2">
169135
<SemesterIcon
@@ -190,10 +156,10 @@ export const CompareTimetables = () => {
190156
render={({ field }) => (
191157
<FormItem>
192158
<Select
193-
onValueChange={(value) => field.onChange(Number(value))}
159+
onValueChange={(value) => field.onChange(value)}
194160
defaultValue={
195161
queryParams.has("id1")
196-
? (queryParams.get("id2") ?? "")
162+
? `timetable2/${preselectedTimetableId2}/${preselectedUserId2}`
197163
: undefined
198164
}
199165
>
@@ -203,11 +169,11 @@ export const CompareTimetables = () => {
203169
</SelectTrigger>
204170
</FormControl>
205171
<SelectContent>
206-
{timetables &&
207-
timetables.map((timetable) => (
172+
{allTimetables &&
173+
allTimetables.map((timetable) => (
208174
<SelectItem
209-
key={timetable.id}
210-
value={timetable.id.toString()}
175+
key={`timetable2/${timetable.id}/${timetable.user_id}`}
176+
value={`timetable2/${timetable.id}/${timetable.user_id}`}
211177
>
212178
<div className="flex items-center gap-2">
213179
<SemesterIcon
@@ -237,48 +203,32 @@ export const CompareTimetables = () => {
237203
<hr className="mb-4" />
238204
<div className="flex gap-4">
239205
<div className="w-1/2">
240-
{!offeringIds1 ? (
241-
<>
242-
{queryParams.has("id1") ? (
243-
<Spinner />
244-
) : (
245-
<div className="w-full text-center py-[8rem] text-sm bg-gray-100/50 rounded">
246-
Select a timetable to compare
247-
</div>
248-
)}
249-
</>
206+
{!timetable1 ? (
207+
<div className="w-full text-center py-[8rem] text-sm bg-gray-100/50 rounded">
208+
Select a timetable to compare
209+
</div>
250210
) : (
251-
<Calendar
252-
setShowLoadingPage={() => {}}
253-
isChoosingSectionsManually={false}
211+
<ViewCalendar
212+
user_id={timetable1.user_id}
213+
calendar_id={timetable1.id}
214+
timetable_title={timetable1?.timetable_title ?? ""}
254215
semester={timetable1?.semester ?? "Fall 2025"}
255-
selectedCourses={[]}
256-
newOfferingIds={offeringIds1}
257-
restrictions={[]}
258-
header={timetable1?.timetable_title}
216+
show_fancy_header={false}
259217
/>
260218
)}
261219
</div>
262220
<div className="w-1/2">
263-
{!offeringIds2 ? (
264-
<>
265-
{queryParams.has("id2") ? (
266-
<Spinner />
267-
) : (
268-
<div className="w-full text-center py-[8rem] text-sm bg-gray-100/50 rounded">
269-
Select a timetable to compare
270-
</div>
271-
)}
272-
</>
221+
{!timetable2 ? (
222+
<div className="w-full text-center py-[8rem] text-sm bg-gray-100/50 rounded">
223+
Select a timetable to compare
224+
</div>
273225
) : (
274-
<Calendar
275-
setShowLoadingPage={() => {}}
276-
isChoosingSectionsManually={false}
226+
<ViewCalendar
227+
user_id={timetable2.user_id}
228+
calendar_id={timetable2.id}
229+
timetable_title={timetable2?.timetable_title ?? ""}
277230
semester={timetable2?.semester ?? "Fall 2025"}
278-
selectedCourses={[]}
279-
newOfferingIds={offeringIds2}
280-
restrictions={[]}
281-
header={timetable2?.timetable_title}
231+
show_fancy_header={false}
282232
/>
283233
)}
284234
</div>

course-matrix/frontend/src/pages/Home/Home.tsx

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import { TimetableCompareButton } from "./TimetableCompareButton";
1414
import { useState } from "react";
1515
import TimetableErrorDialog from "../TimetableBuilder/TimetableErrorDialog";
1616
import { useGetTimetablesSharedWithMeQuery } from "@/api/sharedApiSlice";
17-
import SharedCalendar from "../TimetableBuilder/SharedCalendar";
18-
import { useGetUsernameFromUserIdQuery } from "@/api/authApiSlice";
17+
import ViewCalendar from "../TimetableBuilder/ViewCalendar";
18+
import { sortTimetablesComparator } from "@/utils/calendar-utils";
1919

2020
export interface Timetable {
2121
id: number;
@@ -35,14 +35,6 @@ export interface TimetableShare {
3535
timetables: Timetable[];
3636
}
3737

38-
function sortingFunction(a: Timetable, b: Timetable) {
39-
if (a.favorite == b.favorite)
40-
return b?.updated_at.localeCompare(a?.updated_at);
41-
if (a.favorite) return -1;
42-
if (b.favorite) return 1;
43-
return 0;
44-
}
45-
4638
/**
4739
* Home component that displays the user's timetables and provides options to create or compare timetables.
4840
* @returns {JSX.Element} The rendered component.
@@ -71,11 +63,17 @@ const Home = () => {
7163
const isLoading = myTimetablesDataLoading || sharedWithmeDataLoading;
7264

7365
const myOwningTimetables = [...(myTimetablesData ?? [])].sort(
74-
sortingFunction,
66+
sortTimetablesComparator,
7567
);
7668
const sharedWithMeTimetables = [...(sharedWithMeData ?? [])]
7769
.flatMap((share) => share.timetables)
78-
.sort(sortingFunction);
70+
.sort(sortTimetablesComparator);
71+
const allTimetables = [...myOwningTimetables, ...sharedWithMeTimetables]
72+
.map((timetable, index) => ({
73+
...timetable,
74+
isShared: index >= myOwningTimetables.length,
75+
}))
76+
.sort(sortTimetablesComparator);
7977

8078
const [errorMessage, setErrorMessage] = useState<string | null>(null);
8179
const [activeTab, setActiveTab] = useState("Mine");
@@ -88,13 +86,6 @@ const Home = () => {
8886
const selectedSharedTimetableOwnerId = selectedSharedTimetable?.user_id ?? "";
8987
const selectSharedTimetableSemester = selectedSharedTimetable?.semester ?? "";
9088

91-
// Get the selected shared timetable owner's username
92-
const { data: usernameData } = useGetUsernameFromUserIdQuery(
93-
selectedSharedTimetableOwnerId,
94-
{ skip: selectedSharedTimetableId === -1 },
95-
);
96-
const ownerUsername = usernameData ?? "";
97-
9889
return (
9990
<div className="w-full">
10091
<div className="m-8">
@@ -103,21 +94,15 @@ const Home = () => {
10394
onOpenChange={() => setSelectedSharedTimetable(null)}
10495
>
10596
<DialogTitle></DialogTitle>
106-
<DialogContent className="max-w-[70%] max-h-[90%] overflow-y-scroll">
107-
<SharedCalendar
97+
<DialogContent className="max-w-[80%] max-h-[90%] overflow-y-scroll">
98+
<ViewCalendar
10899
user_id={selectedSharedTimetableOwnerId}
109-
user_username={ownerUsername}
110100
calendar_id={selectedSharedTimetableId}
111101
timetable_title={selectedSharedTimetableTitle}
112102
semester={selectSharedTimetableSemester}
103+
show_fancy_header={true}
113104
/>
114-
<DialogFooter>
115-
<DialogClose asChild>
116-
<Button variant="secondary" size="sm">
117-
Close
118-
</Button>
119-
</DialogClose>
120-
</DialogFooter>
105+
<DialogFooter><DialogClose></DialogClose></DialogFooter>
121106
</DialogContent>
122107
</Dialog>
123108
<div className="mb-4 flex items-center gap-2 relative group">
@@ -146,7 +131,7 @@ const Home = () => {
146131
</Button>
147132
</div>
148133
<div className="flex gap-2">
149-
<TimetableCompareButton timetables={myTimetablesData} />
134+
<TimetableCompareButton timetables={allTimetables} />
150135
<TimetableCreateNewButton />
151136
</div>
152137
</div>

0 commit comments

Comments
 (0)