implement rejection of steps

This commit is contained in:
Joao Correia
2025-01-31 20:56:40 +00:00
parent 662e3b0266
commit a0229cd971
8 changed files with 170 additions and 126 deletions

View File

@@ -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) {

View File

@@ -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<EditableApprovalWorkflow | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isRedirecting, setIsRedirecting] = useState<boolean>(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) {

View File

@@ -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 ? (
<div className={"text-base font-medium text-gray-500 flex flex-col gap-6"}>
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 ")}
<div className="flex flex-row gap-1 text-sm">
<p className="text-base">Approved by:</p>
<p className="text-base">{selectedStep.rejected ? "Rejected" : "Approved"} by:</p>
{(() => {
const assignee = workflowAssignees.find(
(assignee) => assignee.id === selectedStep.completedBy
@@ -324,33 +362,61 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
</div>
)}
{selectedStepIndex === currentStep && !selectedStep.completed &&
<Button
type="submit"
color="purple"
variant="solid"
disabled={!selectedStep.assignees.includes(user.id) || isLoading}
onClick={handleApproveStep}
padding="px-6 py-2"
className="mb-3 w-full text-lg flex items-center justify-center gap-2 text-left"
>
{isRedirecting ? (
<>
<FaSpinner className="animate-spin size-5" />
Reloading...
</>
) : isLoading ? (
<>
<FaSpinner className="animate-spin size-5" />
Loading...
</>
) : (
<>
<IoMdCheckmarkCircleOutline size={20} />
Approve Step
</>
)}
</Button>
{selectedStepIndex === currentStep && !selectedStep.completed && !selectedStep.rejected &&
<div className="flex flex-row gap-2 ">
<Button
type="submit"
color="purple"
variant="solid"
disabled={!selectedStep.assignees.includes(user.id) || isLoading}
onClick={handleApproveStep}
padding="px-6 py-2"
className="mb-3 w-full text-lg flex items-center justify-center gap-2 text-left"
>
{isRedirecting ? (
<>
<FaSpinner className="animate-spin size-5" />
Reloading...
</>
) : isLoading ? (
<>
<FaSpinner className="animate-spin size-5" />
Loading...
</>
) : (
<>
<IoMdCheckmarkCircleOutline size={20} />
Approve Step
</>
)}
</Button>
<Button
type="submit"
color="red"
variant="solid"
disabled={!selectedStep.assignees.includes(user.id) || isLoading}
onClick={handleRejectStep}
padding="px-6 py-2"
className="mb-3 w-1/2 text-lg flex items-center justify-center gap-2 text-left"
>
{isRedirecting ? (
<>
<FaSpinner className="animate-spin size-5" />
Reloading...
</>
) : isLoading ? (
<>
<FaSpinner className="animate-spin size-5" />
Loading...
</>
) : (
<>
<RxCrossCircled size={20} />
Reject Step
</>
)}
</Button>
</div>
}
<hr className="my-4 h-[4px] bg-mti-purple-ultralight rounded-full w-full" />
@@ -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"
/>
<Button
type="submit"
color="purple"