diff --git a/nextjs/src/app/api/courses/[courseName]/settings/route.ts b/nextjs/src/app/api/courses/[courseName]/settings/route.ts
index 82a3fcf..a153ddb 100644
--- a/nextjs/src/app/api/courses/[courseName]/settings/route.ts
+++ b/nextjs/src/app/api/courses/[courseName]/settings/route.ts
@@ -12,3 +12,14 @@ export const GET = async (
const settings = await fileStorageService.getCourseSettings(courseName);
return Response.json(settings);
});
+export const PUT = async (
+ request: Request,
+ { params: { courseName } }: { params: { courseName: string } }
+) =>
+ await withErrorHandling(async () => {
+ const settings = await request.json();
+
+ await fileStorageService.updateCourseSettings(courseName, settings);
+
+ return Response.json({});
+ });
diff --git a/nextjs/src/app/course/[courseName]/settings/DefaultDueTime.tsx b/nextjs/src/app/course/[courseName]/settings/DefaultDueTime.tsx
new file mode 100644
index 0000000..8123cfc
--- /dev/null
+++ b/nextjs/src/app/course/[courseName]/settings/DefaultDueTime.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import {
+ useLocalCourseSettingsQuery,
+ useUpdateLocalCourseSettingsMutation,
+} from "@/hooks/localCourse/localCoursesHooks";
+import { TimePicker } from "../../../../components/TimePicker";
+
+export default function DefaultDueTime() {
+ const { data: settings } = useLocalCourseSettingsQuery();
+ const updateSettings = useUpdateLocalCourseSettingsMutation();
+ return (
+
+
Default Assignment Due Time
+
+
{
+ console.log(simpleTime);
+ updateSettings.mutate({
+ ...settings,
+ defaultDueTime: simpleTime,
+ });
+ }}
+ />
+
+ );
+}
diff --git a/nextjs/src/app/course/[courseName]/settings/SettingsHeader.tsx b/nextjs/src/app/course/[courseName]/settings/SettingsHeader.tsx
new file mode 100644
index 0000000..a3304aa
--- /dev/null
+++ b/nextjs/src/app/course/[courseName]/settings/SettingsHeader.tsx
@@ -0,0 +1,8 @@
+"use client"
+import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
+import React from "react";
+
+export default function SettingsHeader() {
+ const { data: settings } = useLocalCourseSettingsQuery();
+ return Settings for {settings.name}
;
+}
diff --git a/nextjs/src/app/course/[courseName]/settings/StartAndEndDate.tsx b/nextjs/src/app/course/[courseName]/settings/StartAndEndDate.tsx
new file mode 100644
index 0000000..d97a75c
--- /dev/null
+++ b/nextjs/src/app/course/[courseName]/settings/StartAndEndDate.tsx
@@ -0,0 +1,16 @@
+"use client";
+import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
+import { getDateOnlyMarkdownString } from "@/models/local/timeUtils";
+import React from "react";
+
+export default function StartAndEndDate() {
+ const { data: settings } = useLocalCourseSettingsQuery();
+ const startDate = new Date(settings.startDate);
+ const endDate = new Date(settings.endDate);
+ return (
+
+
Start: {getDateOnlyMarkdownString(startDate)}
+
End: {getDateOnlyMarkdownString(endDate)}
+
+ );
+}
diff --git a/nextjs/src/app/course/[courseName]/settings/page.tsx b/nextjs/src/app/course/[courseName]/settings/page.tsx
index cb2be10..db73499 100644
--- a/nextjs/src/app/course/[courseName]/settings/page.tsx
+++ b/nextjs/src/app/course/[courseName]/settings/page.tsx
@@ -1,7 +1,15 @@
-import React from 'react'
+import React from "react";
+import { useCourseContext } from "../context/courseContext";
+import StartAndEndDate from "./StartAndEndDate";
+import SettingsHeader from "./SettingsHeader";
+import DefaultDueTime from "./DefaultDueTime";
export default function page() {
return (
- page
- )
+
+
+
+
+
+ );
}
diff --git a/nextjs/src/app/globals.css b/nextjs/src/app/globals.css
index 2d0c127..ee74d87 100644
--- a/nextjs/src/app/globals.css
+++ b/nextjs/src/app/globals.css
@@ -59,7 +59,7 @@ ol {
padding-left: 1.5rem;
}
hr {
- @apply border-t border-gray-200 my-4;
+ @apply border-t border-gray-500 my-4;
}
blockquote {
@@ -76,3 +76,9 @@ p {
button {
@apply bg-blue-900 hover:bg-blue-700 text-blue-50 font-bold py-1 px-3 rounded transition-all duration-200;
}
+
+select {
+ @apply block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm sm:text-sm;
+ @apply focus:outline-none focus:ring-blue-500 focus:border-blue-500 ;
+ @apply bg-slate-800;
+}
diff --git a/nextjs/src/components/TimePicker.tsx b/nextjs/src/components/TimePicker.tsx
new file mode 100644
index 0000000..be164f4
--- /dev/null
+++ b/nextjs/src/components/TimePicker.tsx
@@ -0,0 +1,99 @@
+"use client";
+import { SimpleTimeOnly } from "@/models/local/localCourse";
+import { FC, useState, useEffect } from "react";
+
+export const TimePicker: FC<{
+ setChosenTime: (simpleTime: SimpleTimeOnly) => void;
+ time: SimpleTimeOnly;
+}> = ({ setChosenTime, time }) => {
+ const adjustedHour = time.hour % 12 === 0 ? 12 : time.hour % 12;
+ const partOfDay = time.hour < 12 ? "AM" : "PM";
+
+ // useEffect(() => {
+ // const newtime = {
+ // hour: partOfDay === "PM" ? hour + 12 : hour,
+ // minute: minute,
+ // };
+ // if (
+ // newtime.hour != startingTime.hour ||
+ // newtime.minute != startingTime.minute
+ // ) {
+ // setChosenTime(newtime);
+ // }
+ // }, [
+ // hour,
+ // minute,
+ // partOfDay,
+ // setChosenTime,
+ // startingTime.hour,
+ // startingTime.minute,
+ // ]);
+
+ return (
+ <>
+
+ >
+ );
+};
diff --git a/nextjs/src/hooks/localCourse/localCoursesHooks.ts b/nextjs/src/hooks/localCourse/localCoursesHooks.ts
index fbbbd14..87e7049 100644
--- a/nextjs/src/hooks/localCourse/localCoursesHooks.ts
+++ b/nextjs/src/hooks/localCourse/localCoursesHooks.ts
@@ -1,7 +1,9 @@
"use client";
import { LocalCourseSettings } from "@/models/local/localCourse";
import {
+ useMutation,
useQueries,
+ useQueryClient,
useSuspenseQueries,
useSuspenseQuery,
} from "@tanstack/react-query";
@@ -14,9 +16,18 @@ import {
useAssignmentNamesQuery,
useAssignmentsQueries,
} from "./assignmentHooks";
-import { getPageNamesQueryConfig, getPageQueryConfig, usePageNamesQuery, usePagesQueries } from "./pageHooks";
-import { getQuizNamesQueryConfig, getQuizQueryConfig, useQuizNamesQuery, useQuizzesQueries } from "./quizHooks";
-import { useMemo } from "react";
+import {
+ getPageNamesQueryConfig,
+ getPageQueryConfig,
+ usePageNamesQuery,
+ usePagesQueries,
+} from "./pageHooks";
+import {
+ getQuizNamesQueryConfig,
+ getQuizQueryConfig,
+ useQuizNamesQuery,
+ useQuizzesQueries,
+} from "./quizHooks";
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
export const useLocalCourseNamesQuery = () =>
@@ -41,6 +52,22 @@ export const useLocalCourseSettingsQuery = () => {
});
};
+export const useUpdateLocalCourseSettingsMutation = () => {
+ const { courseName } = useCourseContext();
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: async (updatedSettings: LocalCourseSettings) => {
+ const url = `/api/courses/${courseName}/settings`;
+ await axiosClient.put(url, updatedSettings);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: localCourseKeys.settings(courseName),
+ });
+ },
+ });
+};
+
export const useModuleNamesQuery = () => {
const { courseName } = useCourseContext();
return useSuspenseQuery({
@@ -116,7 +143,7 @@ export const useAllCourseDataQuery = () => {
}),
});
- const {data: quizNamesAndModules } = useSuspenseQueries({
+ const { data: quizNamesAndModules } = useSuspenseQueries({
queries: moduleNames.map((moduleName) =>
getQuizNamesQueryConfig(courseName, moduleName)
),
@@ -132,9 +159,8 @@ export const useAllCourseDataQuery = () => {
});
const { data: quizzesAndModules } = useSuspenseQueries({
- queries: quizNamesAndModules.map(
- ({ moduleName, quizName }, i) =>
- getQuizQueryConfig(courseName, moduleName, quizName)
+ queries: quizNamesAndModules.map(({ moduleName, quizName }, i) =>
+ getQuizQueryConfig(courseName, moduleName, quizName)
),
combine: (results) => ({
data: results.flatMap((r, i) => ({
@@ -145,7 +171,7 @@ export const useAllCourseDataQuery = () => {
}),
});
- const {data: pageNamesAndModules } = useSuspenseQueries({
+ const { data: pageNamesAndModules } = useSuspenseQueries({
queries: moduleNames.map((moduleName) =>
getPageNamesQueryConfig(courseName, moduleName)
),
@@ -161,9 +187,8 @@ export const useAllCourseDataQuery = () => {
});
const { data: pagesAndModules } = useSuspenseQueries({
- queries: pageNamesAndModules.map(
- ({ moduleName, pageName }, i) =>
- getPageQueryConfig(courseName, moduleName, pageName)
+ queries: pageNamesAndModules.map(({ moduleName, pageName }, i) =>
+ getPageQueryConfig(courseName, moduleName, pageName)
),
combine: (results) => ({
data: results.flatMap((r, i) => ({
@@ -174,8 +199,7 @@ export const useAllCourseDataQuery = () => {
}),
});
-
- return { assignmentsAndModules, quizzesAndModules, pagesAndModules };
+ return { assignmentsAndModules, quizzesAndModules, pagesAndModules };
};
// export const useUpdateCourseMutation = (courseName: string) => {
diff --git a/nextjs/src/models/local/localCourse.ts b/nextjs/src/models/local/localCourse.ts
index b3e7cc4..6e47b71 100644
--- a/nextjs/src/models/local/localCourse.ts
+++ b/nextjs/src/models/local/localCourse.ts
@@ -34,26 +34,24 @@ export enum DayOfWeek {
export const localCourseYamlUtils = {
parseSettingYaml: (settingsString: string): LocalCourseSettings => {
const settings = parse(settingsString);
- return lowercaseFirstLetter(settings)
+ return lowercaseFirstLetter(settings);
},
- settingsToYaml: (settings: LocalCourseSettings) => {
+ settingsToYaml: (settings: Omit) => {
return stringify(settings);
},
};
function lowercaseFirstLetter(obj: T): T {
- if (obj === null || typeof obj !== 'object')
- return obj as T;
+ if (obj === null || typeof obj !== "object") return obj as T;
- if (Array.isArray(obj))
- return obj.map(lowercaseFirstLetter) as unknown as T;
+ if (Array.isArray(obj)) return obj.map(lowercaseFirstLetter) as unknown as T;
const result: Record = {};
Object.keys(obj).forEach((key) => {
const value = (obj as Record)[key];
const newKey = key.charAt(0).toLowerCase() + key.slice(1);
- if (value && typeof value === 'object') {
+ if (value && typeof value === "object") {
result[newKey] = lowercaseFirstLetter(value);
} else {
result[newKey] = value;
@@ -61,4 +59,4 @@ function lowercaseFirstLetter(obj: T): T {
});
return result as T;
-}
\ No newline at end of file
+}
diff --git a/nextjs/src/services/fileStorage/fileStorageService.ts b/nextjs/src/services/fileStorage/fileStorageService.ts
index 1c4df66..9ad5f7e 100644
--- a/nextjs/src/services/fileStorage/fileStorageService.ts
+++ b/nextjs/src/services/fileStorage/fileStorageService.ts
@@ -75,6 +75,21 @@ export const fileStorageService = {
return { ...settings, name: folderName };
},
+ async updateCourseSettings(
+ courseName: string,
+ settings: LocalCourseSettings
+ ) {
+ const courseDirectory = path.join(basePath, courseName);
+ const settingsPath = path.join(courseDirectory, "settings.yml");
+
+ const { name, ...settingsWithoutName } = settings;
+ const settingsMarkdown =
+ localCourseYamlUtils.settingsToYaml(settingsWithoutName);
+
+ console.log(`Saving settings ${settingsPath}`);
+ await fs.writeFile(settingsPath, settingsMarkdown);
+ },
+
async getModuleNames(courseName: string) {
const courseDirectory = path.join(basePath, courseName);
const moduleDirectories = await fs.readdir(courseDirectory, {