major change on how workflow builder works. It now fetches in edit mode all the currently configured workflows
This commit is contained in:
@@ -33,6 +33,7 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityApprove
|
|||||||
key: stepCounter,
|
key: stepCounter,
|
||||||
stepType: "approval-by",
|
stepType: "approval-by",
|
||||||
stepNumber: workflow.steps.length,
|
stepNumber: workflow.steps.length,
|
||||||
|
completed: false,
|
||||||
assignees: [null],
|
assignees: [null],
|
||||||
firstStep: false,
|
firstStep: false,
|
||||||
finalStep: false,
|
finalStep: false,
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ export interface EditableWorkflowStep {
|
|||||||
key: number,
|
key: number,
|
||||||
stepType: StepType,
|
stepType: StepType,
|
||||||
stepNumber: number,
|
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
|
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,
|
firstStep: boolean,
|
||||||
finalStep?: boolean,
|
finalStep?: boolean,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { requestUser } from "@/utils/api";
|
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 { withIronSessionApiRoute } from "iron-session/next";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
@@ -23,5 +23,5 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const approvalWorkflow: ApprovalWorkflow = req.body;
|
const approvalWorkflow: ApprovalWorkflow = req.body;
|
||||||
|
|
||||||
if (approvalWorkflow)
|
if (approvalWorkflow)
|
||||||
return res.status(201).json(await createApprovalWorkflow(approvalWorkflow));
|
return res.status(201).json(await createConfiguredWorkflow(approvalWorkflow));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { requestUser } from "@/utils/api";
|
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 { withIronSessionApiRoute } from "iron-session/next";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const approvalWorkflow: ApprovalWorkflow = req.body;
|
const approvalWorkflow: ApprovalWorkflow = req.body;
|
||||||
|
|
||||||
if (id && approvalWorkflow) {
|
if (id && approvalWorkflow) {
|
||||||
await updateApprovalWorkflow(id, approvalWorkflow);
|
await updateConfiguredWorkflow(approvalWorkflow);
|
||||||
return res.status(204).end();
|
return res.status(204).end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { requestUser } from "@/utils/api";
|
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 { withIronSessionApiRoute } from "iron-session/next";
|
||||||
import type { NextApiRequest, NextApiResponse } from "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 };
|
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) {
|
async function put(req: NextApiRequest, res: NextApiResponse) {
|
||||||
@@ -38,7 +38,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const workflow = req.body;
|
const workflow = req.body;
|
||||||
|
|
||||||
if (id && workflow) {
|
if (id && workflow) {
|
||||||
await updateApprovalWorkflow(id, workflow);
|
await updateConfiguredWorkflow(workflow);
|
||||||
return res.status(204).end();
|
return res.status(204).end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
// 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 { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
||||||
|
import { Entity } from "@/interfaces/entity";
|
||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { requestUser } from "@/utils/api";
|
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 { 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 ReplaceApprovalWorkflowsRequest {
|
||||||
|
filteredWorkflows: ApprovalWorkflow[];
|
||||||
|
userEntitiesWithLabel: Entity[];
|
||||||
|
}
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method === "POST") return await post(req, res);
|
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 });
|
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 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ import { useEffect, useState } from "react";
|
|||||||
import { BsChevronLeft } from "react-icons/bs";
|
import { BsChevronLeft } from "react-icons/bs";
|
||||||
import { GrClearOption } from "react-icons/gr";
|
import { GrClearOption } from "react-icons/gr";
|
||||||
import { toast, ToastContainer } from "react-toastify";
|
import { toast, ToastContainer } from "react-toastify";
|
||||||
import { getApprovalWorkflow } from "@/utils/approval.workflows.be";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { getConfiguredWorkflow } from "@/utils/approval.workflows.be";
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => {
|
export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => {
|
||||||
const user = await requestUser(req, res);
|
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 { id } = params as { id: string };
|
||||||
|
|
||||||
const workflow: ApprovalWorkflow | null = await getApprovalWorkflow(id);
|
const workflow: ApprovalWorkflow | null = await getConfiguredWorkflow(id);
|
||||||
|
|
||||||
if (!workflow)
|
if (!workflow)
|
||||||
return redirect("/approval-workflows")
|
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
|
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,
|
stepType: step.stepType,
|
||||||
stepNumber: step.stepNumber,
|
stepNumber: step.stepNumber,
|
||||||
|
completed: false,
|
||||||
assignees: step.assignees.map(id => id),
|
assignees: step.assignees.map(id => id),
|
||||||
firstStep: step.firstStep || false,
|
firstStep: step.firstStep || false,
|
||||||
finalStep: step.finalStep || false,
|
finalStep: step.finalStep || false,
|
||||||
@@ -179,8 +180,8 @@ export default function Home({ user, workflow, userEntitiesWithLabel, userEntiti
|
|||||||
startDate: Date.now(),
|
startDate: Date.now(),
|
||||||
status: "pending",
|
status: "pending",
|
||||||
steps: [
|
steps: [
|
||||||
{ key: 9998, stepType: "form-intake", stepNumber: 1, firstStep: true, finalStep: false, assignees: [null] },
|
{ key: 9998, stepType: "form-intake", stepNumber: 1, completed: false, firstStep: true, finalStep: false, assignees: [null] },
|
||||||
{ key: 9999, stepType: "approval-by", stepNumber: 2, firstStep: false, finalStep: true, assignees: [null] },
|
{ key: 9999, stepType: "approval-by", stepNumber: 2, completed: false, firstStep: false, finalStep: true, assignees: [null] },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
setCloneWorkflow(newWorkflow);
|
setCloneWorkflow(newWorkflow);
|
||||||
|
|||||||
@@ -8,18 +8,18 @@ import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser, User }
|
|||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { redirect, serialize } from "@/utils";
|
import { redirect, serialize } from "@/utils";
|
||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
|
import { getConfiguredWorkflow } from "@/utils/approval.workflows.be";
|
||||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||||
import { getEntityUsers } from "@/utils/users.be";
|
import { getEntityUsers } from "@/utils/users.be";
|
||||||
|
import axios from "axios";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
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 { BsChevronLeft } from "react-icons/bs";
|
import { BsChevronLeft } from "react-icons/bs";
|
||||||
import { toast, ToastContainer } from "react-toastify";
|
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 }) => {
|
export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => {
|
||||||
const user = await requestUser(req, res);
|
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 { id } = params as { id: string };
|
||||||
|
|
||||||
const workflow: ApprovalWorkflow | null = await getApprovalWorkflow(id);
|
const workflow: ApprovalWorkflow | null = await getConfiguredWorkflow(id);
|
||||||
|
|
||||||
if (!workflow)
|
if (!workflow)
|
||||||
return redirect("/approval-workflows")
|
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
|
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,
|
stepType: step.stepType,
|
||||||
stepNumber: step.stepNumber,
|
stepNumber: step.stepNumber,
|
||||||
|
completed: step.completed,
|
||||||
|
completedBy: step.completedBy || undefined,
|
||||||
|
completedDate: step.completedDate || undefined,
|
||||||
assignees: step.assignees.map(id => id),
|
assignees: step.assignees.map(id => id),
|
||||||
firstStep: step.firstStep || false,
|
firstStep: step.firstStep || false,
|
||||||
finalStep: step.finalStep || false,
|
finalStep: step.finalStep || false,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { User } from "@/interfaces/user";
|
|||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { redirect, serialize } from "@/utils";
|
import { redirect, serialize } from "@/utils";
|
||||||
import { requestUser } from "@/utils/api";
|
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 { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||||
import { getSpecificUsers, getUser } from "@/utils/users.be";
|
import { getSpecificUsers, getUser } from "@/utils/users.be";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@@ -23,12 +23,14 @@ import { useRouter } from "next/router";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { BsChevronLeft } from "react-icons/bs";
|
import { BsChevronLeft } from "react-icons/bs";
|
||||||
import { FaSpinner, FaWpforms } from "react-icons/fa6";
|
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 { MdOutlineDoubleArrow } from "react-icons/md";
|
||||||
import { RiThumbUpLine } from "react-icons/ri";
|
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 { 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 }) => {
|
export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => {
|
||||||
const user = await requestUser(req, res);
|
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 { id } = params as { id: string };
|
||||||
|
|
||||||
const workflow: ApprovalWorkflow | null = await getApprovalWorkflow(id);
|
const workflow: ApprovalWorkflow | null = await getConfiguredWorkflow(id);
|
||||||
|
|
||||||
if (!workflow)
|
if (!workflow)
|
||||||
return redirect("/approval-workflows")
|
return redirect("/approval-workflows")
|
||||||
@@ -132,6 +134,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
|
|
||||||
const updatedWorkflow: ApprovalWorkflow = {
|
const updatedWorkflow: ApprovalWorkflow = {
|
||||||
...workflow,
|
...workflow,
|
||||||
|
status: selectedStepIndex === workflow.steps.length - 1 ? "approved" : "pending",
|
||||||
steps: workflow.steps.map((step, index) =>
|
steps: workflow.steps.map((step, index) =>
|
||||||
index === selectedStepIndex ?
|
index === selectedStepIndex ?
|
||||||
{
|
{
|
||||||
@@ -206,6 +209,14 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleViewExam = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEditExam = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -244,6 +255,29 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
status={workflow.status}
|
status={workflow.status}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-row gap-3">
|
||||||
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="solid"
|
||||||
|
onClick={handleViewExam}
|
||||||
|
padding="px-6 py-2"
|
||||||
|
className="w-[240px] text-lg flex items-center justify-center gap-2 text-left"
|
||||||
|
>
|
||||||
|
<IoDocumentTextOutline />
|
||||||
|
View Exam
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="solid"
|
||||||
|
onClick={handleEditExam}
|
||||||
|
padding="px-6 py-2"
|
||||||
|
className="w-[240px] text-lg flex items-center justify-center gap-2 text-left"
|
||||||
|
>
|
||||||
|
<TiEdit size={20} />
|
||||||
|
Edit Exam
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</div>
|
||||||
{steps.find((step) => !step.completed) === undefined &&
|
{steps.find((step) => !step.completed) === undefined &&
|
||||||
<Tip text="All steps in this instance have been completed." />
|
<Tip text="All steps in this instance have been completed." />
|
||||||
}
|
}
|
||||||
@@ -271,7 +305,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
{/* Side panel */}
|
{/* Side panel */}
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<LayoutGroup key="sidePanel">
|
<LayoutGroup key="sidePanel">
|
||||||
<section className={`absolute inset-y-0 right-0 h-full bg-mti-purple-ultralight bg-opacity-50 shadow-xl shadow-mti-purple transition-all duration-300 overflow-hidden ${isPanelOpen ? 'w-2/5' : 'w-0'}`}>
|
<section className={`absolute inset-y-0 right-0 h-full bg-mti-purple-ultralight bg-opacity-50 shadow-xl shadow-mti-purple transition-all duration-300 overflow-hidden ${isPanelOpen ? 'w-[500px]' : 'w-0'}`}>
|
||||||
{isPanelOpen && selectedStep && (
|
{isPanelOpen && selectedStep && (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="p-6"
|
className="p-6"
|
||||||
@@ -412,7 +446,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<RxCrossCircled size={20} />
|
<RxCrossCircled size={20} />
|
||||||
Reject Step
|
Reject
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser, User }
|
|||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { redirect, serialize } from "@/utils";
|
import { redirect, serialize } from "@/utils";
|
||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
|
import { getConfiguredWorkflowsByEntities } from "@/utils/approval.workflows.be";
|
||||||
import { getEntities } from "@/utils/entities.be";
|
import { getEntities } from "@/utils/entities.be";
|
||||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||||
import { getEntitiesUsers } from "@/utils/users.be";
|
import { getEntitiesUsers } from "@/utils/users.be";
|
||||||
@@ -34,9 +35,12 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
|||||||
|
|
||||||
const userEntitiesWithLabel = await getEntities(user.entities.map(entity => entity.id));
|
const userEntitiesWithLabel = await getEntities(user.entities.map(entity => entity.id));
|
||||||
|
|
||||||
|
const allConfiguredWorkflows = await getConfiguredWorkflowsByEntities(userEntitiesWithLabel.map(entity => entity.id));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: serialize({
|
props: serialize({
|
||||||
user,
|
user,
|
||||||
|
allConfiguredWorkflows,
|
||||||
userEntitiesWithLabel,
|
userEntitiesWithLabel,
|
||||||
userEntitiesApprovers: await getEntitiesUsers(userEntitiesWithLabel.map(entity => entity.id), { type: { $in: ["teacher", "corporate", "mastercorporate", "developer"] } }) as (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[],
|
userEntitiesApprovers: await getEntitiesUsers(userEntitiesWithLabel.map(entity => entity.id), { type: { $in: ["teacher", "corporate", "mastercorporate", "developer"] } }) as (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[],
|
||||||
}),
|
}),
|
||||||
@@ -45,12 +49,13 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: User,
|
user: User,
|
||||||
|
allConfiguredWorkflows: EditableApprovalWorkflow[],
|
||||||
userEntitiesWithLabel: Entity[],
|
userEntitiesWithLabel: Entity[],
|
||||||
userEntitiesApprovers: (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[],
|
userEntitiesApprovers: (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Home({ user, userEntitiesWithLabel, userEntitiesApprovers }: Props) {
|
export default function Home({ user, allConfiguredWorkflows, userEntitiesWithLabel, userEntitiesApprovers }: Props) {
|
||||||
const [workflows, setWorkflows] = useState<EditableApprovalWorkflow[]>([]);
|
const [workflows, setWorkflows] = useState<EditableApprovalWorkflow[]>(allConfiguredWorkflows);
|
||||||
const [selectedWorkflowId, setSelectedWorkflowId] = useState<string | undefined>(undefined);
|
const [selectedWorkflowId, setSelectedWorkflowId] = useState<string | undefined>(undefined);
|
||||||
const [entityId, setEntityId] = useState<string | null | undefined>(null);
|
const [entityId, setEntityId] = useState<string | null | undefined>(null);
|
||||||
const [entityApprovers, setEntityApprovers] = useState<(TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[]>([]);
|
const [entityApprovers, setEntityApprovers] = useState<(TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[]>([]);
|
||||||
@@ -93,13 +98,14 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesApprover
|
|||||||
...workflow,
|
...workflow,
|
||||||
steps: workflow.steps.map(step => ({
|
steps: workflow.steps.map(step => ({
|
||||||
...step,
|
...step,
|
||||||
completed: false,
|
|
||||||
assignees: step.assignees.filter((assignee): assignee is string => assignee !== null && assignee !== undefined)
|
assignees: step.assignees.filter((assignee): assignee is string => assignee !== null && assignee !== undefined)
|
||||||
}))
|
}))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const requestData = {filteredWorkflows, userEntitiesWithLabel};
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.post(`/api/approval-workflows/create`, filteredWorkflows)
|
.post(`/api/approval-workflows/create`, requestData)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Approval Workflows created successfully.");
|
toast.success("Approval Workflows created successfully.");
|
||||||
setIsRedirecting(true);
|
setIsRedirecting(true);
|
||||||
@@ -135,8 +141,8 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesApprover
|
|||||||
startDate: Date.now(),
|
startDate: Date.now(),
|
||||||
status: "pending",
|
status: "pending",
|
||||||
steps: [
|
steps: [
|
||||||
{ key: 9998, stepType: "form-intake", stepNumber: 1, firstStep: true, finalStep: false, assignees: [null] },
|
{ key: 9998, stepType: "form-intake", stepNumber: 1, completed: false, firstStep: true, finalStep: false, assignees: [null] },
|
||||||
{ key: 9999, stepType: "approval-by", stepNumber: 2, firstStep: false, finalStep: true, assignees: [null] },
|
{ key: 9999, stepType: "approval-by", stepNumber: 2, completed: false, firstStep: false, finalStep: true, assignees: [null] },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
setWorkflows((prev) => [...prev, newWorkflow]);
|
setWorkflows((prev) => [...prev, newWorkflow]);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Layout from "@/components/High/Layout";
|
import Layout from "@/components/High/Layout";
|
||||||
import Button from "@/components/Low/Button";
|
import Button from "@/components/Low/Button";
|
||||||
|
import Input from "@/components/Low/Input";
|
||||||
import Select from "@/components/Low/Select";
|
import Select from "@/components/Low/Select";
|
||||||
import { Module, ModuleTypeLabels } from "@/interfaces";
|
import { Module, ModuleTypeLabels } from "@/interfaces";
|
||||||
import { ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel, StepTypeLabel } from "@/interfaces/approval.workflow";
|
import { ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel, StepTypeLabel } from "@/interfaces/approval.workflow";
|
||||||
@@ -8,6 +9,7 @@ import { User } from "@/interfaces/user";
|
|||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { redirect, serialize } from "@/utils";
|
import { redirect, serialize } from "@/utils";
|
||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
|
import { getConfiguredWorkflows } from "@/utils/approval.workflows.be";
|
||||||
import { getEntities } from "@/utils/entities.be";
|
import { getEntities } from "@/utils/entities.be";
|
||||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||||
import { getSpecificUsers } from "@/utils/users.be";
|
import { getSpecificUsers } from "@/utils/users.be";
|
||||||
@@ -17,18 +19,14 @@ 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";
|
||||||
|
import { FaRegClone } from "react-icons/fa6";
|
||||||
import { IoIosAddCircleOutline } from "react-icons/io";
|
import { IoIosAddCircleOutline } from "react-icons/io";
|
||||||
import { toast, ToastContainer } from "react-toastify";
|
import { toast, ToastContainer } from "react-toastify";
|
||||||
|
|
||||||
import Input from "@/components/Low/Input";
|
|
||||||
import { FaRegClone } from "react-icons/fa6";
|
|
||||||
import useApprovalWorkflows from "@/hooks/useApprovalWorkflows";
|
|
||||||
import { getApprovalWorkflows } from "@/utils/approval.workflows.be";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<ApprovalWorkflow>();
|
const columnHelper = createColumnHelper<ApprovalWorkflow>();
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||||
@@ -38,7 +36,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
|||||||
if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type))
|
if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type))
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
const workflows = await getApprovalWorkflows();
|
const workflows = await getConfiguredWorkflows();
|
||||||
|
|
||||||
const allAssigneeIds: string[] = [
|
const allAssigneeIds: string[] = [
|
||||||
...new Set(
|
...new Set(
|
||||||
@@ -331,7 +329,7 @@ export default function ApprovalWorkflows({ user, workflows, workflowsAssignees,
|
|||||||
className="min-w-fit text-lg font-medium flex items-center gap-2 text-left"
|
className="min-w-fit text-lg font-medium flex items-center gap-2 text-left"
|
||||||
>
|
>
|
||||||
<IoIosAddCircleOutline className="size-6" />
|
<IoIosAddCircleOutline className="size-6" />
|
||||||
Configure New Workflows
|
Configure Workflows
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,36 +1,83 @@
|
|||||||
import { ObjectId } from "mongodb";
|
|
||||||
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
||||||
import client from "@/lib/mongodb";
|
import client from "@/lib/mongodb";
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
|
|
||||||
const db = client.db(process.env.MONGODB_DB);
|
const db = client.db(process.env.MONGODB_DB);
|
||||||
|
|
||||||
export const getApprovalWorkflows = async (ids?: string[]) => {
|
export const getConfiguredWorkflows = async (ids?: string[]) => {
|
||||||
return await db
|
return await db
|
||||||
.collection<ApprovalWorkflow>("approval-workflows")
|
.collection<ApprovalWorkflow>("configured-workflows")
|
||||||
.find(ids ? { _id: { $in: ids.map((id) => new ObjectId(id)) } } : {})
|
.find(ids ? { _id: { $in: ids.map((id) => new ObjectId(id)) } } : {})
|
||||||
.toArray();
|
.toArray();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getApprovalWorkflow = async (id: string) => {
|
export const getConfiguredWorkflow = async (id: string) => {
|
||||||
return await db.collection<ApprovalWorkflow>("approval-workflows").findOne({ _id: new ObjectId(id) });
|
return await db.collection<ApprovalWorkflow>("configured-workflows").findOne({ _id: new ObjectId(id) });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createApprovalWorkflow = async (workflow: ApprovalWorkflow) => {
|
export const getConfiguredWorkflowsByEntities = async (ids: string[]) => {
|
||||||
const { _id, ...workflowWithoutId } = workflow as ApprovalWorkflow;
|
return await db
|
||||||
return await db.collection("approval-workflows").insertOne(workflowWithoutId);
|
.collection<ApprovalWorkflow>("configured-workflows")
|
||||||
|
.find({ entityId: { $in: ids } })
|
||||||
|
.toArray();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createApprovalWorkflows = async (workflows: ApprovalWorkflow[]) => {
|
export const createConfiguredWorkflow = async (workflow: ApprovalWorkflow) => {
|
||||||
|
const { _id, ...workflowWithoutId } = workflow as ApprovalWorkflow;
|
||||||
|
return await db.collection("configured-workflows").insertOne(workflowWithoutId);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createConfiguredWorkflows = async (workflows: ApprovalWorkflow[]) => {
|
||||||
if (workflows.length === 0) return;
|
if (workflows.length === 0) return;
|
||||||
const workflowsWithoutIds: ApprovalWorkflow[] = workflows.map(({_id, ...wfs}) => wfs)
|
const workflowsWithoutIds: ApprovalWorkflow[] = workflows.map(({ _id, ...wfs }) => wfs);
|
||||||
return await db.collection("approval-workflows").insertMany(workflowsWithoutIds);
|
return await db.collection("configured-workflows").insertMany(workflowsWithoutIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateApprovalWorkflow = async (id: string, workflow: ApprovalWorkflow) => {
|
export const updateConfiguredWorkflow = async (workflow: ApprovalWorkflow) => {
|
||||||
const { _id, ...workflowWithoutId } = workflow as ApprovalWorkflow;
|
const { _id, ...workflowWithoutId } = workflow as ApprovalWorkflow;
|
||||||
return await db.collection("approval-workflows").replaceOne({ _id: new ObjectId(id) }, workflowWithoutId);
|
return await db.collection("configured-workflows").replaceOne({ _id: new ObjectId(_id) }, workflowWithoutId);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteApprovalWorkflow = async (id: string) => {
|
export const updateConfiguredWorkflows = async (workflows: ApprovalWorkflow[]) => {
|
||||||
return await db.collection("approval-workflows").deleteOne({ _id: new ObjectId(id) });
|
const bulkOperations = workflows.map((workflow) => {
|
||||||
|
const { _id, ...workflowWithoutId } = workflow;
|
||||||
|
return {
|
||||||
|
replaceOne: {
|
||||||
|
filter: { _id: new ObjectId(_id) },
|
||||||
|
replacement: workflowWithoutId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return await db.collection("configured-workflows").bulkWrite(bulkOperations);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteConfiguredWorkflow = async (id: string) => {
|
||||||
|
return await db.collection("configured-workflows").deleteOne({ _id: new ObjectId(id) });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const replaceConfiguredWorkflowsByEntities = async (workflows: ApprovalWorkflow[], entityIds: string[]) => {
|
||||||
|
// 1. Keep track of the _id values of all workflows we want to end up with
|
||||||
|
const finalIds = new Set<string>();
|
||||||
|
|
||||||
|
// 2. Process incoming workflows
|
||||||
|
for (const workflow of workflows) {
|
||||||
|
if (workflow._id) {
|
||||||
|
// Replace the existing ones
|
||||||
|
await updateConfiguredWorkflow(workflow);
|
||||||
|
finalIds.add(workflow._id.toString());
|
||||||
|
} else {
|
||||||
|
// Insert if no _id
|
||||||
|
const insertResult = await createConfiguredWorkflow(workflow);
|
||||||
|
finalIds.add(insertResult.insertedId.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Delete any existing workflow (within these entityIds) that wasn't in the final list
|
||||||
|
await db.collection("configured-workflows").deleteMany({
|
||||||
|
_id: {
|
||||||
|
$nin: Array.from(finalIds).map((id) => new ObjectId(id)),
|
||||||
|
},
|
||||||
|
entityId: { $in: entityIds },
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user