mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-26 07:38:33 -06:00
updating settings
This commit is contained in:
@@ -12,3 +12,14 @@ export const GET = async (
|
|||||||
const settings = await fileStorageService.getCourseSettings(courseName);
|
const settings = await fileStorageService.getCourseSettings(courseName);
|
||||||
return Response.json(settings);
|
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({});
|
||||||
|
});
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<div className="border w-fit p-3 m-3 rounded-md">
|
||||||
|
<div className="text-center">Default Assignment Due Time</div>
|
||||||
|
<hr className="m-1 p-0" />
|
||||||
|
<TimePicker
|
||||||
|
time={settings.defaultDueTime}
|
||||||
|
setChosenTime={(simpleTime) => {
|
||||||
|
console.log(simpleTime);
|
||||||
|
updateSettings.mutate({
|
||||||
|
...settings,
|
||||||
|
defaultDueTime: simpleTime,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 <div>Settings for {settings.name}</div>;
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div>
|
||||||
|
<div>Start: {getDateOnlyMarkdownString(startDate)}</div>
|
||||||
|
<div>End: {getDateOnlyMarkdownString(endDate)}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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() {
|
export default function page() {
|
||||||
return (
|
return (
|
||||||
<div>page</div>
|
<div>
|
||||||
)
|
<SettingsHeader />
|
||||||
|
<StartAndEndDate />
|
||||||
|
<DefaultDueTime />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ ol {
|
|||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
}
|
}
|
||||||
hr {
|
hr {
|
||||||
@apply border-t border-gray-200 my-4;
|
@apply border-t border-gray-500 my-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
@@ -76,3 +76,9 @@ p {
|
|||||||
button {
|
button {
|
||||||
@apply bg-blue-900 hover:bg-blue-700 text-blue-50 font-bold py-1 px-3 rounded transition-all duration-200;
|
@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;
|
||||||
|
}
|
||||||
|
|||||||
99
nextjs/src/components/TimePicker.tsx
Normal file
99
nextjs/src/components/TimePicker.tsx
Normal file
@@ -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 (
|
||||||
|
<>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => e.preventDefault()}
|
||||||
|
className="flex flex-row gap-3"
|
||||||
|
>
|
||||||
|
<div className="">
|
||||||
|
<label>
|
||||||
|
Hour
|
||||||
|
<select
|
||||||
|
value={adjustedHour}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newHours = parseInt(e.target.value);
|
||||||
|
setChosenTime({
|
||||||
|
...time,
|
||||||
|
hour: partOfDay === "PM" ? newHours + 12 : newHours,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Array.from({ length: 12 }, (_, i) => i).map((o) => (
|
||||||
|
<option key={o.toString()}>{o}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="">
|
||||||
|
<label>
|
||||||
|
Minute
|
||||||
|
<select
|
||||||
|
value={time.minute}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newMinute = parseInt(e.target.value);
|
||||||
|
setChosenTime({
|
||||||
|
...time,
|
||||||
|
minute: newMinute,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[0, 15, 30, 45, 59].map((o) => (
|
||||||
|
<option key={o.toString()}>{o}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="">
|
||||||
|
<label>
|
||||||
|
Part of Day
|
||||||
|
<select
|
||||||
|
value={partOfDay}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newPartOfDay = e.target.value;
|
||||||
|
|
||||||
|
setChosenTime({
|
||||||
|
...time,
|
||||||
|
hour: newPartOfDay === "PM" ? time.hour + 12 : time.hour,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{["AM", "PM"].map((o) => (
|
||||||
|
<option key={o.toString()}>{o}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { LocalCourseSettings } from "@/models/local/localCourse";
|
import { LocalCourseSettings } from "@/models/local/localCourse";
|
||||||
import {
|
import {
|
||||||
|
useMutation,
|
||||||
useQueries,
|
useQueries,
|
||||||
|
useQueryClient,
|
||||||
useSuspenseQueries,
|
useSuspenseQueries,
|
||||||
useSuspenseQuery,
|
useSuspenseQuery,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
@@ -14,9 +16,18 @@ import {
|
|||||||
useAssignmentNamesQuery,
|
useAssignmentNamesQuery,
|
||||||
useAssignmentsQueries,
|
useAssignmentsQueries,
|
||||||
} from "./assignmentHooks";
|
} from "./assignmentHooks";
|
||||||
import { getPageNamesQueryConfig, getPageQueryConfig, usePageNamesQuery, usePagesQueries } from "./pageHooks";
|
import {
|
||||||
import { getQuizNamesQueryConfig, getQuizQueryConfig, useQuizNamesQuery, useQuizzesQueries } from "./quizHooks";
|
getPageNamesQueryConfig,
|
||||||
import { useMemo } from "react";
|
getPageQueryConfig,
|
||||||
|
usePageNamesQuery,
|
||||||
|
usePagesQueries,
|
||||||
|
} from "./pageHooks";
|
||||||
|
import {
|
||||||
|
getQuizNamesQueryConfig,
|
||||||
|
getQuizQueryConfig,
|
||||||
|
useQuizNamesQuery,
|
||||||
|
useQuizzesQueries,
|
||||||
|
} from "./quizHooks";
|
||||||
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
|
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
|
||||||
|
|
||||||
export const useLocalCourseNamesQuery = () =>
|
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 = () => {
|
export const useModuleNamesQuery = () => {
|
||||||
const { courseName } = useCourseContext();
|
const { courseName } = useCourseContext();
|
||||||
return useSuspenseQuery({
|
return useSuspenseQuery({
|
||||||
@@ -132,8 +159,7 @@ export const useAllCourseDataQuery = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: quizzesAndModules } = useSuspenseQueries({
|
const { data: quizzesAndModules } = useSuspenseQueries({
|
||||||
queries: quizNamesAndModules.map(
|
queries: quizNamesAndModules.map(({ moduleName, quizName }, i) =>
|
||||||
({ moduleName, quizName }, i) =>
|
|
||||||
getQuizQueryConfig(courseName, moduleName, quizName)
|
getQuizQueryConfig(courseName, moduleName, quizName)
|
||||||
),
|
),
|
||||||
combine: (results) => ({
|
combine: (results) => ({
|
||||||
@@ -161,8 +187,7 @@ export const useAllCourseDataQuery = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: pagesAndModules } = useSuspenseQueries({
|
const { data: pagesAndModules } = useSuspenseQueries({
|
||||||
queries: pageNamesAndModules.map(
|
queries: pageNamesAndModules.map(({ moduleName, pageName }, i) =>
|
||||||
({ moduleName, pageName }, i) =>
|
|
||||||
getPageQueryConfig(courseName, moduleName, pageName)
|
getPageQueryConfig(courseName, moduleName, pageName)
|
||||||
),
|
),
|
||||||
combine: (results) => ({
|
combine: (results) => ({
|
||||||
@@ -174,7 +199,6 @@ export const useAllCourseDataQuery = () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return { assignmentsAndModules, quizzesAndModules, pagesAndModules };
|
return { assignmentsAndModules, quizzesAndModules, pagesAndModules };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,26 +34,24 @@ export enum DayOfWeek {
|
|||||||
export const localCourseYamlUtils = {
|
export const localCourseYamlUtils = {
|
||||||
parseSettingYaml: (settingsString: string): LocalCourseSettings => {
|
parseSettingYaml: (settingsString: string): LocalCourseSettings => {
|
||||||
const settings = parse(settingsString);
|
const settings = parse(settingsString);
|
||||||
return lowercaseFirstLetter(settings)
|
return lowercaseFirstLetter(settings);
|
||||||
},
|
},
|
||||||
settingsToYaml: (settings: LocalCourseSettings) => {
|
settingsToYaml: (settings: Omit<LocalCourseSettings, "name">) => {
|
||||||
return stringify(settings);
|
return stringify(settings);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function lowercaseFirstLetter<T>(obj: T): T {
|
function lowercaseFirstLetter<T>(obj: T): T {
|
||||||
if (obj === null || typeof obj !== 'object')
|
if (obj === null || typeof obj !== "object") return obj as T;
|
||||||
return obj as T;
|
|
||||||
|
|
||||||
if (Array.isArray(obj))
|
if (Array.isArray(obj)) return obj.map(lowercaseFirstLetter) as unknown as T;
|
||||||
return obj.map(lowercaseFirstLetter) as unknown as T;
|
|
||||||
|
|
||||||
const result: Record<string, any> = {};
|
const result: Record<string, any> = {};
|
||||||
Object.keys(obj).forEach((key) => {
|
Object.keys(obj).forEach((key) => {
|
||||||
const value = (obj as Record<string, any>)[key];
|
const value = (obj as Record<string, any>)[key];
|
||||||
const newKey = key.charAt(0).toLowerCase() + key.slice(1);
|
const newKey = key.charAt(0).toLowerCase() + key.slice(1);
|
||||||
|
|
||||||
if (value && typeof value === 'object') {
|
if (value && typeof value === "object") {
|
||||||
result[newKey] = lowercaseFirstLetter(value);
|
result[newKey] = lowercaseFirstLetter(value);
|
||||||
} else {
|
} else {
|
||||||
result[newKey] = value;
|
result[newKey] = value;
|
||||||
|
|||||||
@@ -75,6 +75,21 @@ export const fileStorageService = {
|
|||||||
return { ...settings, name: folderName };
|
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) {
|
async getModuleNames(courseName: string) {
|
||||||
const courseDirectory = path.join(basePath, courseName);
|
const courseDirectory = path.join(basePath, courseName);
|
||||||
const moduleDirectories = await fs.readdir(courseDirectory, {
|
const moduleDirectories = await fs.readdir(courseDirectory, {
|
||||||
|
|||||||
Reference in New Issue
Block a user