mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-26 15:48:32 -06:00
creating assignments
This commit is contained in:
@@ -28,7 +28,26 @@ export const PUT = async (
|
||||
) =>
|
||||
await withErrorHandling(async () => {
|
||||
const assignment = await request.json();
|
||||
await fileStorageService.assignments.updateAssignment(
|
||||
await fileStorageService.assignments.updateOrCreateAssignment(
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
assignment
|
||||
);
|
||||
return Response.json({});
|
||||
});
|
||||
|
||||
export const POST = async (
|
||||
request: Request,
|
||||
{
|
||||
params: { courseName, moduleName, assignmentName },
|
||||
}: {
|
||||
params: { courseName: string; moduleName: string; assignmentName: string };
|
||||
}
|
||||
) =>
|
||||
await withErrorHandling(async () => {
|
||||
const assignment = await request.json();
|
||||
await fileStorageService.assignments.updateOrCreateAssignment(
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
|
||||
@@ -16,14 +16,27 @@ export const GET = async (
|
||||
return Response.json(settings);
|
||||
});
|
||||
|
||||
export const PUT = async (
|
||||
request: Request,
|
||||
{
|
||||
params: { courseName, moduleName, pageName },
|
||||
}: { params: { courseName: string; moduleName: string; pageName: string } }
|
||||
) =>
|
||||
await withErrorHandling(async () => {
|
||||
const page = await request.json();
|
||||
await fileStorageService.pages.updatePage(courseName, moduleName, pageName, page);
|
||||
return Response.json({});
|
||||
});
|
||||
export const PUT = async (
|
||||
request: Request,
|
||||
{
|
||||
params: { courseName, moduleName, pageName },
|
||||
}: { params: { courseName: string; moduleName: string; pageName: string } }
|
||||
) =>
|
||||
await withErrorHandling(async () => {
|
||||
const page = await request.json();
|
||||
await fileStorageService.pages.updatePage(courseName, moduleName, pageName, page);
|
||||
return Response.json({});
|
||||
});
|
||||
|
||||
export const POST = async (
|
||||
request: Request,
|
||||
{
|
||||
params: { courseName, moduleName, pageName },
|
||||
}: { params: { courseName: string; moduleName: string; pageName: string } }
|
||||
) =>
|
||||
await withErrorHandling(async () => {
|
||||
const page = await request.json();
|
||||
await fileStorageService.pages.updatePage(courseName, moduleName, pageName, page);
|
||||
return Response.json({});
|
||||
});
|
||||
|
||||
@@ -6,27 +6,46 @@ export const GET = async (
|
||||
{
|
||||
params: { courseName, moduleName, quizName },
|
||||
}: { params: { courseName: string; moduleName: string; quizName: string } }
|
||||
) => await withErrorHandling(async () => {
|
||||
const quiz = await fileStorageService.quizzes.getQuiz(
|
||||
courseName,
|
||||
moduleName,
|
||||
quizName
|
||||
);
|
||||
return Response.json(quiz);
|
||||
})
|
||||
) =>
|
||||
await withErrorHandling(async () => {
|
||||
const quiz = await fileStorageService.quizzes.getQuiz(
|
||||
courseName,
|
||||
moduleName,
|
||||
quizName
|
||||
);
|
||||
return Response.json(quiz);
|
||||
});
|
||||
|
||||
export const PUT = async (
|
||||
request: Request,
|
||||
{
|
||||
params: { courseName, moduleName, quizName },
|
||||
}: { params: { courseName: string; moduleName: string; quizName: string } }
|
||||
) => await withErrorHandling(async () => {
|
||||
const quiz = await request.json()
|
||||
await fileStorageService.quizzes.updateQuiz(
|
||||
courseName,
|
||||
moduleName,
|
||||
quizName,
|
||||
quiz
|
||||
);
|
||||
return Response.json({});
|
||||
})
|
||||
) =>
|
||||
await withErrorHandling(async () => {
|
||||
const quiz = await request.json();
|
||||
await fileStorageService.quizzes.updateQuiz(
|
||||
courseName,
|
||||
moduleName,
|
||||
quizName,
|
||||
quiz
|
||||
);
|
||||
return Response.json({});
|
||||
});
|
||||
|
||||
export const POST = async (
|
||||
request: Request,
|
||||
{
|
||||
params: { courseName, moduleName, quizName },
|
||||
}: { params: { courseName: string; moduleName: string; quizName: string } }
|
||||
) =>
|
||||
await withErrorHandling(async () => {
|
||||
const quiz = await request.json();
|
||||
await fileStorageService.quizzes.updateQuiz(
|
||||
courseName,
|
||||
moduleName,
|
||||
quizName,
|
||||
quiz
|
||||
);
|
||||
return Response.json({});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
import { IModuleItem } from "@/models/local/IModuleItem";
|
||||
import { getDateFromStringOrThrow } from "@/models/local/timeUtils";
|
||||
import { useState } from "react";
|
||||
import Modal from "../../../../components/Modal";
|
||||
import NewItemForm from "./NewItemForm";
|
||||
|
||||
export default function ExpandableModule({
|
||||
moduleName,
|
||||
@@ -72,16 +74,21 @@ export default function ExpandableModule({
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
`
|
||||
overflow-hidden
|
||||
` + (expanded ? " max-h-[30vh]" : " max-h-0")
|
||||
// transition-all duration-1000 ease-in
|
||||
` overflow-hidden ` + (expanded ? " max-h-[30vh]" : " max-h-0")
|
||||
}
|
||||
style={{
|
||||
transition: "max-height 1s cubic-bezier(0, 1, 0, 1)",
|
||||
}}
|
||||
>
|
||||
<hr />
|
||||
<Modal buttonText="New Item">
|
||||
{({ closeModal }) => (
|
||||
<div>
|
||||
<NewItemForm moduleName={moduleName} />
|
||||
<br />
|
||||
<button onClick={closeModal}>close</button>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
{moduleItems.map(({ type, item }) => (
|
||||
<div key={item.name}>{item.name}</div>
|
||||
))}
|
||||
|
||||
83
nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx
Normal file
83
nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
import ButtonSelect from "@/components/ButtonSelect";
|
||||
import TextInput from "@/components/form/TextInput";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
import { useCreateAssignmentMutation } from "@/hooks/localCourse/assignmentHooks";
|
||||
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
|
||||
import { useCreatePageMutation } from "@/hooks/localCourse/pageHooks";
|
||||
import { useCreateQuizMutation } from "@/hooks/localCourse/quizHooks";
|
||||
import { AssignmentSubmissionType } from "@/models/local/assignment/assignmentSubmissionType";
|
||||
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
|
||||
import { dateToMarkdownString } from "@/models/local/timeUtils";
|
||||
import React, { useState } from "react";
|
||||
|
||||
export default function NewItemForm({ moduleName }: { moduleName: string }) {
|
||||
const [type, setType] = useState<"Assignment" | "Quiz" | "Page">(
|
||||
"Assignment"
|
||||
);
|
||||
const [name, setName] = useState("");
|
||||
const [assignmentGroup, setAssignmentGroup] =
|
||||
useState<LocalAssignmentGroup>();
|
||||
const { data: settings } = useLocalCourseSettingsQuery();
|
||||
const createAssignment = useCreateAssignmentMutation();
|
||||
const createPage = useCreatePageMutation();
|
||||
const createQuiz = useCreateQuizMutation();
|
||||
|
||||
const isPending =
|
||||
createAssignment.isPending || createPage.isPending || createQuiz.isPending;
|
||||
|
||||
return (
|
||||
<form
|
||||
className="flex flex-col gap-3"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (type === "Assignment") {
|
||||
createAssignment.mutate({
|
||||
assignment: {
|
||||
name,
|
||||
description: "",
|
||||
dueAt: dateToMarkdownString(new Date()),
|
||||
submissionTypes: [
|
||||
AssignmentSubmissionType.ONLINE_TEXT_ENTRY,
|
||||
AssignmentSubmissionType.ONLINE_UPLOAD,
|
||||
],
|
||||
allowedFileUploadExtensions: ["pdf"],
|
||||
rubric: [],
|
||||
},
|
||||
moduleName: moduleName,
|
||||
assignmentName: name,
|
||||
});
|
||||
} else if (type === "Quiz") {
|
||||
} else if (type === "Page") {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<ButtonSelect<"Assignment" | "Quiz" | "Page">
|
||||
options={["Assignment", "Quiz", "Page"]}
|
||||
getName={(o) => o?.toString() ?? ""}
|
||||
setSelectedOption={(t) => setType(t ?? "Assignment")}
|
||||
selectedOption={type}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextInput label={type + " Name"} value={name} setValue={setName} />
|
||||
</div>
|
||||
<div>
|
||||
<ButtonSelect
|
||||
options={settings.assignmentGroups}
|
||||
getName={(g) => g?.name ?? ""}
|
||||
setSelectedOption={setAssignmentGroup}
|
||||
selectedOption={assignmentGroup}
|
||||
/>
|
||||
</div>
|
||||
{settings.assignmentGroups.length === 0 && (
|
||||
<div>
|
||||
No assignment groups created, create them in the course settings page
|
||||
</div>
|
||||
)}
|
||||
<button>Create</button>
|
||||
{isPending && <Spinner />}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -83,6 +83,11 @@ button,
|
||||
@apply bg-red-800 hover:bg-red-900 text-red-100;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
@apply bg-transparent text-blue-200 border border-blue-500;
|
||||
@apply hover:border-blue-500 hover:text-blue-100 hover:bg-blue-900;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
30
nextjs/src/components/ButtonSelect.tsx
Normal file
30
nextjs/src/components/ButtonSelect.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
|
||||
export default function ButtonSelect<T>({
|
||||
options,
|
||||
getName,
|
||||
setSelectedOption,
|
||||
selectedOption,
|
||||
}: {
|
||||
options: T[];
|
||||
getName: (value: T | undefined) => string;
|
||||
setSelectedOption: (value: T | undefined) => void;
|
||||
selectedOption: T | undefined;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-row gap-3 w-min">
|
||||
{options.map((o) => (
|
||||
<button
|
||||
type="button"
|
||||
key={getName(o)}
|
||||
className={
|
||||
getName(o) === getName(selectedOption) ? "" : "btn-outline"
|
||||
}
|
||||
onClick={() => setSelectedOption(o)}
|
||||
>
|
||||
{getName(o)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
44
nextjs/src/components/Modal.tsx
Normal file
44
nextjs/src/components/Modal.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
"use client";
|
||||
import React, { ReactNode, useState } from "react";
|
||||
|
||||
export default function Modal({
|
||||
children,
|
||||
buttonText,
|
||||
buttonClass = "",
|
||||
}: {
|
||||
children: (props: { closeModal: () => void }) => ReactNode;
|
||||
buttonText: string;
|
||||
buttonClass?: string;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const openModal = () => setIsOpen(true);
|
||||
const closeModal = () => setIsOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={openModal} className={buttonClass}>
|
||||
{buttonText}
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={
|
||||
"fixed inset-0 flex items-center justify-center transition-all duration-400 " +
|
||||
" bg-black" +
|
||||
(isOpen ? " bg-opacity-50 z-50 " : " bg-opacity-0 -z-50 ")
|
||||
}
|
||||
onClick={closeModal}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
` bg-slate-800 p-6 rounded-lg shadow-lg w-1/3 ` +
|
||||
` transition-all duration-400 ` +
|
||||
` ${isOpen ? "opacity-100" : "opacity-0"}`
|
||||
}
|
||||
>
|
||||
{isOpen && children({ closeModal })}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export default function TextInput({
|
||||
{label}
|
||||
<br />
|
||||
<input
|
||||
className="bg-slate-800 rounded-md w-full px-1"
|
||||
className="bg-slate-800 border border-slate-500 rounded-md w-full px-1"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -127,3 +127,46 @@ export const useUpdateAssignmentMutation = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const useCreateAssignmentMutation = () => {
|
||||
const { courseName } = useCourseContext();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
assignment,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
}: {
|
||||
assignment: LocalAssignment;
|
||||
moduleName: string;
|
||||
assignmentName: string;
|
||||
}) => {
|
||||
queryClient.setQueryData(
|
||||
localCourseKeys.assignment(courseName, moduleName, assignmentName),
|
||||
assignment
|
||||
);
|
||||
const url =
|
||||
"/api/courses/" +
|
||||
encodeURIComponent(courseName) +
|
||||
"/modules/" +
|
||||
encodeURIComponent(moduleName) +
|
||||
"/assignments/" +
|
||||
encodeURIComponent(assignmentName);
|
||||
await axiosClient.post(url, assignment);
|
||||
},
|
||||
onSuccess: (_, { moduleName, assignmentName }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: localCourseKeys.assignment(
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName
|
||||
),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: localCourseKeys.assignmentNames(courseName, moduleName),
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -116,3 +116,41 @@ export const useUpdatePageMutation = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const useCreatePageMutation = () => {
|
||||
const { courseName } = useCourseContext();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
page,
|
||||
moduleName,
|
||||
pageName,
|
||||
}: {
|
||||
page: LocalCoursePage;
|
||||
moduleName: string;
|
||||
pageName: string;
|
||||
}) => {
|
||||
queryClient.setQueryData(
|
||||
localCourseKeys.page(courseName, moduleName, pageName),
|
||||
page
|
||||
);
|
||||
const url =
|
||||
"/api/courses/" +
|
||||
encodeURIComponent(courseName) +
|
||||
"/modules/" +
|
||||
encodeURIComponent(moduleName) +
|
||||
"/pages/" +
|
||||
encodeURIComponent(pageName);
|
||||
await axiosClient.post(url, page);
|
||||
},
|
||||
onSuccess: (_, { moduleName, pageName }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: localCourseKeys.page(courseName, moduleName, pageName),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: localCourseKeys.pageNames(courseName, moduleName),
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -105,3 +105,41 @@ export const useUpdateQuizMutation = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const useCreateQuizMutation = () => {
|
||||
const { courseName } = useCourseContext();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
quiz,
|
||||
moduleName,
|
||||
quizName,
|
||||
}: {
|
||||
quiz: LocalQuiz;
|
||||
moduleName: string;
|
||||
quizName: string;
|
||||
}) => {
|
||||
queryClient.setQueryData(
|
||||
localCourseKeys.quiz(courseName, moduleName, quizName),
|
||||
quiz
|
||||
);
|
||||
const url =
|
||||
"/api/courses/" +
|
||||
encodeURIComponent(courseName) +
|
||||
"/modules/" +
|
||||
encodeURIComponent(moduleName) +
|
||||
"/quizzes/" +
|
||||
encodeURIComponent(quizName);
|
||||
await axiosClient.post(url, quiz);
|
||||
},
|
||||
onSuccess: (_, { moduleName, quizName }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: localCourseKeys.quiz(courseName, moduleName, quizName),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: localCourseKeys.quizNames(courseName, moduleName),
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -147,12 +147,21 @@ export const fileStorageService = {
|
||||
);
|
||||
return localAssignmentMarkdown.parseMarkdown(rawFile);
|
||||
},
|
||||
async updateAssignment(
|
||||
async updateOrCreateAssignment(
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
assignmentName: string,
|
||||
assignment: LocalAssignment
|
||||
) {
|
||||
const folder = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"assignments",
|
||||
);
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
|
||||
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
@@ -201,6 +210,13 @@ export const fileStorageService = {
|
||||
quizName: string,
|
||||
quiz: LocalQuiz
|
||||
) {
|
||||
const folder = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"quizzes",
|
||||
);
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
@@ -248,6 +264,14 @@ export const fileStorageService = {
|
||||
pageName: string,
|
||||
page: LocalCoursePage
|
||||
) {
|
||||
const folder = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"pages",
|
||||
);
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
|
||||
Reference in New Issue
Block a user