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();
|
||||||
@@ -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;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (selectedStepIndex + 1 < currentWorkflow.steps.length){
|
||||||
handleStepClick(selectedStepIndex + 1, currentWorkflow.steps[selectedStepIndex + 1]);
|
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