Skip to content

Commit d8d8939

Browse files
committed
Add check for same name timetables in frontend and check for same name timetables in update endpoint
1 parent 234c1a4 commit d8d8939

File tree

6 files changed

+98
-7
lines changed

6 files changed

+98
-7
lines changed

course-matrix/backend/src/controllers/timetablesController.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,27 @@ export default {
190190
return res.status(404).json({ error: "Calendar id not found" });
191191
}
192192

193+
// Check if another timetable with the same title already exist for this user
194+
const { data: existingTimetable, error: existingTimetableError } =
195+
await supabase
196+
.schema("timetable")
197+
.from("timetables")
198+
.select("id")
199+
.eq("user_id", user_id)
200+
.eq("timetable_title", timetable_title)
201+
.neq("id", id)
202+
.maybeSingle();
203+
204+
if (existingTimetableError) {
205+
return res.status(400).json({ error: existingTimetableError.message });
206+
}
207+
208+
if (existingTimetable) {
209+
return res
210+
.status(400)
211+
.json({ error: "Another timetable with this title already exists" });
212+
}
213+
193214
let updateData: any = {};
194215
if (timetable_title) updateData.timetable_title = timetable_title;
195216
if (semester) updateData.semester = semester;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import TimetableCard from "./TimetableCard";
44
import TimetableCompareButton from "./TimetableCompareButton";
55
import TimetableCreateNewButton from "./TimetableCreateNewButton";
66
import { useGetTimetablesQuery } from "../../api/timetableApiSlice";
7+
import { useState } from "react";
8+
import TimetableErrorDialog from "../TimetableBuilder/TimetableErrorDialog";
79

