diff --git a/nextjs/src/app/api/courses/[courseName]/route.ts b/nextjs/src/app/api/courses/[courseName]/route.ts new file mode 100644 index 0000000..66d0ab6 --- /dev/null +++ b/nextjs/src/app/api/courses/[courseName]/route.ts @@ -0,0 +1,14 @@ +import { fileStorageService } from "@/services/fileStorage/fileStorageService"; + +export async function PUT( + request: Request, + { params: { courseName } }: { params: { courseName: string } } +) { + const { updatedCourse, previousCourse } = await request.json(); + + console.log(updatedCourse); + console.log(courseName); + + await fileStorageService.saveCourseAsync(updatedCourse, previousCourse); + return Response.json({}); +} diff --git a/nextjs/src/app/course/[courseName]/calendar/Day.tsx b/nextjs/src/app/course/[courseName]/calendar/Day.tsx index a7dff0e..e640758 100644 --- a/nextjs/src/app/course/[courseName]/calendar/Day.tsx +++ b/nextjs/src/app/course/[courseName]/calendar/Day.tsx @@ -4,12 +4,17 @@ import { getDateFromStringOrThrow } from "@/models/local/timeUtils"; import { useCourseContext } from "../context/courseContext"; export default function Day({ day, month }: { day: Date; month: number }) { - const context = useCourseContext(); + const { + localCourse: { modules }, + startItemDrag, + endItemDrag, + itemDrop, + } = useCourseContext(); const isInSameMonth = day.getMonth() + 1 != month; const backgroundClass = isInSameMonth ? "" : "bg-slate-900"; - const todaysAssignments = context.localCourse.modules + const todaysAssignments = modules .flatMap((m) => m.assignments) .filter((a) => { const dueDate = getDateFromStringOrThrow( @@ -22,7 +27,7 @@ export default function Day({ day, month }: { day: Date; month: number }) { dueDate.getDate() === day.getDate() ); }); - const todaysQuizzes = context.localCourse.modules + const todaysQuizzes = modules .flatMap((m) => m.quizzes) .filter((q) => { const dueDate = getDateFromStringOrThrow( @@ -35,7 +40,7 @@ export default function Day({ day, month }: { day: Date; month: number }) { dueDate.getDate() === day.getDate() ); }); - const todaysPages = context.localCourse.modules + const todaysPages = modules .flatMap((m) => m.pages) .filter((p) => { const dueDate = getDateFromStringOrThrow( @@ -53,17 +58,34 @@ export default function Day({ day, month }: { day: Date; month: number }) { className={ "border border-slate-600 rounded-lg p-2 pb-4 m-1 " + backgroundClass } + onDrop={() => itemDrop(day)} + onDragOver={(e) => e.preventDefault()} > {day.getDate()} diff --git a/nextjs/src/app/course/[courseName]/context/CourseContextProvider.tsx b/nextjs/src/app/course/[courseName]/context/CourseContextProvider.tsx index 17e3481..c3ebe64 100644 --- a/nextjs/src/app/course/[courseName]/context/CourseContextProvider.tsx +++ b/nextjs/src/app/course/[courseName]/context/CourseContextProvider.tsx @@ -1,7 +1,13 @@ "use client"; import { ReactNode, useState } from "react"; import { CourseContext, DraggableItem } from "./courseContext"; -import { useLocalCourseDetailsQuery } from "@/hooks/localCoursesHooks"; +import { + useLocalCourseDetailsQuery, + useUpdateCourseMutation, +} from "@/hooks/localCoursesHooks"; +import { LocalQuiz } from "@/models/local/quiz/localQuiz"; +import { LocalCourse } from "@/models/local/localCourse"; +import { dateToMarkdownString } from "@/models/local/timeUtils"; export default function CourseContextProvider({ localCourseName, @@ -11,15 +17,59 @@ export default function CourseContextProvider({ localCourseName: string; }) { const { data: course } = useLocalCourseDetailsQuery(localCourseName); + const updateCourseMutation = useUpdateCourseMutation(course.settings.name); const [itemBeingDragged, setItemBeingDragged] = useState< DraggableItem | undefined >(); + return ( setItemBeingDragged(d), - stopModuleDrag: (day) => setItemBeingDragged(undefined), + startItemDrag: (d) => { + console.log("starting drag"); + setItemBeingDragged(d); + }, + endItemDrag: () => { + console.log("stopping drag"); + setItemBeingDragged(undefined); + }, + itemDrop: (day) => { + console.log("dropping"); + if (itemBeingDragged && day) { + if (itemBeingDragged.type === "quiz") { + const updatedQuiz: LocalQuiz = { + ...(itemBeingDragged.item as LocalQuiz), + dueAt: dateToMarkdownString(day), + }; + + const localModule = course.modules.find((m) => + m.quizzes.map((q) => q.name).includes(updatedQuiz.name) + ); + if (!localModule) + console.log("could not find module for quiz ", updatedQuiz); + + const updatedCourse: LocalCourse = { + ...course, + modules: course.modules.map((m) => + m.name !== localModule?.name + ? m + : { + ...m, + quizzes: m.quizzes.map((q) => + q.name === updatedQuiz.name ? updatedQuiz : q + ), + } + ), + }; + updateCourseMutation.mutate({ + updatedCourse, + previousCourse: course, + }); + } + } + setItemBeingDragged(undefined); + }, }} > {children} diff --git a/nextjs/src/app/course/[courseName]/context/courseContext.ts b/nextjs/src/app/course/[courseName]/context/courseContext.ts index a353f43..e1383e4 100644 --- a/nextjs/src/app/course/[courseName]/context/courseContext.ts +++ b/nextjs/src/app/course/[courseName]/context/courseContext.ts @@ -10,8 +10,9 @@ export interface DraggableItem { export interface CourseContextInterface { localCourse: LocalCourse; - startModuleDrag: (dragging: DraggableItem) => void; - stopModuleDrag: (droppedOnDay?: Date) => void; + startItemDrag: (dragging: DraggableItem) => void; + endItemDrag: () => void; + itemDrop: (droppedOnDay?: Date) => void; } const defaultValue: CourseContextInterface = { @@ -29,10 +30,9 @@ const defaultValue: CourseContextInterface = { }, }, }, - startModuleDrag: () => { }, - stopModuleDrag: function (droppedOnDay?: Date): void { - throw new Error("Function not implemented."); - } + startItemDrag: () => {}, + endItemDrag: () => {}, + itemDrop: () => {}, }; export const CourseContext = diff --git a/nextjs/src/hooks/localCoursesHooks.ts b/nextjs/src/hooks/localCoursesHooks.ts index 97a54c0..608d1f6 100644 --- a/nextjs/src/hooks/localCoursesHooks.ts +++ b/nextjs/src/hooks/localCoursesHooks.ts @@ -1,5 +1,10 @@ import { LocalCourse } from "@/models/local/localCourse"; -import { useSuspenseQuery } from "@tanstack/react-query"; +import { + dataTagSymbol, + useMutation, + useQueryClient, + useSuspenseQuery, +} from "@tanstack/react-query"; import axios from "axios"; export const localCourseKeys = { @@ -32,3 +37,22 @@ export const useLocalCourseDetailsQuery = (courseName: string) => { }, }); }; + +export const useUpdateCourseMutation = (courseName: string) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (body: { + updatedCourse: LocalCourse; + previousCourse: LocalCourse; + }) => { + const url = `/api/courses/${courseName}`; + await axios.put(url, body); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: localCourseKeys.allCourses }); //optimize? + }, + scope: { + id: "update course", + }, + }); +};