From a0229cd971c642e810e315cf27bc8dfe4567da33 Mon Sep 17 00:00:00 2001 From: Joao Correia Date: Fri, 31 Jan 2025 20:56:40 +0000 Subject: [PATCH] implement rejection of steps --- .../WorkflowStepComponent.tsx | 104 +++++-------- .../ApprovalWorkflows/WorkflowStepNumber.tsx | 10 +- src/interfaces/approval.workflow.ts | 1 + src/pages/approval-workflows/[id]/clone.tsx | 6 +- src/pages/approval-workflows/[id]/edit.tsx | 14 +- src/pages/approval-workflows/[id]/index.tsx | 142 +++++++++++++----- src/pages/approval-workflows/create.tsx | 6 +- src/pages/approval-workflows/index.tsx | 13 +- 8 files changed, 170 insertions(+), 126 deletions(-) diff --git a/src/components/ApprovalWorkflows/WorkflowStepComponent.tsx b/src/components/ApprovalWorkflows/WorkflowStepComponent.tsx index 8ae7d862..75681040 100644 --- a/src/components/ApprovalWorkflows/WorkflowStepComponent.tsx +++ b/src/components/ApprovalWorkflows/WorkflowStepComponent.tsx @@ -17,6 +17,7 @@ export default function WorkflowStepComponent({ stepType, stepNumber, completed, + rejected = false, completedBy, assignees, finalStep, @@ -31,11 +32,12 @@ export default function WorkflowStepComponent({
- + {/* Vertical Bar connecting steps */} {!finalStep && ( @@ -53,71 +55,45 @@ export default function WorkflowStepComponent({
- {stepType === "form-intake" ? ( - <> -

Form: Intake

- {completed && completedBy && ( -
- -
- )} - {!completed && currentStep && ( -
- In Progress... Assignees: -
- {assigneesUsers.map(user => ( - - - - ))} -
-
- )} - - ) : ( - stepType === "approval-by" && ( - <> - {completed && completedBy ? ( -
-

Approval: {getUserTypeLabel(completedByUser!.type)} Approval

+ {completed && completedBy && rejected ? ( +
+

{stepType === "approval-by" ? `Approval: ${getUserTypeLabel(completedByUser!.type)} Approval` : `Form Intake: ${getUserTypeLabel(completedByUser!.type)} Intake`}

+ +
+ ) : completed && completedBy && !rejected ? ( +
+

{stepType === "approval-by" ? `Approval: ${getUserTypeLabel(completedByUser!.type)} Approval` : `Form Intake: ${getUserTypeLabel(completedByUser!.type)} Intake`}

+ +
+ ) : !completed && currentStep ? ( +
+

{stepType === "approval-by" ? `Approval:` : `Form Intake:`}

+ In Progress... Assignees: +
+ {assigneesUsers.map(user => ( + -
- ) : !completed && currentStep ? ( -
-

Approval:

- In Progress... Assignees: -
- {assigneesUsers.map(user => ( - - - - ))} -
-
- ) : ( -
-

Approval:

- Waiting for previous steps... -
- )} - - ) + + ))} +
+
+ ) : ( +
+

{stepType === "approval-by" ? `Approval:` : `Form Intake:`}

+ Waiting for previous steps... +
)}
diff --git a/src/components/ApprovalWorkflows/WorkflowStepNumber.tsx b/src/components/ApprovalWorkflows/WorkflowStepNumber.tsx index a1d6a900..842ecc6c 100644 --- a/src/components/ApprovalWorkflows/WorkflowStepNumber.tsx +++ b/src/components/ApprovalWorkflows/WorkflowStepNumber.tsx @@ -1,21 +1,25 @@ import { WorkflowStep } from "@/interfaces/approval.workflow"; import clsx from "clsx"; import { IoCheckmarkDoneSharp, IoCheckmarkSharp } from "react-icons/io5"; +import { RxCross2 } from "react-icons/rx"; -type Props = Pick +type Props = Pick -export default function WorkflowStepNumber({ stepNumber, selected = false, completed, finalStep }: Props) { +export default function WorkflowStepNumber({ stepNumber, selected = false, completed, rejected, finalStep }: Props) { return (
- {completed && finalStep ? ( + {rejected ? ( + + ) : completed && finalStep ? ( ) : completed && !finalStep ? ( diff --git a/src/interfaces/approval.workflow.ts b/src/interfaces/approval.workflow.ts index 5c7dacf2..f5563e58 100644 --- a/src/interfaces/approval.workflow.ts +++ b/src/interfaces/approval.workflow.ts @@ -28,6 +28,7 @@ export interface WorkflowStep { stepType: StepType, stepNumber: number, completed: boolean, + rejected?: boolean, completedBy?: User["id"], completedDate?: number, assignees: (User["id"])[]; diff --git a/src/pages/approval-workflows/[id]/clone.tsx b/src/pages/approval-workflows/[id]/clone.tsx index f91a83da..8baaaf36 100644 --- a/src/pages/approval-workflows/[id]/clone.tsx +++ b/src/pages/approval-workflows/[id]/clone.tsx @@ -47,7 +47,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } user, workflow, 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)[], }), }; }, sessionOptions); @@ -134,9 +134,7 @@ export default function Home({ user, workflow, userEntitiesWithLabel, userEntiti .then(() => { toast.success("Approval Workflow cloned successfully."); setIsRedirecting(true); - setTimeout(() => { - router.push("/approval-workflows"); - }, 1000); + router.push("/approval-workflows"); }) .catch((reason) => { if (reason.response.status === 401) { diff --git a/src/pages/approval-workflows/[id]/edit.tsx b/src/pages/approval-workflows/[id]/edit.tsx index 7c9a1d83..1677f3ca 100644 --- a/src/pages/approval-workflows/[id]/edit.tsx +++ b/src/pages/approval-workflows/[id]/edit.tsx @@ -39,7 +39,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } props: serialize({ user, workflow, - workflowEntityApprovers: await getEntityUsers(workflow.entityId, undefined, { type: {$in: ["teacher", "corporate", "mastercorporate", "developer"]} }) as (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[], + workflowEntityApprovers: await getEntityUsers(workflow.entityId, undefined, { type: { $in: ["teacher", "corporate", "mastercorporate", "developer"] } }) as (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[], }), }; }, sessionOptions); @@ -54,7 +54,7 @@ export default function Home({ user, workflow, workflowEntityApprovers }: Props) const [updatedWorkflow, setUpdatedWorkflow] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isRedirecting, setIsRedirecting] = useState(false); - + const router = useRouter(); useEffect(() => { @@ -86,10 +86,10 @@ export default function Home({ user, workflow, workflowEntityApprovers }: Props) e.preventDefault(); setIsLoading(true); - if (!updatedWorkflow){ + if (!updatedWorkflow) { setIsLoading(false); return; - } + } const filteredWorkflow: ApprovalWorkflow = { ...updatedWorkflow, @@ -99,15 +99,13 @@ export default function Home({ user, workflow, workflowEntityApprovers }: Props) assignees: step.assignees.filter((assignee): assignee is string => assignee !== null && assignee !== undefined) })) }; - + axios .put(`/api/approval-workflows/${updatedWorkflow.id}/edit`, filteredWorkflow) .then(() => { toast.success("Approval Workflow edited successfully."); setIsRedirecting(true); - setTimeout(() => { - router.push("/approval-workflows"); - }, 1000); + router.push("/approval-workflows"); }) .catch((reason) => { if (reason.response.status === 401) { diff --git a/src/pages/approval-workflows/[id]/index.tsx b/src/pages/approval-workflows/[id]/index.tsx index 60be5360..a510f033 100644 --- a/src/pages/approval-workflows/[id]/index.tsx +++ b/src/pages/approval-workflows/[id]/index.tsx @@ -28,6 +28,7 @@ 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"; export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => { const user = await requestUser(req, res); @@ -71,7 +72,7 @@ interface Props { export default function Home({ user, workflow, workflowAssignees, workflowRequester }: Props) { const steps = workflow.steps; - let currentStep = steps.findIndex(step => !step.completed); + let currentStep = steps.findIndex(step => !step.completed || step.rejected); if (currentStep === -1) currentStep = steps.length - 1; @@ -110,9 +111,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques .then(() => { toast.success("Comments saved successfully."); setIsRedirecting(true); - setTimeout(() => { - router.reload(); - }, 1000); + router.reload(); }) .catch((reason) => { if (reason.response.status === 401) { @@ -129,7 +128,6 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques }; const handleApproveStep = () => { - if (!confirm(`Are you sure you want to approve this step?`)) return; setIsLoading(true); const updatedWorkflow: ApprovalWorkflow = { @@ -151,9 +149,48 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques .then(() => { toast.success("Step approved successfully."); setIsRedirecting(true); - setTimeout(() => { - router.reload(); - }, 1000); + router.reload(); + }) + .catch((reason) => { + if (reason.response.status === 401) { + toast.error("Not logged in!"); + } else if (reason.response.status === 403) { + toast.error("You do not have permission to approve this step!"); + } else { + toast.error("Something went wrong, please try again later."); + } + setIsLoading(false); + console.log("Submitted Values:", updatedWorkflow); + return; + }) + }; + + const handleRejectStep = () => { + if (!confirm(`Are you sure you want to reject this step?`)) return; + setIsLoading(true); + + const updatedWorkflow: ApprovalWorkflow = { + ...workflow, + status: "rejected", + steps: workflow.steps.map((step, index) => + index === selectedStepIndex ? + { + ...step, + completed: true, + completedBy: user.id, + completedDate: Date.now(), + rejected: true, + } + : step + ) + }; + + axios + .put(`/api/approval-workflows/${workflow._id}`, updatedWorkflow) + .then(() => { + toast.success("Step rejected successfully."); + setIsRedirecting(true); + router.reload(); }) .catch((reason) => { if (reason.response.status === 401) { @@ -219,6 +256,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques key={index} completed={step.completed} completedBy={step.completedBy} + rejected={step.rejected} stepNumber={step.stepNumber} stepType={step.stepType} assignees={step.assignees} @@ -276,7 +314,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques {selectedStep.completed ? (
- Approved on {new Date(selectedStep.completedDate!).toLocaleString("en-CA", { + {selectedStep.rejected ? "Rejected" : "Approved"} on {new Date(selectedStep.completedDate!).toLocaleString("en-CA", { year: "numeric", month: "2-digit", day: "2-digit", @@ -286,7 +324,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques hour12: false, }).replace(", ", " at ")}
-

Approved by:

+

{selectedStep.rejected ? "Rejected" : "Approved"} by:

{(() => { const assignee = workflowAssignees.find( (assignee) => assignee.id === selectedStep.completedBy @@ -324,33 +362,61 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
)} - {selectedStepIndex === currentStep && !selectedStep.completed && - + {selectedStepIndex === currentStep && !selectedStep.completed && !selectedStep.rejected && +
+ + +
}
@@ -361,7 +427,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques placeholder="Input comments here" className="w-full h-64 p-2 border-2 rounded-xl shadow-lg focus:border-mti-purple focus:outline-none mt-3 resize-none" /> - +