diff --git a/nextjs/package.json b/nextjs/package.json index 352683f..e83d66f 100644 --- a/nextjs/package.json +++ b/nextjs/package.json @@ -10,20 +10,14 @@ "test": "vitest" }, "dependencies": { + "jsdom": "^25.0.0", "next": "^14.2.7", "react": "^18", - "jsdom": "^25.0.0", "react-dom": "^18" }, "devDependencies": { - "yaml": "^2.5.0", - "axios": "^1.7.5", - "marked": "^14.1.2", "@monaco-editor/react": "^4.6.0", "@tanstack/react-query": "^5.54.1", - "isomorphic-dompurify": "^2.15.0", - "react-error-boundary": "^4.0.13", - "react-hot-toast": "^2.4.1", "@tanstack/react-query-devtools": "^5.54.1", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.0.0", @@ -31,10 +25,16 @@ "@types/react": "^18", "@types/react-dom": "^18", "@vitejs/plugin-react": "^4.3.1", + "axios": "^1.7.5", "eslint-config-next": "^14.2.7", + "isomorphic-dompurify": "^2.15.0", + "marked": "^14.1.2", "postcss": "^8", + "react-error-boundary": "^4.0.13", + "react-hot-toast": "^2.4.1", "tailwindcss": "^3.4.1", "typescript": "^5", - "vitest": "^2.0.5" + "vitest": "^2.0.5", + "yaml": "^2.5.0" } } diff --git a/nextjs/src/app/course/[courseName]/modules/CreateModule.tsx b/nextjs/src/app/course/[courseName]/modules/CreateModule.tsx index 1bcb4ec..f738b1d 100644 --- a/nextjs/src/app/course/[courseName]/modules/CreateModule.tsx +++ b/nextjs/src/app/course/[courseName]/modules/CreateModule.tsx @@ -1,17 +1,20 @@ +import { Expandable } from "@/components/Expandable"; import TextInput from "@/components/form/TextInput"; import { useCreateModuleMutation } from "@/hooks/localCourse/localCourseModuleHooks"; import React, { useState } from "react"; export default function CreateModule() { const createModule = useCreateModuleMutation(); - const [showForm, setShowForm] = useState(false); const [moduleName, setModuleName] = useState(""); return ( <> - -
+ ( + + )} + >
{ e.preventDefault(); @@ -30,7 +33,7 @@ export default function CreateModule() { />
-
+ ); } diff --git a/nextjs/src/app/course/[courseName]/modules/ModuleList.tsx b/nextjs/src/app/course/[courseName]/modules/ModuleList.tsx index 2ec9321..f7b7c15 100644 --- a/nextjs/src/app/course/[courseName]/modules/ModuleList.tsx +++ b/nextjs/src/app/course/[courseName]/modules/ModuleList.tsx @@ -7,10 +7,12 @@ export default function ModuleList() { const { data: moduleNames } = useModuleNamesQuery(); return (
- {moduleNames.map((m) => ( ))} +
+ +



diff --git a/nextjs/src/app/course/[courseName]/settings/HolidayConfig.tsx b/nextjs/src/app/course/[courseName]/settings/HolidayConfig.tsx new file mode 100644 index 0000000..6219f6b --- /dev/null +++ b/nextjs/src/app/course/[courseName]/settings/HolidayConfig.tsx @@ -0,0 +1,85 @@ +"use client"; + +import TextInput from "@/components/form/TextInput"; +import { + useLocalCourseSettingsQuery, + useUpdateLocalCourseSettingsMutation, +} from "@/hooks/localCourse/localCoursesHooks"; +import { + dateToMarkdownString, + getDateFromStringOrThrow, +} from "@/models/local/timeUtils"; +import { useState } from "react"; + +const exampleString = `springBreak: +- 10/12/2024 +- 10/13/2024 +- 10/14/2024 +laborDay: +- 9/1/2024`; + +export default function HolidayConfig() { + const { data: settings } = useLocalCourseSettingsQuery(); + const updateSettings = useUpdateLocalCourseSettingsMutation(); + + const [rawText, setRawText] = useState(""); + + const parsedText = parseHolidays(rawText); + + return ( +
+ +
+ Format your holidays like so: +
+          {exampleString}
+        
+
+
+ {Object.keys(parsedText).map((k) => ( +
+
{k}
+
+ {parsedText[k].map((day) => { + const parsedDate = getDateFromStringOrThrow( + day, + "holiday preview display" + ); + return
{dateToMarkdownString(parsedDate)}
; + })} +
+
+ ))} +
+
+ ); +} + +const parseHolidays = ( + inputText: string +): { [holidayName: string]: string[] } => { + const holidays: { [holidayName: string]: string[] } = {}; + + const lines = inputText.split("\n").filter(line => line.trim() !== ""); + let currentHoliday: string | null = null; + + lines.forEach(line => { + if (line.includes(":")) { + // It's a holiday name + const holidayName = line.split(":")[0].trim(); + currentHoliday = holidayName; + holidays[currentHoliday] = []; + } else if (currentHoliday && line.startsWith("-")) { + // It's a date under the current holiday + const date = line.replace("-", "").trim(); + holidays[currentHoliday].push(date); + } + }); + + return holidays; +}; diff --git a/nextjs/src/app/course/[courseName]/settings/page.tsx b/nextjs/src/app/course/[courseName]/settings/page.tsx index a0c9f63..152cac3 100644 --- a/nextjs/src/app/course/[courseName]/settings/page.tsx +++ b/nextjs/src/app/course/[courseName]/settings/page.tsx @@ -7,6 +7,7 @@ import DaysOfWeekSettings from "./DaysOfWeekSettings"; import AssignmentGroupManagement from "./AssignmentGroupManagement"; import SubmissionDefaults from "./SubmissionDefaults"; import DefaultFileUploadTypes from "./DefaultFileUploadTypes"; +import HolidayConfig from "./HolidayConfig"; export default function page() { return ( @@ -19,6 +20,7 @@ export default function page() { +


diff --git a/nextjs/src/components/form/TextInput.tsx b/nextjs/src/components/form/TextInput.tsx index ad471bd..d961279 100644 --- a/nextjs/src/components/form/TextInput.tsx +++ b/nextjs/src/components/form/TextInput.tsx @@ -5,21 +5,32 @@ export default function TextInput({ setValue, label, className, + isTextArea = false, }: { value: string; setValue: (newValue: string) => void; label: string; className?: string; + isTextArea?: boolean; }) { return ( -