implement initialization of approval workflows on exam creation.
This commit is contained in:
@@ -17,6 +17,7 @@ import ListeningComponents from "./listening/components";
|
|||||||
import ReadingComponents from "./reading/components";
|
import ReadingComponents from "./reading/components";
|
||||||
import SpeakingComponents from "./speaking/components";
|
import SpeakingComponents from "./speaking/components";
|
||||||
import SectionPicker from "./Shared/SectionPicker";
|
import SectionPicker from "./Shared/SectionPicker";
|
||||||
|
import { getExamById } from "@/utils/exams";
|
||||||
|
|
||||||
|
|
||||||
const LevelSettings: React.FC = () => {
|
const LevelSettings: React.FC = () => {
|
||||||
@@ -213,6 +214,28 @@ const LevelSettings: React.FC = () => {
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const requestBody = await (async () => {
|
||||||
|
const handledExam = await getExamById("level", result.data.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
examAuthor: handledExam?.createdBy ?? "Unknown Author",
|
||||||
|
examId: handledExam?.id ?? "Unknown ID",
|
||||||
|
examModule: "level"
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
await axios.post(`/api/approval-workflows`, requestBody)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Approval Workflow for exam has been created`);
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
if (reason.response.status === 404) {
|
||||||
|
toast.error("No configured workflow found for examAuthor.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toast.error("Something went wrong while creating approval workflow, please try again later.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error submitting exam:', error);
|
console.error('Error submitting exam:', error);
|
||||||
toast.error(
|
toast.error(
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { usePersistentExamStore } from "@/stores/exam";
|
|||||||
import { playSound } from "@/utils/sound";
|
import { playSound } from "@/utils/sound";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import ListeningComponents from "./components";
|
import ListeningComponents from "./components";
|
||||||
|
import { getExamById } from "@/utils/exams";
|
||||||
|
|
||||||
const ListeningSettings: React.FC = () => {
|
const ListeningSettings: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -151,6 +152,28 @@ const ListeningSettings: React.FC = () => {
|
|||||||
playSound("sent");
|
playSound("sent");
|
||||||
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
||||||
|
|
||||||
|
const requestBody = await (async () => {
|
||||||
|
const handledExam = await getExamById("listening", result.data.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
examAuthor: handledExam?.createdBy ?? "Unknown Author",
|
||||||
|
examId: handledExam?.id ?? "Unknown ID",
|
||||||
|
examModule: "listening"
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
await axios.post(`/api/approval-workflows`, requestBody)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Approval Workflow for exam has been created`);
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
if (reason.response.status === 404) {
|
||||||
|
toast.error("No configured workflow found for examAuthor.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toast.error("Something went wrong while creating approval workflow, please try again later.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
toast.error('No audio sections found in the exam! Please either import them or generate them.');
|
toast.error('No audio sections found in the exam! Please either import them or generate them.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import axios from "axios";
|
|||||||
import { playSound } from "@/utils/sound";
|
import { playSound } from "@/utils/sound";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import ReadingComponents from "./components";
|
import ReadingComponents from "./components";
|
||||||
|
import { getExamById } from "@/utils/exams";
|
||||||
|
|
||||||
const ReadingSettings: React.FC = () => {
|
const ReadingSettings: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -46,15 +47,15 @@ const ReadingSettings: React.FC = () => {
|
|||||||
{
|
{
|
||||||
label: "Preset: Reading Passage 1",
|
label: "Preset: Reading Passage 1",
|
||||||
value: "Welcome to {part} of the {label}. You will read texts relating to everyday topics and situations. These may include advertisements, brochures, manuals, or official documents. Answer questions that test your ability to locate specific information and understand main ideas."
|
value: "Welcome to {part} of the {label}. You will read texts relating to everyday topics and situations. These may include advertisements, brochures, manuals, or official documents. Answer questions that test your ability to locate specific information and understand main ideas."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Preset: Reading Passage 2",
|
label: "Preset: Reading Passage 2",
|
||||||
value: "Welcome to {part} of the {label}. You will read texts dealing with general interest topics that may include news articles, company policies, or workplace documents. Answer questions testing your understanding of main ideas, specific details, and the author's views."
|
value: "Welcome to {part} of the {label}. You will read texts dealing with general interest topics that may include news articles, company policies, or workplace documents. Answer questions testing your understanding of main ideas, specific details, and the author's views."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Preset: Reading Passage 3",
|
label: "Preset: Reading Passage 3",
|
||||||
value: "Welcome to {part} of the {label}. You will read longer academic texts that may include journal articles, academic essays, or research papers. Answer questions testing your ability to understand complex arguments, identify key points, and follow the development of ideas."
|
value: "Welcome to {part} of the {label}. You will read longer academic texts that may include journal articles, academic essays, or research papers. Answer questions testing your ability to understand complex arguments, identify key points, and follow the development of ideas."
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const canPreviewOrSubmit = sections.some(
|
const canPreviewOrSubmit = sections.some(
|
||||||
@@ -89,11 +90,29 @@ const ReadingSettings: React.FC = () => {
|
|||||||
.then((result) => {
|
.then((result) => {
|
||||||
playSound("sent");
|
playSound("sent");
|
||||||
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
||||||
|
|
||||||
|
return getExamById("reading", result.data.id);
|
||||||
|
})
|
||||||
|
.then((handledExam) => {
|
||||||
|
const requestBody = {
|
||||||
|
examAuthor: handledExam?.createdBy ?? "Unknown Author",
|
||||||
|
examId: handledExam?.id ?? "Unknown ID",
|
||||||
|
examModule: "reading"
|
||||||
|
};
|
||||||
|
|
||||||
|
return axios.post(`/api/approval-workflows`, requestBody);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Approval Workflow for exam has been created`);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(error);
|
if (error.response && error.response.status === 404) {
|
||||||
toast.error(error.response.data.error || "Something went wrong while submitting, please try again later.");
|
toast.error("No configured workflow found for examAuthor.");
|
||||||
})
|
} else {
|
||||||
|
// This error could come from either of the requests
|
||||||
|
toast.error(error.response?.data?.error || "Something went wrong, please try again later.");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const preview = () => {
|
const preview = () => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import openDetachedTab from "@/utils/popout";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { playSound } from "@/utils/sound";
|
import { playSound } from "@/utils/sound";
|
||||||
import SpeakingComponents from "./components";
|
import SpeakingComponents from "./components";
|
||||||
|
import { getExamById } from "@/utils/exams";
|
||||||
|
|
||||||
export interface Avatar {
|
export interface Avatar {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -195,6 +196,28 @@ const SpeakingSettings: React.FC = () => {
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const requestBody = await (async () => {
|
||||||
|
const handledExam = await getExamById("speaking", result.data.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
examAuthor: handledExam?.createdBy ?? "Unknown Author",
|
||||||
|
examId: handledExam?.id ?? "Unknown ID",
|
||||||
|
examModule: "speaking"
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
await axios.post(`/api/approval-workflows`, requestBody)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Approval Workflow for exam has been created`);
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
if (reason.response.status === 404) {
|
||||||
|
toast.error("No configured workflow found for examAuthor.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toast.error("Something went wrong while creating approval workflow, please try again later.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(
|
toast.error(
|
||||||
"Something went wrong while submitting, please try again later."
|
"Something went wrong while submitting, please try again later."
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import axios from "axios";
|
|||||||
import { playSound } from "@/utils/sound";
|
import { playSound } from "@/utils/sound";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import WritingComponents from "./components";
|
import WritingComponents from "./components";
|
||||||
|
import { getExamById } from "@/utils/exams";
|
||||||
|
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
||||||
|
|
||||||
const WritingSettings: React.FC = () => {
|
const WritingSettings: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -140,6 +142,28 @@ const WritingSettings: React.FC = () => {
|
|||||||
playSound("sent");
|
playSound("sent");
|
||||||
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
||||||
|
|
||||||
|
const requestBody = await (async () => {
|
||||||
|
const handledExam = await getExamById("writing", result.data.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
examAuthor: handledExam?.createdBy ?? "Unknown Author",
|
||||||
|
examId: handledExam?.id ?? "Unknown ID",
|
||||||
|
examModule: "writing"
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
await axios.post(`/api/approval-workflows`, requestBody)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Approval Workflow for exam has been created`);
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
if (reason.response.status === 404) {
|
||||||
|
toast.error("No configured workflow found for examAuthor.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toast.error("Something went wrong while creating approval workflow, please try again later.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error submitting exam:', error);
|
console.error('Error submitting exam:', error);
|
||||||
toast.error(
|
toast.error(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface ApprovalWorkflow {
|
|||||||
requester: User["id"],
|
requester: User["id"],
|
||||||
startDate: number,
|
startDate: number,
|
||||||
modules: Module[],
|
modules: Module[],
|
||||||
|
examId?: string,
|
||||||
status: ApprovalWorkflowStatus,
|
status: ApprovalWorkflowStatus,
|
||||||
steps: WorkflowStep[],
|
steps: WorkflowStep[],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async function del(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
const { id } = req.query as { id?: string };
|
const { id } = req.query as { id?: string };
|
||||||
|
|
||||||
if (id) return res.status(200).json(await deleteApprovalWorkflow("configured-workflows", id));
|
if (id) return res.status(200).json(await deleteApprovalWorkflow("active-workflows", id));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function put(req: NextApiRequest, res: NextApiResponse) {
|
async function put(req: NextApiRequest, res: NextApiResponse) {
|
||||||
@@ -41,7 +41,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
if (id && workflow) {
|
if (id && workflow) {
|
||||||
workflow._id = new ObjectId(id);
|
workflow._id = new ObjectId(id);
|
||||||
await updateApprovalWorkflow("configured-workflows", workflow);
|
await updateApprovalWorkflow("active-workflows", workflow);
|
||||||
return res.status(204).end();
|
return res.status(204).end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,6 +57,6 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const { id } = req.query as { id?: string };
|
const { id } = req.query as { id?: string };
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
return res.status(200).json(await getApprovalWorkflow("configured-workflows", id));
|
return res.status(200).json(await getApprovalWorkflow("active-workflows", id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,5 +33,5 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
await replaceApprovalWorkflowsByEntities(configuredWorkflows, entitiesIds);
|
await replaceApprovalWorkflowsByEntities(configuredWorkflows, entitiesIds);
|
||||||
|
|
||||||
return res.status(201).json({ ok: true });
|
return res.status(204).end();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import { Module } from "@/interfaces";
|
||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
import { getApprovalWorkflows } from "@/utils/approval.workflows.be";
|
import { createApprovalWorkflow, getApprovalWorkflowByFormIntaker, getApprovalWorkflows } from "@/utils/approval.workflows.be";
|
||||||
import { withIronSessionApiRoute } from "iron-session/next";
|
import { withIronSessionApiRoute } from "iron-session/next";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
export default withIronSessionApiRoute(handler, sessionOptions);
|
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||||
|
|
||||||
|
interface PostRequestBody {
|
||||||
|
examAuthor: string,
|
||||||
|
examId: string,
|
||||||
|
examName: string,
|
||||||
|
examModule: Module,
|
||||||
|
}
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method === "GET") return await get(req, res);
|
if (req.method === "GET") return await get(req, res);
|
||||||
|
if (req.method === "POST") return await post(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get(req: NextApiRequest, res: NextApiResponse) {
|
async function get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
@@ -19,5 +28,33 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
return res.status(403).json({ ok: false });
|
return res.status(403).json({ ok: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json(await getApprovalWorkflows("configured-workflows"));
|
return res.status(200).json(await getApprovalWorkflows("active-workflows"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function post(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const user = await requestUser(req, res);
|
||||||
|
if (!user) return res.status(401).json({ ok: false });
|
||||||
|
|
||||||
|
if (!["admin", "developer", "corporate", "mastercorporate"].includes(user.type)) {
|
||||||
|
return res.status(403).json({ ok: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { examAuthor, examId, examModule } = req.body as PostRequestBody;
|
||||||
|
|
||||||
|
if (examAuthor) {
|
||||||
|
const configuredWorkflow = await getApprovalWorkflowByFormIntaker(examAuthor);
|
||||||
|
if(configuredWorkflow) {
|
||||||
|
configuredWorkflow.modules.push(examModule);
|
||||||
|
configuredWorkflow.name = `${examId}`;
|
||||||
|
configuredWorkflow.examId = examId;
|
||||||
|
configuredWorkflow.startDate = Date.now();
|
||||||
|
|
||||||
|
return res.status(201).json(await createApprovalWorkflow("active-workflows", configuredWorkflow));
|
||||||
|
} else {
|
||||||
|
return res.status(404).json("No configured workflow found for examAuthor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }
|
|||||||
|
|
||||||
const { id } = params as { id: string };
|
const { id } = params as { id: string };
|
||||||
|
|
||||||
const workflow: ApprovalWorkflow | null = await getApprovalWorkflow("configured-workflows", id);
|
const workflow: ApprovalWorkflow | null = await getApprovalWorkflow("active-workflows", id);
|
||||||
|
|
||||||
if (!workflow)
|
if (!workflow)
|
||||||
return redirect("/approval-workflows")
|
return redirect("/approval-workflows")
|
||||||
@@ -161,7 +161,11 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor
|
|||||||
return;
|
return;
|
||||||
})
|
})
|
||||||
|
|
||||||
handleStepClick(selectedStepIndex + 1, currentWorkflow.steps[selectedStepIndex + 1]);
|
if (selectedStepIndex + 1 < currentWorkflow.steps.length){
|
||||||
|
handleStepClick(selectedStepIndex + 1, currentWorkflow.steps[selectedStepIndex + 1]);
|
||||||
|
} else {
|
||||||
|
setIsPanelOpen(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRejectStep = () => {
|
const handleRejectStep = () => {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import clsx from "clsx";
|
|||||||
import { withIronSessionSsr } from "iron-session/next";
|
import { withIronSessionSsr } from "iron-session/next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { BsTrash } from "react-icons/bs";
|
import { BsTrash } from "react-icons/bs";
|
||||||
import { FaRegEdit } from "react-icons/fa";
|
import { FaRegEdit } from "react-icons/fa";
|
||||||
@@ -98,6 +97,8 @@ export default function ApprovalWorkflows({ user, initialWorkflows, workflowsAss
|
|||||||
const {workflows, reload} = useApprovalWorkflows();
|
const {workflows, reload} = useApprovalWorkflows();
|
||||||
const currentWorkflows = workflows || initialWorkflows;
|
const currentWorkflows = workflows || initialWorkflows;
|
||||||
|
|
||||||
|
console.log(currentWorkflows);
|
||||||
|
|
||||||
const [filteredWorkflows, setFilteredWorkflows] = useState<ApprovalWorkflow[]>([]);
|
const [filteredWorkflows, setFilteredWorkflows] = useState<ApprovalWorkflow[]>([]);
|
||||||
|
|
||||||
const [statusFilter, setStatusFilter] = useState<CustomStatus>(undefined);
|
const [statusFilter, setStatusFilter] = useState<CustomStatus>(undefined);
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ export const getApprovalWorkflowsByEntities = async (collection: string, ids: st
|
|||||||
.toArray();
|
.toArray();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getApprovalWorkflowByFormIntaker = async (entityId: string, formIntakerId: string) => {
|
export const getApprovalWorkflowByFormIntaker = async (/* entityId: string, */ formIntakerId: string) => {
|
||||||
return await db.collection<ApprovalWorkflow>("configured-workflows").findOne({
|
return await db.collection<ApprovalWorkflow>("configured-workflows").findOne({
|
||||||
entityId,
|
/* entityId, */
|
||||||
steps: {
|
steps: {
|
||||||
$elemMatch: {
|
$elemMatch: {
|
||||||
stepNumber: 1,
|
stepNumber: 1,
|
||||||
|
|||||||
Reference in New Issue
Block a user