use custom hook to rerender workflow instead of reloading full page.
This commit is contained in:
@@ -2,16 +2,16 @@ import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function useApprovalWorkflows() {
|
export default function useApprovalWorkflow(id: string) {
|
||||||
const [workflows, setWorkflows] = useState<ApprovalWorkflow[]>([]);
|
const [workflow, setWorkflow] = useState<ApprovalWorkflow>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isError, setIsError] = useState(false);
|
const [isError, setIsError] = useState(false);
|
||||||
|
|
||||||
const getData = useCallback(() => {
|
const getData = useCallback(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
axios
|
axios
|
||||||
.get<ApprovalWorkflow[]>(`/api/approval-workflows`)
|
.get<ApprovalWorkflow>(`/api/approval-workflows/${id}`)
|
||||||
.then((response) => setWorkflows(response.data))
|
.then((response) => setWorkflow(response.data))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setIsError(true);
|
setIsError(true);
|
||||||
})
|
})
|
||||||
@@ -20,5 +20,5 @@ export default function useApprovalWorkflows() {
|
|||||||
|
|
||||||
useEffect(getData, [getData]);
|
useEffect(getData, [getData]);
|
||||||
|
|
||||||
return { workflows, isLoading, isError, reload: getData };
|
return { workflow, isLoading, isError, reload: getData };
|
||||||
}
|
}
|
||||||
@@ -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 { deleteApprovalWorkflow, getApprovalWorkflow, updateApprovalWorkflow } from "@/utils/approval.workflows.be";
|
||||||
import { withIronSessionApiRoute } from "iron-session/next";
|
import { withIronSessionApiRoute } from "iron-session/next";
|
||||||
import { ObjectId } from "mongodb";
|
import { ObjectId } from "mongodb";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
@@ -12,6 +12,7 @@ export default withIronSessionApiRoute(handler, sessionOptions);
|
|||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method === "DELETE") return await del(req, res);
|
if (req.method === "DELETE") return await del(req, res);
|
||||||
if (req.method === "PUT") return await put(req, res);
|
if (req.method === "PUT") return await put(req, res);
|
||||||
|
if (req.method === "GET") return await get(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function del(req: NextApiRequest, res: NextApiResponse) {
|
async function del(req: NextApiRequest, res: NextApiResponse) {
|
||||||
@@ -44,3 +45,18 @@ async function put(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
return res.status(204).end();
|
return res.status(204).end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function get(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 { id } = req.query as { id?: string };
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
return res.status(200).json(await getApprovalWorkflow("configured-workflows", id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import UserWithProfilePic from "@/components/ApprovalWorkflows/UserWithProfilePi
|
|||||||
import WorkflowStepComponent from "@/components/ApprovalWorkflows/WorkflowStepComponent";
|
import WorkflowStepComponent from "@/components/ApprovalWorkflows/WorkflowStepComponent";
|
||||||
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 useApprovalWorkflow from "@/hooks/useApprovalWorkflow";
|
||||||
import { ApprovalWorkflow, getUserTypeLabelShort, WorkflowStep } from "@/interfaces/approval.workflow";
|
import { ApprovalWorkflow, getUserTypeLabelShort, WorkflowStep } from "@/interfaces/approval.workflow";
|
||||||
import { User } from "@/interfaces/user";
|
import { User } from "@/interfaces/user";
|
||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
@@ -19,7 +20,6 @@ import { AnimatePresence, LayoutGroup, 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 { 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";
|
||||||
@@ -57,7 +57,8 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }
|
|||||||
return {
|
return {
|
||||||
props: serialize({
|
props: serialize({
|
||||||
user,
|
user,
|
||||||
workflow,
|
initialWorkflow: workflow,
|
||||||
|
id,
|
||||||
workflowAssignees: await getSpecificUsers(allAssigneeIds),
|
workflowAssignees: await getSpecificUsers(allAssigneeIds),
|
||||||
workflowRequester: await getUser(workflow.requester),
|
workflowRequester: await getUser(workflow.requester),
|
||||||
}),
|
}),
|
||||||
@@ -66,25 +67,26 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: User,
|
user: User,
|
||||||
workflow: ApprovalWorkflow,
|
initialWorkflow: ApprovalWorkflow,
|
||||||
|
id: string,
|
||||||
workflowAssignees: User[],
|
workflowAssignees: User[],
|
||||||
workflowRequester: User,
|
workflowRequester: User,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Home({ user, workflow, workflowAssignees, workflowRequester }: Props) {
|
export default function Home({ user, initialWorkflow, id, workflowAssignees, workflowRequester }: Props) {
|
||||||
const steps = workflow.steps;
|
|
||||||
|
|
||||||
let currentStep = steps.findIndex(step => !step.completed || step.rejected);
|
const { workflow, reload, isLoading } = useApprovalWorkflow(id);
|
||||||
|
|
||||||
|
const currentWorkflow = workflow || initialWorkflow;
|
||||||
|
|
||||||
|
let currentStep = currentWorkflow.steps.findIndex(step => !step.completed || step.rejected);
|
||||||
if (currentStep === -1)
|
if (currentStep === -1)
|
||||||
currentStep = steps.length - 1;
|
currentStep = currentWorkflow.steps.length - 1;
|
||||||
|
|
||||||
const [selectedStepIndex, setSelectedStepIndex] = useState<number>(currentStep);
|
const [selectedStepIndex, setSelectedStepIndex] = useState<number>(currentStep);
|
||||||
const [selectedStep, setSelectedStep] = useState<WorkflowStep>(steps[selectedStepIndex]);
|
const [selectedStep, setSelectedStep] = useState<WorkflowStep>(currentWorkflow.steps[selectedStepIndex]);
|
||||||
const [isPanelOpen, setIsPanelOpen] = useState(true);
|
const [isPanelOpen, setIsPanelOpen] = useState(true);
|
||||||
const [comments, setComments] = useState<string>(selectedStep.comments || "");
|
const [comments, setComments] = useState<string>(selectedStep.comments || "");
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
||||||
const [isRedirecting, setIsRedirecting] = useState<boolean>(false);
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const handleStepClick = (index: number, stepInfo: WorkflowStep) => {
|
const handleStepClick = (index: number, stepInfo: WorkflowStep) => {
|
||||||
setSelectedStep(stepInfo);
|
setSelectedStep(stepInfo);
|
||||||
@@ -94,11 +96,9 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveComments = () => {
|
const handleSaveComments = () => {
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const updatedWorkflow: ApprovalWorkflow = {
|
const updatedWorkflow: ApprovalWorkflow = {
|
||||||
...workflow,
|
...currentWorkflow,
|
||||||
steps: workflow.steps.map((step, index) =>
|
steps: currentWorkflow.steps.map((step, index) =>
|
||||||
index === selectedStepIndex ?
|
index === selectedStepIndex ?
|
||||||
{
|
{
|
||||||
...step,
|
...step,
|
||||||
@@ -109,11 +109,10 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
};
|
};
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.put(`/api/approval-workflows/${workflow._id}`, updatedWorkflow)
|
.put(`/api/approval-workflows/${id}`, updatedWorkflow)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Comments saved successfully.");
|
toast.success("Comments saved successfully.");
|
||||||
setIsRedirecting(true);
|
reload();
|
||||||
router.reload();
|
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
if (reason.response.status === 401) {
|
if (reason.response.status === 401) {
|
||||||
@@ -123,19 +122,16 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
} else {
|
} else {
|
||||||
toast.error("Something went wrong, please try again later.");
|
toast.error("Something went wrong, please try again later.");
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
|
||||||
console.log("Submitted Values:", updatedWorkflow);
|
console.log("Submitted Values:", updatedWorkflow);
|
||||||
return;
|
return;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleApproveStep = () => {
|
const handleApproveStep = () => {
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const updatedWorkflow: ApprovalWorkflow = {
|
const updatedWorkflow: ApprovalWorkflow = {
|
||||||
...workflow,
|
...currentWorkflow,
|
||||||
status: selectedStepIndex === workflow.steps.length - 1 ? "approved" : "pending",
|
status: selectedStepIndex === currentWorkflow.steps.length - 1 ? "approved" : "pending",
|
||||||
steps: workflow.steps.map((step, index) =>
|
steps: currentWorkflow.steps.map((step, index) =>
|
||||||
index === selectedStepIndex ?
|
index === selectedStepIndex ?
|
||||||
{
|
{
|
||||||
...step,
|
...step,
|
||||||
@@ -148,11 +144,10 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
};
|
};
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.put(`/api/approval-workflows/${workflow._id}`, updatedWorkflow)
|
.put(`/api/approval-workflows/${id}`, updatedWorkflow)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Step approved successfully.");
|
toast.success("Step approved successfully.");
|
||||||
setIsRedirecting(true);
|
reload();
|
||||||
router.reload();
|
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
if (reason.response.status === 401) {
|
if (reason.response.status === 401) {
|
||||||
@@ -162,20 +157,20 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
} else {
|
} else {
|
||||||
toast.error("Something went wrong, please try again later.");
|
toast.error("Something went wrong, please try again later.");
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
|
||||||
console.log("Submitted Values:", updatedWorkflow);
|
console.log("Submitted Values:", updatedWorkflow);
|
||||||
return;
|
return;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
handleStepClick(selectedStepIndex + 1, currentWorkflow.steps[selectedStepIndex + 1]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRejectStep = () => {
|
const handleRejectStep = () => {
|
||||||
if (!confirm(`Are you sure you want to reject this step?`)) return;
|
if (!confirm(`Are you sure you want to reject this step?`)) return;
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const updatedWorkflow: ApprovalWorkflow = {
|
const updatedWorkflow: ApprovalWorkflow = {
|
||||||
...workflow,
|
...currentWorkflow,
|
||||||
status: "rejected",
|
status: "rejected",
|
||||||
steps: workflow.steps.map((step, index) =>
|
steps: currentWorkflow.steps.map((step, index) =>
|
||||||
index === selectedStepIndex ?
|
index === selectedStepIndex ?
|
||||||
{
|
{
|
||||||
...step,
|
...step,
|
||||||
@@ -189,11 +184,10 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
};
|
};
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.put(`/api/approval-workflows/${workflow._id}`, updatedWorkflow)
|
.put(`/api/approval-workflows/${id}`, updatedWorkflow)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Step rejected successfully.");
|
toast.success("Step rejected successfully.");
|
||||||
setIsRedirecting(true);
|
reload();
|
||||||
router.reload();
|
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
if (reason.response.status === 401) {
|
if (reason.response.status === 401) {
|
||||||
@@ -203,7 +197,6 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
} else {
|
} else {
|
||||||
toast.error("Something went wrong, please try again later.");
|
toast.error("Something went wrong, please try again later.");
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
|
||||||
console.log("Submitted Values:", updatedWorkflow);
|
console.log("Submitted Values:", updatedWorkflow);
|
||||||
return;
|
return;
|
||||||
})
|
})
|
||||||
@@ -220,7 +213,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title> {workflow.name} | EnCoach</title>
|
<title> Workflow | EnCoach</title>
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
|
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
|
||||||
@@ -238,7 +231,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl">
|
className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl">
|
||||||
<BsChevronLeft />
|
<BsChevronLeft />
|
||||||
</Link>
|
</Link>
|
||||||
<h1 className="text-2xl font-semibold">{workflow.name}</h1>
|
<h1 className="text-2xl font-semibold">{currentWorkflow.name}</h1>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="flex flex-col gap-6">
|
<section className="flex flex-col gap-6">
|
||||||
@@ -249,10 +242,10 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
profileImage={workflowRequester.profilePicture}
|
profileImage={workflowRequester.profilePicture}
|
||||||
/>
|
/>
|
||||||
<StartedOn
|
<StartedOn
|
||||||
date={workflow.startDate}
|
date={currentWorkflow.startDate}
|
||||||
/>
|
/>
|
||||||
<Status
|
<Status
|
||||||
status={workflow.status}
|
status={currentWorkflow.status}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row gap-3">
|
<div className="flex flex-row gap-3">
|
||||||
@@ -278,13 +271,13 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{steps.find((step) => !step.completed) === undefined &&
|
{currentWorkflow.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." />
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="flex flex-col gap-0">
|
<section className="flex flex-col gap-0">
|
||||||
{steps.map((step, index) => (
|
{currentWorkflow.steps.map((step, index) => (
|
||||||
<WorkflowStepComponent
|
<WorkflowStepComponent
|
||||||
workflowAssignees={workflowAssignees}
|
workflowAssignees={workflowAssignees}
|
||||||
key={index}
|
key={index}
|
||||||
@@ -294,7 +287,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
stepNumber={step.stepNumber}
|
stepNumber={step.stepNumber}
|
||||||
stepType={step.stepType}
|
stepType={step.stepType}
|
||||||
assignees={step.assignees}
|
assignees={step.assignees}
|
||||||
finalStep={index === steps.length - 1}
|
finalStep={index === currentWorkflow.steps.length - 1}
|
||||||
currentStep={index === currentStep}
|
currentStep={index === currentStep}
|
||||||
selected={index === selectedStepIndex}
|
selected={index === selectedStepIndex}
|
||||||
onClick={() => handleStepClick(index, step)}
|
onClick={() => handleStepClick(index, step)}
|
||||||
@@ -407,12 +400,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
padding="px-6 py-2"
|
padding="px-6 py-2"
|
||||||
className="mb-3 w-full text-lg flex items-center justify-center gap-2 text-left"
|
className="mb-3 w-full text-lg flex items-center justify-center gap-2 text-left"
|
||||||
>
|
>
|
||||||
{isRedirecting ? (
|
{isLoading ? (
|
||||||
<>
|
|
||||||
<FaSpinner className="animate-spin size-5" />
|
|
||||||
Reloading...
|
|
||||||
</>
|
|
||||||
) : isLoading ? (
|
|
||||||
<>
|
<>
|
||||||
<FaSpinner className="animate-spin size-5" />
|
<FaSpinner className="animate-spin size-5" />
|
||||||
Loading...
|
Loading...
|
||||||
@@ -433,12 +421,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
padding="px-6 py-2"
|
padding="px-6 py-2"
|
||||||
className="mb-3 w-1/2 text-lg flex items-center justify-center gap-2 text-left"
|
className="mb-3 w-1/2 text-lg flex items-center justify-center gap-2 text-left"
|
||||||
>
|
>
|
||||||
{isRedirecting ? (
|
{isLoading ? (
|
||||||
<>
|
|
||||||
<FaSpinner className="animate-spin size-5" />
|
|
||||||
Reloading...
|
|
||||||
</>
|
|
||||||
) : isLoading ? (
|
|
||||||
<>
|
<>
|
||||||
<FaSpinner className="animate-spin size-5" />
|
<FaSpinner className="animate-spin size-5" />
|
||||||
Loading...
|
Loading...
|
||||||
@@ -471,12 +454,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
|||||||
padding="px-6 py-2"
|
padding="px-6 py-2"
|
||||||
className="mt-6 mb-3 w-full text-lg flex items-center justify-center gap-2 text-left"
|
className="mt-6 mb-3 w-full text-lg flex items-center justify-center gap-2 text-left"
|
||||||
>
|
>
|
||||||
{isRedirecting ? (
|
{isLoading ? (
|
||||||
<>
|
|
||||||
<FaSpinner className="animate-spin size-5" />
|
|
||||||
Reloading...
|
|
||||||
</>
|
|
||||||
) : isLoading ? (
|
|
||||||
<>
|
<>
|
||||||
<FaSpinner className="animate-spin size-5" />
|
<FaSpinner className="animate-spin size-5" />
|
||||||
Loading...
|
Loading...
|
||||||
|
|||||||
Reference in New Issue
Block a user