mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-26 23:58:31 -06:00
moving v2 to top level
This commit is contained in:
25
src/app/course/[courseName]/settings/AllSettings.tsx
Normal file
25
src/app/course/[courseName]/settings/AllSettings.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
"use client"
|
||||
|
||||
import AssignmentGroupManagement from "./AssignmentGroupManagement";
|
||||
import DaysOfWeekSettings from "./DaysOfWeekSettings";
|
||||
import DefaultDueTime from "./DefaultDueTime";
|
||||
import DefaultFileUploadTypes from "./DefaultFileUploadTypes";
|
||||
import HolidayConfig from "./HolidayConfig";
|
||||
import SettingsHeader from "./SettingsHeader";
|
||||
import StartAndEndDate from "./StartAndEndDate";
|
||||
import SubmissionDefaults from "./SubmissionDefaults";
|
||||
|
||||
export default function AllSettings() {
|
||||
return (
|
||||
<>
|
||||
<SettingsHeader />
|
||||
<DaysOfWeekSettings />
|
||||
<StartAndEndDate />
|
||||
<SubmissionDefaults />
|
||||
<DefaultFileUploadTypes />
|
||||
<DefaultDueTime />
|
||||
<AssignmentGroupManagement />
|
||||
<HolidayConfig />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
useLocalCourseSettingsQuery,
|
||||
useUpdateLocalCourseSettingsMutation,
|
||||
} from "@/hooks/localCourse/localCoursesHooks";
|
||||
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
|
||||
import { useEffect, useState } from "react";
|
||||
import TextInput from "../../../../components/form/TextInput";
|
||||
import { useSetAssignmentGroupsMutation } from "@/hooks/canvas/canvasCourseHooks";
|
||||
import { settingsBox } from "./sharedSettings";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
|
||||
export default function AssignmentGroupManagement() {
|
||||
const [settings, { isPending }] = useLocalCourseSettingsQuery();
|
||||
const updateSettings = useUpdateLocalCourseSettingsMutation();
|
||||
const applyInCanvas = useSetAssignmentGroupsMutation(settings.canvasId);
|
||||
|
||||
const [assignmentGroups, setAssignmentGroups] = useState<
|
||||
LocalAssignmentGroup[]
|
||||
>(settings.assignmentGroups);
|
||||
|
||||
useEffect(() => {
|
||||
const delay = 1000;
|
||||
const handler = setTimeout(() => {
|
||||
if (
|
||||
!areAssignmentGroupsEqual(assignmentGroups, settings.assignmentGroups)
|
||||
) {
|
||||
console.log(
|
||||
"updating",
|
||||
assignmentGroups,
|
||||
updateSettings.isPending,
|
||||
isPending
|
||||
);
|
||||
updateSettings.mutate({
|
||||
settings: {
|
||||
...settings,
|
||||
assignmentGroups,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [assignmentGroups, isPending, settings, updateSettings]);
|
||||
|
||||
return (
|
||||
<div className={settingsBox}>
|
||||
{assignmentGroups.map((group) => (
|
||||
<div key={group.id} className="flex flex-row gap-3">
|
||||
<TextInput
|
||||
value={group.name}
|
||||
setValue={(newValue) =>
|
||||
setAssignmentGroups((oldGroups) =>
|
||||
oldGroups.map((g) =>
|
||||
g.id === group.id ? { ...g, name: newValue } : g
|
||||
)
|
||||
)
|
||||
}
|
||||
label={"Group Name"}
|
||||
/>
|
||||
<TextInput
|
||||
value={group.weight.toString()}
|
||||
setValue={(newValue) =>
|
||||
setAssignmentGroups((oldGroups) =>
|
||||
oldGroups.map((g) =>
|
||||
g.id === group.id
|
||||
? { ...g, weight: parseInt(newValue || "0") }
|
||||
: g
|
||||
)
|
||||
)
|
||||
}
|
||||
label={"Weight"}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-3 mt-3">
|
||||
<button
|
||||
className="btn-danger"
|
||||
onClick={() => {
|
||||
setAssignmentGroups((oldGroups) => oldGroups.slice(0, -1));
|
||||
}}
|
||||
>
|
||||
Remove Assignment Group
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setAssignmentGroups((oldGroups) => [
|
||||
...oldGroups,
|
||||
{
|
||||
id: Date.now().toString(),
|
||||
name: "",
|
||||
weight: 0,
|
||||
},
|
||||
]);
|
||||
}}
|
||||
>
|
||||
Add Assignment Group
|
||||
</button>
|
||||
</div>
|
||||
<br />
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={async () => {
|
||||
const newSettings = await applyInCanvas.mutateAsync(settings);
|
||||
|
||||
// prevent debounce from resetting
|
||||
if (newSettings) setAssignmentGroups(newSettings.assignmentGroups);
|
||||
}}
|
||||
disabled={applyInCanvas.isPending}
|
||||
>
|
||||
Update Assignment Groups In Canvas
|
||||
</button>
|
||||
</div>
|
||||
{applyInCanvas.isPending && <Spinner />}
|
||||
{applyInCanvas.isSuccess && (
|
||||
<div>
|
||||
{
|
||||
"You will need to go to your course assignments page > settings > Assignment Group Weights"
|
||||
}
|
||||
<br />
|
||||
{"and check the 'Weight final grade based on assignment groups' box"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function areAssignmentGroupsEqual(
|
||||
list1: LocalAssignmentGroup[],
|
||||
list2: LocalAssignmentGroup[]
|
||||
): boolean {
|
||||
// Check if lists have the same length
|
||||
if (list1.length !== list2.length) return false;
|
||||
|
||||
// Sort both lists by the unique 'id' or 'canvasId' as a fallback
|
||||
const sortedList1 = [...list1].sort((a, b) => {
|
||||
if (a.id !== b.id) return a.id > b.id ? 1 : -1;
|
||||
if (a.canvasId !== b.canvasId) return (a.canvasId || 0) - (b.canvasId || 0);
|
||||
return 0;
|
||||
});
|
||||
|
||||
const sortedList2 = [...list2].sort((a, b) => {
|
||||
if (a.id !== b.id) return a.id > b.id ? 1 : -1;
|
||||
if (a.canvasId !== b.canvasId) return (a.canvasId || 0) - (b.canvasId || 0);
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Deep compare each object in the sorted lists
|
||||
for (let i = 0; i < sortedList1.length; i++) {
|
||||
const group1 = sortedList1[i];
|
||||
const group2 = sortedList2[i];
|
||||
|
||||
if (
|
||||
group1.id !== group2.id ||
|
||||
group1.name !== group2.name ||
|
||||
group1.weight !== group2.weight ||
|
||||
group1.canvasId !== group2.canvasId
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
34
src/app/course/[courseName]/settings/DaysOfWeekSettings.tsx
Normal file
34
src/app/course/[courseName]/settings/DaysOfWeekSettings.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
import { DayOfWeekInput } from "@/components/form/DayOfWeekInput";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
import {
|
||||
useLocalCourseSettingsQuery,
|
||||
useUpdateLocalCourseSettingsMutation,
|
||||
} from "@/hooks/localCourse/localCoursesHooks";
|
||||
import React from "react";
|
||||
|
||||
export default function DaysOfWeekSettings() {
|
||||
const [settings] = useLocalCourseSettingsQuery();
|
||||
const updateSettings = useUpdateLocalCourseSettingsMutation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DayOfWeekInput
|
||||
selectedDays={settings.daysOfWeek}
|
||||
updateSettings={(day) => {
|
||||
const hasDay = settings.daysOfWeek.includes(day);
|
||||
|
||||
updateSettings.mutate({
|
||||
settings: {
|
||||
...settings,
|
||||
daysOfWeek: hasDay
|
||||
? settings.daysOfWeek.filter((d) => d !== day)
|
||||
: [day, ...settings.daysOfWeek],
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{updateSettings.isPending && <Spinner />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
71
src/app/course/[courseName]/settings/DefaultDueTime.tsx
Normal file
71
src/app/course/[courseName]/settings/DefaultDueTime.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
useLocalCourseSettingsQuery,
|
||||
useUpdateLocalCourseSettingsMutation,
|
||||
} from "@/hooks/localCourse/localCoursesHooks";
|
||||
import { TimePicker } from "../../../../components/TimePicker";
|
||||
import { useState } from "react";
|
||||
import DefaultLockOffset from "./DefaultLockOffset";
|
||||
import { settingsBox } from "./sharedSettings";
|
||||
|
||||
export default function DefaultDueTime() {
|
||||
const [settings] = useLocalCourseSettingsQuery();
|
||||
const updateSettings = useUpdateLocalCourseSettingsMutation();
|
||||
const [haveLockOffset, setHaveLockOffset] = useState(
|
||||
typeof settings.defaultLockHoursOffset !== "undefined"
|
||||
);
|
||||
return (
|
||||
<div className={settingsBox}>
|
||||
<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: {
|
||||
...settings,
|
||||
defaultDueTime: simpleTime,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<hr />
|
||||
{!haveLockOffset && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
await updateSettings.mutateAsync({
|
||||
settings: {
|
||||
...settings,
|
||||
defaultLockHoursOffset: 0,
|
||||
},
|
||||
});
|
||||
setHaveLockOffset(true);
|
||||
}}
|
||||
>
|
||||
have a default Lock Offset?
|
||||
</button>
|
||||
)}
|
||||
|
||||
{haveLockOffset && <DefaultLockOffset />}
|
||||
<br />
|
||||
{haveLockOffset && (
|
||||
<button
|
||||
className="btn-danger"
|
||||
onClick={async () => {
|
||||
await updateSettings.mutateAsync({
|
||||
settings: {
|
||||
...settings,
|
||||
defaultLockHoursOffset: undefined,
|
||||
},
|
||||
});
|
||||
setHaveLockOffset(false);
|
||||
}}
|
||||
>
|
||||
remove default Lock Offset?
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
import TextInput from "@/components/form/TextInput";
|
||||
import {
|
||||
useLocalCourseSettingsQuery,
|
||||
useUpdateLocalCourseSettingsMutation,
|
||||
} from "@/hooks/localCourse/localCoursesHooks";
|
||||
import { useState, useEffect } from "react";
|
||||
import { settingsBox } from "./sharedSettings";
|
||||
|
||||
export default function DefaultFileUploadTypes() {
|
||||
const [settings] = useLocalCourseSettingsQuery();
|
||||
const [defaultFileUploadTypes, setDefaultFileUploadTypes] = useState<
|
||||
string[]
|
||||
>(settings.defaultFileUploadTypes);
|
||||
const updateSettings = useUpdateLocalCourseSettingsMutation();
|
||||
|
||||
useEffect(() => {
|
||||
const id = setTimeout(() => {
|
||||
if (
|
||||
JSON.stringify(settings.defaultFileUploadTypes) !==
|
||||
JSON.stringify(defaultFileUploadTypes)
|
||||
) {
|
||||
updateSettings.mutate({
|
||||
settings: {
|
||||
...settings,
|
||||
defaultFileUploadTypes: defaultFileUploadTypes,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
return () => clearTimeout(id);
|
||||
}, [defaultFileUploadTypes, settings, updateSettings]);
|
||||
|
||||
return (
|
||||
<div className={settingsBox}>
|
||||
<div className="text-center">Default File Upload Types</div>
|
||||
|
||||
{defaultFileUploadTypes.map((type, index) => (
|
||||
<div key={index} className="flex flex-row gap-3">
|
||||
<TextInput
|
||||
value={type}
|
||||
setValue={(newValue) =>
|
||||
setDefaultFileUploadTypes((oldTypes) =>
|
||||
oldTypes.map((t, i) => (i === index ? newValue : t))
|
||||
)
|
||||
}
|
||||
label={"Default Type " + index}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-3 mt-3">
|
||||
<button
|
||||
className="btn-danger"
|
||||
onClick={() => {
|
||||
setDefaultFileUploadTypes((old) => old.slice(0, -1));
|
||||
}}
|
||||
>
|
||||
Remove Default File Upload Type
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDefaultFileUploadTypes((old) => [...old, ""])}
|
||||
>
|
||||
Add Default File Upload Type
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
src/app/course/[courseName]/settings/DefaultLockOffset.tsx
Normal file
49
src/app/course/[courseName]/settings/DefaultLockOffset.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import TextInput from "@/components/form/TextInput";
|
||||
import {
|
||||
useLocalCourseSettingsQuery,
|
||||
useUpdateLocalCourseSettingsMutation,
|
||||
} from "@/hooks/localCourse/localCoursesHooks";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function DefaultLockOffset() {
|
||||
const [settings] = useLocalCourseSettingsQuery();
|
||||
const updateSettings = useUpdateLocalCourseSettingsMutation();
|
||||
const [hoursOffset, setHoursOffset] = useState(
|
||||
settings.defaultLockHoursOffset?.toString() ?? "0"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const id = setTimeout(() => {
|
||||
try {
|
||||
const hoursNumber = parseInt(hoursOffset);
|
||||
if (
|
||||
!Number.isNaN(hoursNumber) &&
|
||||
hoursNumber !== settings.defaultLockHoursOffset
|
||||
) {
|
||||
updateSettings.mutate({
|
||||
settings: {
|
||||
...settings,
|
||||
defaultLockHoursOffset: hoursNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch {}
|
||||
}, 500);
|
||||
return () => clearTimeout(id);
|
||||
}, [hoursOffset, settings, settings.defaultLockHoursOffset, updateSettings]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="text-center">Default Assignment Due Time</div>
|
||||
<hr className="m-1 p-0" />
|
||||
|
||||
<TextInput
|
||||
value={hoursOffset}
|
||||
setValue={(n) => setHoursOffset(n)}
|
||||
label={"Hours Offset"}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
160
src/app/course/[courseName]/settings/HolidayConfig.tsx
Normal file
160
src/app/course/[courseName]/settings/HolidayConfig.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
"use client";
|
||||
|
||||
import TextInput from "@/components/form/TextInput";
|
||||
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
|
||||
import {
|
||||
useLocalCourseSettingsQuery,
|
||||
useUpdateLocalCourseSettingsMutation,
|
||||
} from "@/hooks/localCourse/localCoursesHooks";
|
||||
import { getDateFromString } from "@/models/local/utils/timeUtils";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
holidaysToString,
|
||||
parseHolidays,
|
||||
} from "../../../../models/local/utils/settingsUtils";
|
||||
import { settingsBox } from "./sharedSettings";
|
||||
|
||||
const exampleString = `springBreak:
|
||||
- 10/12/2024
|
||||
- 10/13/2024
|
||||
- 10/14/2024
|
||||
laborDay:
|
||||
- 9/1/2024`;
|
||||
|
||||
export const holidaysAreEqual = (
|
||||
holidays1: {
|
||||
name: string;
|
||||
days: string[];
|
||||
}[],
|
||||
holidays2: {
|
||||
name: string;
|
||||
days: string[];
|
||||
}[]
|
||||
): boolean => {
|
||||
if (holidays1.length !== holidays2.length) return false;
|
||||
|
||||
const sortedObj1 = [...holidays1].sort((a, b) => a.name.localeCompare(b.name));
|
||||
const sortedObj2 = [...holidays2].sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
for (let i = 0; i < sortedObj1.length; i++) {
|
||||
const holiday1 = sortedObj1[i];
|
||||
const holiday2 = sortedObj2[i];
|
||||
|
||||
if (holiday1.name !== holiday2.name) return false;
|
||||
|
||||
const sortedDays1 = [...holiday1.days].sort();
|
||||
const sortedDays2 = [...holiday2.days].sort();
|
||||
|
||||
if (sortedDays1.length !== sortedDays2.length) return false;
|
||||
|
||||
for (let j = 0; j < sortedDays1.length; j++) {
|
||||
if (sortedDays1[j] !== sortedDays2[j]) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default function HolidayConfig() {
|
||||
return (
|
||||
<SuspenseAndErrorHandling>
|
||||
<InnerHolidayConfig />
|
||||
</SuspenseAndErrorHandling>
|
||||
);
|
||||
}
|
||||
function InnerHolidayConfig() {
|
||||
const [settings] = useLocalCourseSettingsQuery();
|
||||
const updateSettings = useUpdateLocalCourseSettingsMutation();
|
||||
|
||||
const [rawText, setRawText] = useState(holidaysToString(settings.holidays));
|
||||
|
||||
useEffect(() => {
|
||||
const id = setTimeout(() => {
|
||||
try {
|
||||
const parsed = parseHolidays(rawText);
|
||||
|
||||
if (!holidaysAreEqual(settings.holidays, parsed)) {
|
||||
console.log("different holiday configs", settings.holidays, parsed);
|
||||
|
||||
updateSettings.mutate({
|
||||
settings: {
|
||||
...settings,
|
||||
holidays: parsed,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error: any) {}
|
||||
}, 500);
|
||||
return () => clearTimeout(id);
|
||||
}, [rawText, settings.holidays, settings, updateSettings]);
|
||||
|
||||
return (
|
||||
<div className={settingsBox}>
|
||||
<div className="flex flex-row gap-3">
|
||||
<TextInput
|
||||
value={rawText}
|
||||
setValue={setRawText}
|
||||
label={"Holiday Days"}
|
||||
isTextArea={true}
|
||||
/>
|
||||
<div>
|
||||
Format your holidays like so:
|
||||
<pre>
|
||||
<code>{exampleString}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<SuspenseAndErrorHandling>
|
||||
<ParsedHolidaysDisplay value={rawText} />
|
||||
</SuspenseAndErrorHandling>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ParsedHolidaysDisplay({ value }: { value: string }) {
|
||||
const [parsedHolidays, setParsedHolidays] = useState<
|
||||
{
|
||||
name: string;
|
||||
days: string[];
|
||||
}[]
|
||||
>([]);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const parsed = parseHolidays(value);
|
||||
setParsedHolidays(parsed);
|
||||
setError("");
|
||||
} catch (error: any) {
|
||||
setError(error + "");
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="text-rose-500">{error}</div>
|
||||
{parsedHolidays.map((holiday) => (
|
||||
<div key={holiday.name}>
|
||||
<div>{holiday.name}</div>
|
||||
<div>
|
||||
{holiday.days.map((day) => {
|
||||
const date = getDateFromString(day);
|
||||
return (
|
||||
<div key={day}>
|
||||
{date?.toLocaleDateString("en-us", {
|
||||
weekday: "short",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
src/app/course/[courseName]/settings/SettingsHeader.tsx
Normal file
28
src/app/course/[courseName]/settings/SettingsHeader.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
"use client";
|
||||
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
|
||||
import { getCourseUrl } from "@/services/urlUtils";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
import { useCourseContext } from "../context/courseContext";
|
||||
|
||||
export default function SettingsHeader() {
|
||||
const { courseName } = useCourseContext();
|
||||
const [settings] = useLocalCourseSettingsQuery();
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row justify-between">
|
||||
<div className="my-auto">
|
||||
<Link className="btn" href={getCourseUrl(courseName)}>
|
||||
Back To Course
|
||||
</Link>
|
||||
</div>
|
||||
<h3 className="text-center mb-3">
|
||||
{settings.name}{" "}
|
||||
<span className="text-slate-500 text-xl"> settings</span>
|
||||
</h3>
|
||||
<div></div>
|
||||
</div>
|
||||
<hr />
|
||||
</>
|
||||
);
|
||||
}
|
||||
17
src/app/course/[courseName]/settings/StartAndEndDate.tsx
Normal file
17
src/app/course/[courseName]/settings/StartAndEndDate.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
|
||||
import { getDateOnlyMarkdownString } from "@/models/local/utils/timeUtils";
|
||||
import React from "react";
|
||||
import { settingsBox } from "./sharedSettings";
|
||||
|
||||
export default function StartAndEndDate() {
|
||||
const [settings] = useLocalCourseSettingsQuery();
|
||||
const startDate = new Date(settings.startDate);
|
||||
const endDate = new Date(settings.endDate);
|
||||
return (
|
||||
<div className={settingsBox}>
|
||||
<div>Semester Start: {getDateOnlyMarkdownString(startDate)}</div>
|
||||
<div>Semester End: {getDateOnlyMarkdownString(endDate)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
77
src/app/course/[courseName]/settings/SubmissionDefaults.tsx
Normal file
77
src/app/course/[courseName]/settings/SubmissionDefaults.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
"use client";
|
||||
import SelectInput from "@/components/form/SelectInput";
|
||||
import {
|
||||
useLocalCourseSettingsQuery,
|
||||
useUpdateLocalCourseSettingsMutation,
|
||||
} from "@/hooks/localCourse/localCoursesHooks";
|
||||
import {
|
||||
AssignmentSubmissionType,
|
||||
AssignmentSubmissionTypeList,
|
||||
} from "@/models/local/assignment/assignmentSubmissionType";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { settingsBox } from "./sharedSettings";
|
||||
|
||||
export default function SubmissionDefaults() {
|
||||
const [settings] = useLocalCourseSettingsQuery();
|
||||
const [defaultSubmissionTypes, setDefaultSubmissionTypes] = useState<
|
||||
AssignmentSubmissionType[]
|
||||
>(settings.defaultAssignmentSubmissionTypes);
|
||||
const updateSettings = useUpdateLocalCourseSettingsMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
JSON.stringify(settings.defaultAssignmentSubmissionTypes) !==
|
||||
JSON.stringify(defaultSubmissionTypes)
|
||||
) {
|
||||
updateSettings.mutate({
|
||||
settings: {
|
||||
...settings,
|
||||
defaultAssignmentSubmissionTypes: defaultSubmissionTypes,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [defaultSubmissionTypes, settings, updateSettings]);
|
||||
|
||||
return (
|
||||
<div className={settingsBox}>
|
||||
<div className="text-center">Default Assignment Submission Type</div>
|
||||
|
||||
{defaultSubmissionTypes.map((type, index) => (
|
||||
<div key={index} className="flex flex-row gap-3">
|
||||
<SelectInput
|
||||
value={type}
|
||||
setValue={(newType) => {
|
||||
if (newType)
|
||||
setDefaultSubmissionTypes((oldTypes) =>
|
||||
oldTypes.map((t, i) => (i === index ? newType : t))
|
||||
);
|
||||
}}
|
||||
label={""}
|
||||
options={AssignmentSubmissionTypeList}
|
||||
getOptionName={(t) => t}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-3 mt-3">
|
||||
<button
|
||||
className="btn-danger"
|
||||
onClick={() => {
|
||||
setDefaultSubmissionTypes((old) => old.slice(0, -1));
|
||||
}}
|
||||
>
|
||||
Remove Default Type
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
setDefaultSubmissionTypes((old) => [
|
||||
...old,
|
||||
AssignmentSubmissionType.NONE,
|
||||
])
|
||||
}
|
||||
>
|
||||
Add Default Type
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
14
src/app/course/[courseName]/settings/page.tsx
Normal file
14
src/app/course/[courseName]/settings/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import AllSettings from "./AllSettings";
|
||||
|
||||
export default function page() {
|
||||
return (
|
||||
<div className="flex justify-center h-full overflow-auto pt-5 ">
|
||||
<div className=" w-fit ">
|
||||
<AllSettings />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
src/app/course/[courseName]/settings/sharedSettings.ts
Normal file
1
src/app/course/[courseName]/settings/sharedSettings.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const settingsBox = "border w-full p-3 m-3 rounded-md border-slate-500"
|
||||
Reference in New Issue
Block a user