diff --git a/src/components/ApprovalWorkflows/WorkflowForm.tsx b/src/components/ApprovalWorkflows/WorkflowForm.tsx index ffa92404..8cc4686d 100644 --- a/src/components/ApprovalWorkflows/WorkflowForm.tsx +++ b/src/components/ApprovalWorkflows/WorkflowForm.tsx @@ -33,6 +33,7 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityApprove key: stepCounter, stepType: "approval-by", stepNumber: workflow.steps.length, + completed: false, assignees: [null], firstStep: false, finalStep: false, diff --git a/src/interfaces/approval.workflow.ts b/src/interfaces/approval.workflow.ts index f5563e58..69cffa5e 100644 --- a/src/interfaces/approval.workflow.ts +++ b/src/interfaces/approval.workflow.ts @@ -43,6 +43,10 @@ export interface EditableWorkflowStep { key: number, stepType: StepType, stepNumber: number, + completed: boolean, + rejected?: boolean, + completedBy?: User["id"], + completedDate?: number, assignees: (User["id"] | null | undefined)[]; // bit of an hack, but allowing null or undefined values allows us to match one to one the select input components with the assignees array. And since select inputs allow undefined or null values, it is allowed here too, but must validate required input before form submission firstStep: boolean, finalStep?: boolean, diff --git a/src/pages/api/approval-workflows/[id]/clone.ts b/src/pages/api/approval-workflows/[id]/clone.ts index 6515d751..be0ee1b8 100644 --- a/src/pages/api/approval-workflows/[id]/clone.ts +++ b/src/pages/api/approval-workflows/[id]/clone.ts @@ -2,7 +2,7 @@ import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; import { sessionOptions } from "@/lib/session"; import { requestUser } from "@/utils/api"; -import { createApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { createConfiguredWorkflow } from "@/utils/approval.workflows.be"; import { withIronSessionApiRoute } from "iron-session/next"; import type { NextApiRequest, NextApiResponse } from "next"; @@ -23,5 +23,5 @@ async function post(req: NextApiRequest, res: NextApiResponse) { const approvalWorkflow: ApprovalWorkflow = req.body; if (approvalWorkflow) - return res.status(201).json(await createApprovalWorkflow(approvalWorkflow)); + return res.status(201).json(await createConfiguredWorkflow(approvalWorkflow)); } diff --git a/src/pages/api/approval-workflows/[id]/edit.ts b/src/pages/api/approval-workflows/[id]/edit.ts index c61e200a..39fc607f 100644 --- a/src/pages/api/approval-workflows/[id]/edit.ts +++ b/src/pages/api/approval-workflows/[id]/edit.ts @@ -2,7 +2,7 @@ import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; import { sessionOptions } from "@/lib/session"; import { requestUser } from "@/utils/api"; -import { updateApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { updateConfiguredWorkflow } from "@/utils/approval.workflows.be"; import { withIronSessionApiRoute } from "iron-session/next"; import type { NextApiRequest, NextApiResponse } from "next"; @@ -24,7 +24,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) { const approvalWorkflow: ApprovalWorkflow = req.body; if (id && approvalWorkflow) { - await updateApprovalWorkflow(id, approvalWorkflow); + await updateConfiguredWorkflow(approvalWorkflow); return res.status(204).end(); } } diff --git a/src/pages/api/approval-workflows/[id]/index.ts b/src/pages/api/approval-workflows/[id]/index.ts index e8de49a8..53ce6f21 100644 --- a/src/pages/api/approval-workflows/[id]/index.ts +++ b/src/pages/api/approval-workflows/[id]/index.ts @@ -2,7 +2,7 @@ import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; import { sessionOptions } from "@/lib/session"; import { requestUser } from "@/utils/api"; -import { deleteApprovalWorkflow, updateApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { deleteConfiguredWorkflow, updateConfiguredWorkflow } from "@/utils/approval.workflows.be"; import { withIronSessionApiRoute } from "iron-session/next"; import type { NextApiRequest, NextApiResponse } from "next"; @@ -23,7 +23,7 @@ async function del(req: NextApiRequest, res: NextApiResponse) { const { id } = req.query as { id?: string }; - if (id) return res.status(200).json(await deleteApprovalWorkflow(id)); + if (id) return res.status(200).json(await deleteConfiguredWorkflow(id)); } async function put(req: NextApiRequest, res: NextApiResponse) { @@ -38,7 +38,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) { const workflow = req.body; if (id && workflow) { - await updateApprovalWorkflow(id, workflow); + await updateConfiguredWorkflow(workflow); return res.status(204).end(); } } diff --git a/src/pages/api/approval-workflows/create.ts b/src/pages/api/approval-workflows/create.ts index dab8e80f..cbdddb02 100644 --- a/src/pages/api/approval-workflows/create.ts +++ b/src/pages/api/approval-workflows/create.ts @@ -1,13 +1,19 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; +import { Entity } from "@/interfaces/entity"; import { sessionOptions } from "@/lib/session"; import { requestUser } from "@/utils/api"; -import { createApprovalWorkflows } from "@/utils/approval.workflows.be"; +import { replaceConfiguredWorkflowsByEntities } from "@/utils/approval.workflows.be"; import { withIronSessionApiRoute } from "iron-session/next"; import type { NextApiRequest, NextApiResponse } from "next"; export default withIronSessionApiRoute(handler, sessionOptions); +interface ReplaceApprovalWorkflowsRequest { + filteredWorkflows: ApprovalWorkflow[]; + userEntitiesWithLabel: Entity[]; +} + async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === "POST") return await post(req, res); } @@ -20,9 +26,12 @@ async function post(req: NextApiRequest, res: NextApiResponse) { return res.status(403).json({ ok: false }); } - const approvalWorkflows: ApprovalWorkflow[] = req.body; + const { filteredWorkflows, userEntitiesWithLabel } = req.body as ReplaceApprovalWorkflowsRequest; - await createApprovalWorkflows(approvalWorkflows); + const configuredWorkflows: ApprovalWorkflow[] = filteredWorkflows; + const entitiesIds: string[] = userEntitiesWithLabel.map((e) => e.id); - return res.status(201).json(approvalWorkflows); + await replaceConfiguredWorkflowsByEntities(configuredWorkflows, entitiesIds); + + return res.status(201).json({ ok: true }); } diff --git a/src/pages/approval-workflows/[id]/clone.tsx b/src/pages/approval-workflows/[id]/clone.tsx index 8baaaf36..b42293a1 100644 --- a/src/pages/approval-workflows/[id]/clone.tsx +++ b/src/pages/approval-workflows/[id]/clone.tsx @@ -22,9 +22,9 @@ import { useEffect, useState } from "react"; import { BsChevronLeft } from "react-icons/bs"; import { GrClearOption } from "react-icons/gr"; import { toast, ToastContainer } from "react-toastify"; -import { getApprovalWorkflow } from "@/utils/approval.workflows.be"; import { useRouter } from "next/router"; import axios from "axios"; +import { getConfiguredWorkflow } from "@/utils/approval.workflows.be"; export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => { const user = await requestUser(req, res); @@ -35,7 +35,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } const { id } = params as { id: string }; - const workflow: ApprovalWorkflow | null = await getApprovalWorkflow(id); + const workflow: ApprovalWorkflow | null = await getConfiguredWorkflow(id); if (!workflow) return redirect("/approval-workflows") @@ -91,6 +91,7 @@ export default function Home({ user, workflow, userEntitiesWithLabel, userEntiti key: step.stepNumber + 999, // just making sure they are unique because new steps that users add will have key=3 key=4 etc stepType: step.stepType, stepNumber: step.stepNumber, + completed: false, assignees: step.assignees.map(id => id), firstStep: step.firstStep || false, finalStep: step.finalStep || false, @@ -179,8 +180,8 @@ export default function Home({ user, workflow, userEntitiesWithLabel, userEntiti startDate: Date.now(), status: "pending", steps: [ - { key: 9998, stepType: "form-intake", stepNumber: 1, firstStep: true, finalStep: false, assignees: [null] }, - { key: 9999, stepType: "approval-by", stepNumber: 2, firstStep: false, finalStep: true, assignees: [null] }, + { key: 9998, stepType: "form-intake", stepNumber: 1, completed: false, firstStep: true, finalStep: false, assignees: [null] }, + { key: 9999, stepType: "approval-by", stepNumber: 2, completed: false, firstStep: false, finalStep: true, assignees: [null] }, ], }; setCloneWorkflow(newWorkflow); diff --git a/src/pages/approval-workflows/[id]/edit.tsx b/src/pages/approval-workflows/[id]/edit.tsx index 1677f3ca..185cc655 100644 --- a/src/pages/approval-workflows/[id]/edit.tsx +++ b/src/pages/approval-workflows/[id]/edit.tsx @@ -8,18 +8,18 @@ import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser, User } import { sessionOptions } from "@/lib/session"; import { redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; +import { getConfiguredWorkflow } from "@/utils/approval.workflows.be"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { getEntityUsers } from "@/utils/users.be"; +import axios from "axios"; import { motion } from "framer-motion"; 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 { BsChevronLeft } from "react-icons/bs"; import { toast, ToastContainer } from "react-toastify"; -import axios from "axios"; -import { getApprovalWorkflow } from "@/utils/approval.workflows.be"; -import { useRouter } from "next/router"; export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => { const user = await requestUser(req, res); @@ -30,7 +30,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } const { id } = params as { id: string }; - const workflow: ApprovalWorkflow | null = await getApprovalWorkflow(id); + const workflow: ApprovalWorkflow | null = await getConfiguredWorkflow(id); if (!workflow) return redirect("/approval-workflows") @@ -62,6 +62,9 @@ export default function Home({ user, workflow, workflowEntityApprovers }: Props) key: step.stepNumber + 999, // just making sure they are unique because new steps that users add will have key=3 key=4 etc stepType: step.stepType, stepNumber: step.stepNumber, + completed: step.completed, + completedBy: step.completedBy || undefined, + completedDate: step.completedDate || undefined, assignees: step.assignees.map(id => id), firstStep: step.firstStep || false, finalStep: step.finalStep || false, diff --git a/src/pages/approval-workflows/[id]/index.tsx b/src/pages/approval-workflows/[id]/index.tsx index a510f033..12d79a90 100644 --- a/src/pages/approval-workflows/[id]/index.tsx +++ b/src/pages/approval-workflows/[id]/index.tsx @@ -11,7 +11,7 @@ import { User } from "@/interfaces/user"; import { sessionOptions } from "@/lib/session"; import { redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; -import { getApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { getConfiguredWorkflow } from "@/utils/approval.workflows.be"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { getSpecificUsers, getUser } from "@/utils/users.be"; import axios from "axios"; @@ -23,12 +23,14 @@ import { useRouter } from "next/router"; import { useState } from "react"; import { BsChevronLeft } from "react-icons/bs"; import { FaSpinner, FaWpforms } from "react-icons/fa6"; +import { FiSave } from "react-icons/fi"; +import { IoMdCheckmarkCircleOutline } from "react-icons/io"; +import { IoDocumentTextOutline } from "react-icons/io5"; import { MdOutlineDoubleArrow } from "react-icons/md"; import { RiThumbUpLine } from "react-icons/ri"; -import { toast, ToastContainer } from "react-toastify"; -import { IoMdCheckmarkCircleOutline } from "react-icons/io"; -import { FiSave } from "react-icons/fi"; import { RxCrossCircled } from "react-icons/rx"; +import { TiEdit } from "react-icons/ti"; +import { toast, ToastContainer } from "react-toastify"; export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => { const user = await requestUser(req, res); @@ -39,7 +41,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } const { id } = params as { id: string }; - const workflow: ApprovalWorkflow | null = await getApprovalWorkflow(id); + const workflow: ApprovalWorkflow | null = await getConfiguredWorkflow(id); if (!workflow) return redirect("/approval-workflows") @@ -132,6 +134,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques const updatedWorkflow: ApprovalWorkflow = { ...workflow, + status: selectedStepIndex === workflow.steps.length - 1 ? "approved" : "pending", steps: workflow.steps.map((step, index) => index === selectedStepIndex ? { @@ -206,6 +209,14 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques }) }; + const handleViewExam = () => { + + } + + const handleEditExam = () => { + + } + return ( <>
@@ -244,6 +255,29 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques status={workflow.status} /> +