diff --git a/src/components/ExamEditor/SettingsEditor/level.tsx b/src/components/ExamEditor/SettingsEditor/level.tsx index c5fc97e1..913abb4b 100644 --- a/src/components/ExamEditor/SettingsEditor/level.tsx +++ b/src/components/ExamEditor/SettingsEditor/level.tsx @@ -17,6 +17,7 @@ import ListeningComponents from "./listening/components"; import ReadingComponents from "./reading/components"; import SpeakingComponents from "./speaking/components"; import SectionPicker from "./Shared/SectionPicker"; +import { getExamById } from "@/utils/exams"; const LevelSettings: React.FC = () => { @@ -213,6 +214,28 @@ const LevelSettings: React.FC = () => { 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) { console.error('Error submitting exam:', error); toast.error( diff --git a/src/components/ExamEditor/SettingsEditor/listening/index.tsx b/src/components/ExamEditor/SettingsEditor/listening/index.tsx index e3c4d416..0c9ae1f6 100644 --- a/src/components/ExamEditor/SettingsEditor/listening/index.tsx +++ b/src/components/ExamEditor/SettingsEditor/listening/index.tsx @@ -17,6 +17,7 @@ import { usePersistentExamStore } from "@/stores/exam"; import { playSound } from "@/utils/sound"; import { toast } from "react-toastify"; import ListeningComponents from "./components"; +import { getExamById } from "@/utils/exams"; const ListeningSettings: React.FC = () => { const router = useRouter(); @@ -151,6 +152,28 @@ const ListeningSettings: React.FC = () => { playSound("sent"); 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 { toast.error('No audio sections found in the exam! Please either import them or generate them.'); } diff --git a/src/components/ExamEditor/SettingsEditor/reading/index.tsx b/src/components/ExamEditor/SettingsEditor/reading/index.tsx index 590a246d..2d4abe67 100644 --- a/src/components/ExamEditor/SettingsEditor/reading/index.tsx +++ b/src/components/ExamEditor/SettingsEditor/reading/index.tsx @@ -12,6 +12,7 @@ import axios from "axios"; import { playSound } from "@/utils/sound"; import { toast } from "react-toastify"; import ReadingComponents from "./components"; +import { getExamById } from "@/utils/exams"; const ReadingSettings: React.FC = () => { const router = useRouter(); @@ -46,15 +47,15 @@ const ReadingSettings: React.FC = () => { { 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." - }, - { - 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." - }, - { + }, + { 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." - } + } ]; const canPreviewOrSubmit = sections.some( @@ -89,11 +90,29 @@ const ReadingSettings: React.FC = () => { .then((result) => { playSound("sent"); 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) => { - console.log(error); - toast.error(error.response.data.error || "Something went wrong while submitting, please try again later."); - }) + if (error.response && error.response.status === 404) { + 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 = () => { diff --git a/src/components/ExamEditor/SettingsEditor/speaking/index.tsx b/src/components/ExamEditor/SettingsEditor/speaking/index.tsx index 8fccbc97..ba86d02d 100644 --- a/src/components/ExamEditor/SettingsEditor/speaking/index.tsx +++ b/src/components/ExamEditor/SettingsEditor/speaking/index.tsx @@ -11,6 +11,7 @@ import openDetachedTab from "@/utils/popout"; import axios from "axios"; import { playSound } from "@/utils/sound"; import SpeakingComponents from "./components"; +import { getExamById } from "@/utils/exams"; export interface Avatar { name: string; @@ -194,6 +195,28 @@ const SpeakingSettings: React.FC = () => { Array.from(urlMap.values()).forEach(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) { toast.error( diff --git a/src/components/ExamEditor/SettingsEditor/writing/index.tsx b/src/components/ExamEditor/SettingsEditor/writing/index.tsx index 6ed9aebe..7799ca07 100644 --- a/src/components/ExamEditor/SettingsEditor/writing/index.tsx +++ b/src/components/ExamEditor/SettingsEditor/writing/index.tsx @@ -12,6 +12,8 @@ import axios from "axios"; import { playSound } from "@/utils/sound"; import { toast } from "react-toastify"; import WritingComponents from "./components"; +import { getExamById } from "@/utils/exams"; +import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; const WritingSettings: React.FC = () => { const router = useRouter(); @@ -140,6 +142,28 @@ const WritingSettings: React.FC = () => { playSound("sent"); 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) { console.error('Error submitting exam:', error); toast.error( diff --git a/src/interfaces/approval.workflow.ts b/src/interfaces/approval.workflow.ts index 69cffa5e..84a6567d 100644 --- a/src/interfaces/approval.workflow.ts +++ b/src/interfaces/approval.workflow.ts @@ -9,6 +9,7 @@ export interface ApprovalWorkflow { requester: User["id"], startDate: number, modules: Module[], + examId?: string, status: ApprovalWorkflowStatus, steps: WorkflowStep[], } diff --git a/src/pages/api/approval-workflows/[id]/index.ts b/src/pages/api/approval-workflows/[id]/index.ts index 7bf8ecf3..c50026c0 100644 --- a/src/pages/api/approval-workflows/[id]/index.ts +++ b/src/pages/api/approval-workflows/[id]/index.ts @@ -25,7 +25,7 @@ async function del(req: NextApiRequest, res: NextApiResponse) { 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) { @@ -41,7 +41,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) { if (id && workflow) { workflow._id = new ObjectId(id); - await updateApprovalWorkflow("configured-workflows", workflow); + await updateApprovalWorkflow("active-workflows", workflow); return res.status(204).end(); } } @@ -57,6 +57,6 @@ async function get(req: NextApiRequest, res: NextApiResponse) { const { id } = req.query as { id?: string }; if (id) { - return res.status(200).json(await getApprovalWorkflow("configured-workflows", id)); + return res.status(200).json(await getApprovalWorkflow("active-workflows", id)); } } diff --git a/src/pages/api/approval-workflows/create.ts b/src/pages/api/approval-workflows/create.ts index ddd46da5..96889899 100644 --- a/src/pages/api/approval-workflows/create.ts +++ b/src/pages/api/approval-workflows/create.ts @@ -33,5 +33,5 @@ async function post(req: NextApiRequest, res: NextApiResponse) { await replaceApprovalWorkflowsByEntities(configuredWorkflows, entitiesIds); - return res.status(201).json({ ok: true }); + return res.status(204).end(); } diff --git a/src/pages/api/approval-workflows/index.ts b/src/pages/api/approval-workflows/index.ts index e9a5b075..9cffb13d 100644 --- a/src/pages/api/approval-workflows/index.ts +++ b/src/pages/api/approval-workflows/index.ts @@ -1,14 +1,23 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import { Module } from "@/interfaces"; import { sessionOptions } from "@/lib/session"; 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 type { NextApiRequest, NextApiResponse } from "next"; export default withIronSessionApiRoute(handler, sessionOptions); +interface PostRequestBody { + examAuthor: string, + examId: string, + examName: string, + examModule: Module, +} + async function handler(req: NextApiRequest, res: NextApiResponse) { 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) { @@ -19,5 +28,33 @@ async function get(req: NextApiRequest, res: NextApiResponse) { 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."); + } + + } + + } diff --git a/src/pages/approval-workflows/[id]/index.tsx b/src/pages/approval-workflows/[id]/index.tsx index c3ae3cae..64d16bd5 100644 --- a/src/pages/approval-workflows/[id]/index.tsx +++ b/src/pages/approval-workflows/[id]/index.tsx @@ -41,7 +41,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } 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) return redirect("/approval-workflows") @@ -161,7 +161,11 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor 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 = () => { diff --git a/src/pages/approval-workflows/index.tsx b/src/pages/approval-workflows/index.tsx index ab51ee4f..516f543a 100644 --- a/src/pages/approval-workflows/index.tsx +++ b/src/pages/approval-workflows/index.tsx @@ -20,7 +20,6 @@ import clsx from "clsx"; import { withIronSessionSsr } from "iron-session/next"; import Head from "next/head"; import Link from "next/link"; -import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { BsTrash } from "react-icons/bs"; import { FaRegEdit } from "react-icons/fa"; @@ -98,6 +97,8 @@ export default function ApprovalWorkflows({ user, initialWorkflows, workflowsAss const {workflows, reload} = useApprovalWorkflows(); const currentWorkflows = workflows || initialWorkflows; + console.log(currentWorkflows); + const [filteredWorkflows, setFilteredWorkflows] = useState([]); const [statusFilter, setStatusFilter] = useState(undefined); diff --git a/src/utils/approval.workflows.be.ts b/src/utils/approval.workflows.be.ts index baf02bfd..200a3d89 100644 --- a/src/utils/approval.workflows.be.ts +++ b/src/utils/approval.workflows.be.ts @@ -22,9 +22,9 @@ export const getApprovalWorkflowsByEntities = async (collection: string, ids: st .toArray(); }; -export const getApprovalWorkflowByFormIntaker = async (entityId: string, formIntakerId: string) => { +export const getApprovalWorkflowByFormIntaker = async (/* entityId: string, */ formIntakerId: string) => { return await db.collection("configured-workflows").findOne({ - entityId, + /* entityId, */ steps: { $elemMatch: { stepNumber: 1,