810
export interface Timetable {
911
id: number;
@@ -31,13 +33,16 @@ const Home = () => {
3133
refetch: () => void;
3234
};
3335

36+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
37+
3438
return (
3539
<div className="w-full">
3640
<div className="m-8">
3741
<div className="mb-4 flex items-center gap-2 relative group">
3842
<h1 className="text-2xl font-medium tracking-tight">My Timetables</h1>
3943
<Pin size={24} className="text-blue-500" />
4044
</div>
45+
<TimetableErrorDialog isCreating={false} errorMessage={errorMessage} setErrorMessage={setErrorMessage} />
4146
<div className="mb-4 flex flex-row justify-between items-center">
4247
<div className="flex gap-4">
4348
<Button
@@ -78,6 +83,7 @@ const Home = () => {
7883
.map((timetable) => (
7984
<TimetableCard
8085
refetch={refetch}
86+
setErrorMessage={setErrorMessage}
8187
key={timetable.id}
8288
timetableId={timetable.id}
8389
title={timetable.timetable_title}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Link } from "react-router-dom";
1515

1616
interface TimetableCardProps {
1717
refetch: () => void;
18+
setErrorMessage: React.Dispatch<React.SetStateAction<string | null>>;
1819
timetableId: number;
1920
title: string;
2021
lastEditedDate: Date;
@@ -28,6 +29,7 @@ interface TimetableCardProps {
2829
*/
2930
const TimetableCard = ({
3031
refetch,
32+
setErrorMessage,
3133
timetableId,
3234
title,
3335
lastEditedDate,
@@ -56,7 +58,9 @@ const TimetableCard = ({
5658
}).unwrap();
5759
setIsEditingTitle(false);
5860
} catch (error) {
59-
console.error("Failed to update timetable title:", error);
61+
const errorData = (error as { data?: { error?: string } }).data;
62+
setErrorMessage(errorData?.error ?? "Unknown error occurred");
63+
return;
6064
}
6165
};
6266

course-matrix/frontend/src/pages/TimetableBuilder/Calendar.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
useDeleteRestrictionMutation,
3636
} from "@/api/restrictionsApiSlice";
3737
import { z } from "zod";
38-
import React, { useEffect, useRef } from "react";
38+
import React, { useEffect, useRef, useState } from "react";
3939
import { useGetNumberOfCourseSectionsQuery } from "@/api/coursesApiSlice";
4040
import {
4141
useCreateEventMutation,
@@ -58,6 +58,7 @@ import {
5858
getSemesterStartAndEndDatesPlusOneWeek,
5959
} from "@/utils/semester-utils";
6060
import { courseEventStyles } from "@/constants/calendarConstants";
61+
import TimetableErrorDialog from "./TimetableErrorDialog";
6162

6263
interface CalendarProps {
6364
setShowLoadingPage: React.Dispatch<React.SetStateAction<boolean>>;
@@ -110,6 +111,8 @@ const Calendar = React.memo<CalendarProps>(
110111
const [createRestriction] = useCreateRestrictionMutation();
111112
const [deleteRestriction] = useDeleteRestrictionMutation();
112113

114+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
115+
113116
const semesterStartDate = getSemesterStartAndEndDates(semester).start;
114117
const { start: semesterStartDatePlusOneWeek, end: semesterEndDate } =
115118
getSemesterStartAndEndDatesPlusOneWeek(semester);
@@ -224,17 +227,18 @@ const Calendar = React.memo<CalendarProps>(
224227
}, [timetablesData, editingTimetableId, isEditingTimetable]);
225228

226229
const handleCreate = async () => {
227-
setShowLoadingPage(true);
228230
const timetableTitle = timetableTitleRef.current?.value ?? "";
229231
// Create timetable
230232
const { data, error } = await createTimetable({
231233
timetable_title: timetableTitle,
232234
semester: semester,
233235
});
234236
if (error) {
235-
console.error(error);
237+
const errorData = (error as { data?: { error?: string } }).data;
238+
setErrorMessage(errorData?.error ?? "Unknown error occurred");
236239
return;
237240
}
241+
setShowLoadingPage(true);
238242
// Create course events for the newly created timetable
239243
const newTimetableId = data[0].id;
240244
for (const offeringId of newOfferingIds) {
@@ -336,6 +340,11 @@ const Calendar = React.memo<CalendarProps>(
336340
<div>
337341
<h1 className="text-2xl flex flex-row justify-between font-medium tracking-tight mb-8">
338342
<div>Your Timetable </div>
343+
<TimetableErrorDialog
344+
isCreating={!isEditingTimetable}
345+
errorMessage={errorMessage}
346+
setErrorMessage={setErrorMessage}
347+
/>
339348
{!isEditingTimetable ? (
340349
<Dialog>
341350
{isChoosingSectionsManually &&

course-matrix/frontend/src/pages/TimetableBuilder/GeneratedCalendars.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import { useCreateTimetableMutation } from "@/api/timetableApiSlice";
4545
import { useCreateEventMutation } from "@/api/eventsApiSlice";
4646
import { useCreateRestrictionMutation } from "@/api/restrictionsApiSlice";
4747
import { TimetableForm } from "@/models/timetable-form";
48+
import { set } from "zod";
49+
import TimetableErrorDialog from "./TimetableErrorDialog";
4850

4951
interface GeneratedCalendarsProps {
5052
setShowLoadingPage: (_: boolean) => void;
@@ -120,18 +122,21 @@ export const GeneratedCalendars = React.memo<GeneratedCalendarsProps>(
120122
parseEvent(index + 1, event, "courseEvent"),
121123
) ?? [];
122124

125+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
126+
123127
const handleCreate = async () => {
124-
setShowLoadingPage(true);
125128
const timetableTitle = timetableTitleRef.current?.value ?? "";
126129
// Create timetable
127130
const { data, error } = await createTimetable({
128131
timetable_title: timetableTitle,
129132
semester: semester,
130133
});
131134
if (error) {
132-
console.error(error);
135+
const errorData = (error as { data?: { error?: string } }).data;
136+
setErrorMessage(errorData?.error ?? "Unknown error occurred");
133137
return;
134-
}
138+
}
139+
setShowLoadingPage(true);
135140
// Create course events for the newly created timetable
136141
const newTimetableId = data[0].id;
137142
for (const offering of currentTimetableOfferings) {
@@ -210,6 +215,11 @@ export const GeneratedCalendars = React.memo<GeneratedCalendarsProps>(
210215
>
211216
Cancel Generating
212217
</Button>
218+
<TimetableErrorDialog
219+
isCreating={true}
220+
errorMessage={errorMessage}
221+
setErrorMessage={setErrorMessage}
222+
/>
213223
<Dialog>
214224
<DialogTrigger asChild>
215225
<Button size="sm" onClick={() => {}}>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Button } from "@/components/ui/button";
2+
import {
3+
DialogHeader,
4+
DialogFooter,
5+
Dialog,
6+
DialogContent,
7+
DialogTitle,
8+
DialogDescription,
9+
DialogClose,
10+
} from "@/components/ui/dialog";
11+
import React from "react";
12+
13+
interface TimetableErrorDialogProps {
14+
isCreating: boolean;
15+
errorMessage: string | null;
16+
setErrorMessage: React.Dispatch<React.SetStateAction<string | null>>;
17+
}
18+
19+
const TimetableErrorDialog: React.FC<TimetableErrorDialogProps> = ({ isCreating, errorMessage, setErrorMessage }) => {
20+
const dialogTitle = isCreating ? "An error occurred while creating your timetable" : "An error occurred while updating your timetable";
21+
22+
return (
23+
<Dialog open={errorMessage !== null} onOpenChange={() => setErrorMessage(null)}>
24+
<DialogContent className="gap-5">
25+
<DialogHeader>
26+
<DialogTitle className="text-red-500">{dialogTitle}</DialogTitle>
27+
<DialogDescription>{errorMessage}</DialogDescription>
28+
</DialogHeader>
29+
<DialogFooter>
30+
<DialogClose asChild>
31+
<Button variant="secondary" onClick={() => setErrorMessage(null)}>
32+
Close
33+
</Button>
34+
</DialogClose>
35+
</DialogFooter>
36+
</DialogContent>
37+
</Dialog>
38+
);
39+
};
40+
41+
export default TimetableErrorDialog;

0 commit comments

Comments
 (0)