diff --git a/Management/Features/Configuration/Synchronization/AssignmentSyncronizationExtensions.cs b/Management/Features/Configuration/Synchronization/AssignmentSyncronizationExtensions.cs index 3250a7c..7a4b114 100644 --- a/Management/Features/Configuration/Synchronization/AssignmentSyncronizationExtensions.cs +++ b/Management/Features/Configuration/Synchronization/AssignmentSyncronizationExtensions.cs @@ -73,6 +73,7 @@ public static partial class AssignmentSyncronizationExtensions var reason = localAssignment.GetUpdateReason(canvasAssignment, canvasAssignmentGroupId, quiet); return reason != string.Empty; } + public static string GetUpdateReason( this LocalAssignment localAssignment, CanvasAssignment canvasAssignment, diff --git a/nextjs/src/app/course/[courseName]/calendar/CalendarMonth.tsx b/nextjs/src/app/course/[courseName]/calendar/CalendarMonth.tsx index ae07bb7..d045ef8 100644 --- a/nextjs/src/app/course/[courseName]/calendar/CalendarMonth.tsx +++ b/nextjs/src/app/course/[courseName]/calendar/CalendarMonth.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { CalendarMonthModel } from "./calendarMonthUtils"; import { DayOfWeek } from "@/models/local/localCourse"; -import Day from "./Day"; +import Day from "./day/Day"; export const CalendarMonth = ({ month }: { month: CalendarMonthModel }) => { const weekInMilliseconds = 604_800_000; diff --git a/nextjs/src/app/course/[courseName]/calendar/Day.tsx b/nextjs/src/app/course/[courseName]/calendar/Day.tsx deleted file mode 100644 index 7f178da..0000000 --- a/nextjs/src/app/course/[courseName]/calendar/Day.tsx +++ /dev/null @@ -1,307 +0,0 @@ -"use client"; -import { - dateToMarkdownString, - getDateFromStringOrThrow, - getDateOnlyMarkdownString, -} from "@/models/local/timeUtils"; -import { DraggableItem, useDraggingContext } from "../context/draggingContext"; -import { useCalendarItemsContext } from "../context/calendarItemsContext"; -import { useCourseContext } from "../context/courseContext"; -import Link from "next/link"; -import { IModuleItem } from "@/models/local/IModuleItem"; -import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; -import { getDayOfWeek } from "@/models/local/localCourse"; -import { getLectureUrl, getModuleItemUrl } from "@/services/urlUtils"; -import { LocalAssignment } from "@/models/local/assignment/localAssignment"; -import { LocalQuiz } from "@/models/local/quiz/localQuiz"; -import { LocalCoursePage } from "@/models/local/page/localCoursePage"; -import { useCanvasAssignmentsQuery } from "@/hooks/canvas/canvasAssignmentHooks"; -import { useCanvasQuizzesQuery } from "@/hooks/canvas/canvasQuizHooks"; -import { useCanvasPagesQuery } from "@/hooks/canvas/canvasPageHooks"; -import DropTargetStyling from "./DropTargetStyling"; -import { CanvasQuiz } from "@/models/canvas/quizzes/canvasQuizModel"; -import { CanvasAssignment } from "@/models/canvas/assignments/canvasAssignment"; -import { CanvasPage } from "@/models/canvas/pages/canvasPageModel"; -import { canvasService } from "@/services/canvas/canvasService"; -import { ReactNode } from "react"; - -export default function Day({ day, month }: { day: string; month: number }) { - const dayAsDate = getDateFromStringOrThrow( - day, - "calculating same month in day" - ); - const isToday = - getDateOnlyMarkdownString(new Date()) === - getDateOnlyMarkdownString(dayAsDate); - - const { data: settings } = useLocalCourseSettingsQuery(); - const { itemDropOnDay } = useDraggingContext(); - - const { todaysAssignments, todaysQuizzes, todaysPages } = useTodaysItems(day); - - const isInSameMonth = dayAsDate.getMonth() + 1 == month; - - const classOnThisDay = settings.daysOfWeek.includes(getDayOfWeek(dayAsDate)); - - const meetingClasses = classOnThisDay ? " bg-slate-900 " : " "; - const monthClass = isInSameMonth - ? isToday - ? " border border-blue-700 shadow-[0_0px_10px_0px] shadow-blue-500/50 " - : " border border-slate-700 " - : " "; - - return ( -
itemDropOnDay(e, day)} - onDragOver={(e) => e.preventDefault()} - > - - -
- {todaysAssignments.map( - ({ assignment, moduleName, status, message }) => ( - - ) - )} - {todaysQuizzes.map(({ quiz, moduleName, status, message }) => ( - - ))} - {todaysPages.map(({ page, moduleName, status, message }) => ( - - ))} -
-
-
- ); -} - -function DayTitle({ day, dayAsDate }: { day: string; dayAsDate: Date }) { - const { courseName } = useCourseContext(); - return ( - - {dayAsDate.getDate()} - - ); -} - -function useTodaysItems(day: string) { - const dayAsDate = getDateFromStringOrThrow( - day, - "calculating same month in day items" - ); - const itemsContext = useCalendarItemsContext(); - const dateKey = getDateOnlyMarkdownString(dayAsDate); - const todaysModules = itemsContext[dateKey]; - - const { data: canvasAssignments } = useCanvasAssignmentsQuery(); - const { data: canvasQuizzes } = useCanvasQuizzesQuery(); - const { data: canvasPages } = useCanvasPagesQuery(); - const todaysAssignments: { - moduleName: string; - assignment: LocalAssignment; - status: "localOnly" | "incomplete" | "published"; - message: ReactNode; - }[] = todaysModules - ? Object.keys(todaysModules).flatMap((moduleName) => - todaysModules[moduleName].assignments.map((assignment) => { - const canvasAssignment = canvasAssignments.find( - (c) => c.name === assignment.name - ); - return { - moduleName, - assignment, - ...getStatus({ - item: assignment, - canvasItem: canvasAssignment, - type: "assignment", - }), - }; - }) - ) - : []; - - const todaysQuizzes: { - moduleName: string; - quiz: LocalQuiz; - status: "localOnly" | "incomplete" | "published"; - message: string; - }[] = todaysModules - ? Object.keys(todaysModules).flatMap((moduleName) => - todaysModules[moduleName].quizzes.map((quiz) => { - const canvasQuiz = canvasQuizzes.find((q) => q.title === quiz.name); - return { - moduleName, - quiz, - status: canvasQuiz - ? canvasQuiz.published - ? "published" - : "incomplete" - : "localOnly", - message: "", - }; - }) - ) - : []; - - const todaysPages: { - moduleName: string; - page: LocalCoursePage; - status: "localOnly" | "incomplete" | "published"; - message: string; - }[] = todaysModules - ? Object.keys(todaysModules).flatMap((moduleName) => - todaysModules[moduleName].pages.map((page) => { - const canvasPage = canvasPages.find((p) => p.title === page.name); - return { - moduleName, - page, - status: canvasPage - ? canvasPage.published - ? "published" - : "incomplete" - : "localOnly", - message: "", - }; - }) - ) - : []; - return { todaysAssignments, todaysQuizzes, todaysPages }; -} - -function DraggableListItem({ - type, - moduleName, - status, - item, - message, -}: { - type: "assignment" | "page" | "quiz"; - status: "localOnly" | "incomplete" | "published"; - moduleName: string; - item: IModuleItem; - message: ReactNode; -}) { - const { courseName } = useCourseContext(); - const { dragStart } = useDraggingContext(); - return ( - { - const draggableItem: DraggableItem = { - type, - item, - sourceModuleName: moduleName, - }; - e.dataTransfer.setData("draggableItem", JSON.stringify(draggableItem)); - dragStart(); - }} - > - {item.name} - {status === "incomplete" && ( -
- {message} -
- )} - - ); -} - -const getStatus = ({ - item, - canvasItem, - type, -}: { - item: LocalQuiz | LocalAssignment | LocalCoursePage; - canvasItem?: CanvasQuiz | CanvasAssignment | CanvasPage; - type: "assignment" | "page" | "quiz"; -}): { - status: "localOnly" | "incomplete" | "published"; - message: ReactNode; -} => { - if (!canvasItem) return { status: "localOnly", message: "not in canvas" }; - - if (!canvasItem.published) - return { status: "incomplete", message: "not published in canvas" }; - - if (type === "assignment") { - const assignment = item as LocalAssignment; - const canvasAssignment = canvasItem as CanvasAssignment; - - if(canvasAssignment.name === "Javascript 1") - console.log('js 1', canvasAssignment.due_at, canvasAssignment); - - - if (!canvasAssignment.due_at) - return { status: "incomplete", message: "due date not in canvas" }; - - if (assignment.lockAt && !canvasAssignment.lock_at) - return { status: "incomplete", message: "lock date not in canvas" }; - - const localDueDate = dateToMarkdownString( - getDateFromStringOrThrow(assignment.dueAt, "comparing due dates for day") - ); - const canvasDueDate = dateToMarkdownString( - getDateFromStringOrThrow( - canvasAssignment.due_at, - "comparing canvas due date for day" - ) - ); - if (localDueDate !== canvasDueDate) { - return { - status: "incomplete", - message: ( -
- due date different -
{localDueDate}
-
{canvasDueDate}
-
- ), - }; - } - } - - return { status: "published", message: "" }; -}; diff --git a/nextjs/src/app/course/[courseName]/calendar/day/Day.tsx b/nextjs/src/app/course/[courseName]/calendar/day/Day.tsx new file mode 100644 index 0000000..07cea3b --- /dev/null +++ b/nextjs/src/app/course/[courseName]/calendar/day/Day.tsx @@ -0,0 +1,95 @@ +"use client"; +import { + getDateFromStringOrThrow, + getDateOnlyMarkdownString, +} from "@/models/local/timeUtils"; +import { useDraggingContext } from "../../context/draggingContext"; +import { useCourseContext } from "../../context/courseContext"; +import Link from "next/link"; +import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; +import { getDayOfWeek } from "@/models/local/localCourse"; +import { getLectureUrl } from "@/services/urlUtils"; +import DropTargetStyling from "../DropTargetStyling"; +import { DraggableListItem } from "./DraggableListItem"; +import { useTodaysItems } from "./useTodaysItems"; + +export default function Day({ day, month }: { day: string; month: number }) { + const dayAsDate = getDateFromStringOrThrow( + day, + "calculating same month in day" + ); + const isToday = + getDateOnlyMarkdownString(new Date()) === + getDateOnlyMarkdownString(dayAsDate); + + const { data: settings } = useLocalCourseSettingsQuery(); + const { itemDropOnDay } = useDraggingContext(); + + const { todaysAssignments, todaysQuizzes, todaysPages } = useTodaysItems(day); + + const isInSameMonth = dayAsDate.getMonth() + 1 == month; + + const classOnThisDay = settings.daysOfWeek.includes(getDayOfWeek(dayAsDate)); + + const meetingClasses = classOnThisDay ? " bg-slate-900 " : " "; + const monthClass = isInSameMonth + ? isToday + ? " border border-blue-700 shadow-[0_0px_10px_0px] shadow-blue-500/50 " + : " border border-slate-700 " + : " "; + + return ( +
itemDropOnDay(e, day)} + onDragOver={(e) => e.preventDefault()} + > + + +
+ {todaysAssignments.map( + ({ assignment, moduleName, status, message }) => ( + + ) + )} + {todaysQuizzes.map(({ quiz, moduleName, status, message }) => ( + + ))} + {todaysPages.map(({ page, moduleName, status, message }) => ( + + ))} +
+
+
+ ); +} + +function DayTitle({ day, dayAsDate }: { day: string; dayAsDate: Date }) { + const { courseName } = useCourseContext(); + return ( + + {dayAsDate.getDate()} + + ); +} diff --git a/nextjs/src/app/course/[courseName]/calendar/day/DraggableListItem.tsx b/nextjs/src/app/course/[courseName]/calendar/day/DraggableListItem.tsx new file mode 100644 index 0000000..daa4b19 --- /dev/null +++ b/nextjs/src/app/course/[courseName]/calendar/day/DraggableListItem.tsx @@ -0,0 +1,65 @@ +import { IModuleItem } from "@/models/local/IModuleItem"; +import { getModuleItemUrl } from "@/services/urlUtils"; +import Link from "next/link"; +import { ReactNode } from "react"; +import { useCourseContext } from "../../context/courseContext"; +import { useDraggingContext, DraggableItem } from "../../context/draggingContext"; + +export function DraggableListItem({ + type, + moduleName, + status, + item, + message, +}: { + type: "assignment" | "page" | "quiz"; + status: "localOnly" | "incomplete" | "published"; + moduleName: string; + item: IModuleItem; + message: ReactNode; +}) { + const { courseName } = useCourseContext(); + const { dragStart } = useDraggingContext(); + return ( + { + const draggableItem: DraggableItem = { + type, + item, + sourceModuleName: moduleName, + }; + e.dataTransfer.setData("draggableItem", JSON.stringify(draggableItem)); + dragStart(); + }} + > + {item.name} + {status === "incomplete" && ( +
+ {message} +
+ )} + + ); +} diff --git a/nextjs/src/app/course/[courseName]/calendar/day/getStatus.tsx b/nextjs/src/app/course/[courseName]/calendar/day/getStatus.tsx new file mode 100644 index 0000000..86624e7 --- /dev/null +++ b/nextjs/src/app/course/[courseName]/calendar/day/getStatus.tsx @@ -0,0 +1,106 @@ +"use client"; +import { CanvasAssignment } from "@/models/canvas/assignments/canvasAssignment"; +import { CanvasPage } from "@/models/canvas/pages/canvasPageModel"; +import { CanvasQuiz } from "@/models/canvas/quizzes/canvasQuizModel"; +import { LocalAssignment } from "@/models/local/assignment/localAssignment"; +import { LocalCoursePage } from "@/models/local/page/localCoursePage"; +import { LocalQuiz } from "@/models/local/quiz/localQuiz"; +import { + dateToMarkdownString, + getDateFromStringOrThrow, +} from "@/models/local/timeUtils"; +import { ReactNode } from "react"; + +export const getStatus = ({ + item, + canvasItem, + type, +}: { + item: LocalQuiz | LocalAssignment | LocalCoursePage; + canvasItem?: CanvasQuiz | CanvasAssignment | CanvasPage; + type: "assignment" | "page" | "quiz"; +}): { + status: "localOnly" | "incomplete" | "published"; + message: ReactNode; +} => { + if (!canvasItem) return { status: "localOnly", message: "not in canvas" }; + + if (!canvasItem.published) + return { status: "incomplete", message: "not published in canvas" }; + + if (type === "page") { + const canvasPage = canvasItem as CanvasPage; + const page = item as LocalCoursePage; + + if (!canvasPage.published) + return { status: "incomplete", message: "canvas page not published" }; + return { status: "published", message: "" }; + } else if (type === "quiz") { + const quiz = item as LocalQuiz; + const canvasQuiz = canvasItem as CanvasQuiz + + + if (!canvasQuiz.due_at) + return { status: "incomplete", message: "due date not in canvas" }; + + if (quiz.lockAt && !canvasQuiz.lock_at) + return { status: "incomplete", message: "lock date not in canvas" }; + + const localDueDate = dateToMarkdownString( + getDateFromStringOrThrow(quiz.dueAt, "comparing due dates for day") + ); + + const canvasDueDate = dateToMarkdownString( + getDateFromStringOrThrow( + canvasQuiz.due_at, + "comparing canvas due date for day" + ) + ); + if (localDueDate !== canvasDueDate) { + return { + status: "incomplete", + message: ( +
+ due date different +
{localDueDate}
+
{canvasDueDate}
+
+ ), + }; + } + + } else if (type === "assignment") { + const assignment = item as LocalAssignment; + const canvasAssignment = canvasItem as CanvasAssignment; + + if (!canvasAssignment.due_at) + return { status: "incomplete", message: "due date not in canvas" }; + + if (assignment.lockAt && !canvasAssignment.lock_at) + return { status: "incomplete", message: "lock date not in canvas" }; + + const localDueDate = dateToMarkdownString( + getDateFromStringOrThrow(assignment.dueAt, "comparing due dates for day") + ); + const canvasDueDate = dateToMarkdownString( + getDateFromStringOrThrow( + canvasAssignment.due_at, + "comparing canvas due date for day" + ) + ); + if (localDueDate !== canvasDueDate) { + return { + status: "incomplete", + message: ( +
+ due date different +
{localDueDate}
+
{canvasDueDate}
+
+ ), + }; + } + } + + return { status: "published", message: "" }; +}; diff --git a/nextjs/src/app/course/[courseName]/calendar/day/useTodaysItems.tsx b/nextjs/src/app/course/[courseName]/calendar/day/useTodaysItems.tsx new file mode 100644 index 0000000..614886a --- /dev/null +++ b/nextjs/src/app/course/[courseName]/calendar/day/useTodaysItems.tsx @@ -0,0 +1,96 @@ +"use client"; +import { useCanvasAssignmentsQuery } from "@/hooks/canvas/canvasAssignmentHooks"; +import { useCanvasPagesQuery } from "@/hooks/canvas/canvasPageHooks"; +import { useCanvasQuizzesQuery } from "@/hooks/canvas/canvasQuizHooks"; +import { LocalAssignment } from "@/models/local/assignment/localAssignment"; +import { LocalCoursePage } from "@/models/local/page/localCoursePage"; +import { LocalQuiz } from "@/models/local/quiz/localQuiz"; +import { + getDateFromStringOrThrow, + getDateOnlyMarkdownString, +} from "@/models/local/timeUtils"; +import { ReactNode } from "react"; +import { useCalendarItemsContext } from "../../context/calendarItemsContext"; +import { getStatus } from "./getStatus"; + +export function useTodaysItems(day: string) { + const dayAsDate = getDateFromStringOrThrow( + day, + "calculating same month in day items" + ); + const itemsContext = useCalendarItemsContext(); + const dateKey = getDateOnlyMarkdownString(dayAsDate); + const todaysModules = itemsContext[dateKey]; + + const { data: canvasAssignments } = useCanvasAssignmentsQuery(); + const { data: canvasQuizzes } = useCanvasQuizzesQuery(); + const { data: canvasPages } = useCanvasPagesQuery(); + const todaysAssignments: { + moduleName: string; + assignment: LocalAssignment; + status: "localOnly" | "incomplete" | "published"; + message: ReactNode; + }[] = todaysModules + ? Object.keys(todaysModules).flatMap((moduleName) => + todaysModules[moduleName].assignments.map((assignment) => { + const canvasAssignment = canvasAssignments.find( + (c) => c.name === assignment.name + ); + return { + moduleName, + assignment, + ...getStatus({ + item: assignment, + canvasItem: canvasAssignment, + type: "assignment", + }), + }; + }) + ) + : []; + + const todaysQuizzes: { + moduleName: string; + quiz: LocalQuiz; + status: "localOnly" | "incomplete" | "published"; + message: ReactNode; + }[] = todaysModules + ? Object.keys(todaysModules).flatMap((moduleName) => + todaysModules[moduleName].quizzes.map((quiz) => { + const canvasQuiz = canvasQuizzes.find((q) => q.title === quiz.name); + return { + moduleName, + quiz, + ...getStatus({ + item: quiz, + canvasItem: canvasQuiz, + type: "quiz", + }), + }; + }) + ) + : []; + + const todaysPages: { + moduleName: string; + page: LocalCoursePage; + status: "localOnly" | "incomplete" | "published"; + message: ReactNode; + }[] = todaysModules + ? Object.keys(todaysModules).flatMap((moduleName) => + todaysModules[moduleName].pages.map((page) => { + const canvasPage = canvasPages.find((p) => p.title === page.name); + return { + moduleName, + page, + ...getStatus({ + item: page, + canvasItem: canvasPage, + type: "page", + }), + }; + }) + ) + : []; + return { todaysAssignments, todaysQuizzes, todaysPages }; +}