implement edit active workflow and do not allow editing on already completed steps
This commit is contained in:
@@ -12,6 +12,7 @@ import WorkflowStepSelects from "./WorkflowStepSelects";
|
|||||||
interface Props extends Pick<EditableWorkflowStep, 'stepNumber' | 'assignees' | 'finalStep' | 'onDelete'> {
|
interface Props extends Pick<EditableWorkflowStep, 'stepNumber' | 'assignees' | 'finalStep' | 'onDelete'> {
|
||||||
entityApprovers: (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[];
|
entityApprovers: (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[];
|
||||||
onSelectChange: (numberOfSelects: number, index: number, value: Option | null) => void;
|
onSelectChange: (numberOfSelects: number, index: number, value: Option | null) => void;
|
||||||
|
isCompleted: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WorkflowEditableStepComponent({
|
export default function WorkflowEditableStepComponent({
|
||||||
@@ -21,6 +22,7 @@ export default function WorkflowEditableStepComponent({
|
|||||||
onDelete,
|
onDelete,
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
entityApprovers,
|
entityApprovers,
|
||||||
|
isCompleted,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
|
||||||
const [selects, setSelects] = useState<(Option | null | undefined)[]>([null]);
|
const [selects, setSelects] = useState<(Option | null | undefined)[]>([null]);
|
||||||
@@ -95,7 +97,7 @@ export default function WorkflowEditableStepComponent({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{stepNumber !== 1 && !finalStep
|
{stepNumber !== 1 && !finalStep && !isCompleted
|
||||||
? <LuGripHorizontal className="ml-3 mt-2 cursor-grab active:cursor-grabbing min-w-[25px] min-h-[25px]" />
|
? <LuGripHorizontal className="ml-3 mt-2 cursor-grab active:cursor-grabbing min-w-[25px] min-h-[25px]" />
|
||||||
: <div className="ml-3 mt-2" style={{ width: 25, height: 25 }}></div>
|
: <div className="ml-3 mt-2" style={{ width: 25, height: 25 }}></div>
|
||||||
}
|
}
|
||||||
@@ -106,6 +108,7 @@ export default function WorkflowEditableStepComponent({
|
|||||||
selects={selects}
|
selects={selects}
|
||||||
placeholder={stepNumber === 1 ? "Form Intake By:" : "Approval By:"}
|
placeholder={stepNumber === 1 ? "Form Intake By:" : "Approval By:"}
|
||||||
onSelectChange={handleSelectChangeAt}
|
onSelectChange={handleSelectChangeAt}
|
||||||
|
isCompleted={isCompleted}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { EditableApprovalWorkflow, EditableWorkflowStep } from "@/interfaces/approval.workflow";
|
import { EditableApprovalWorkflow, EditableWorkflowStep } from "@/interfaces/approval.workflow";
|
||||||
import Option from "@/interfaces/option";
|
import Option from "@/interfaces/option";
|
||||||
import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser } from "@/interfaces/user";
|
import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser } from "@/interfaces/user";
|
||||||
import { AnimatePresence, Reorder } from "framer-motion";
|
import { AnimatePresence, Reorder, motion } from "framer-motion";
|
||||||
import { useState } from "react";
|
|
||||||
import { FaRegCheckCircle, FaSpinner } from "react-icons/fa";
|
import { FaRegCheckCircle, FaSpinner } from "react-icons/fa";
|
||||||
import { IoIosAddCircleOutline } from "react-icons/io";
|
import { IoIosAddCircleOutline } from "react-icons/io";
|
||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
@@ -13,9 +12,9 @@ interface Props {
|
|||||||
workflow: EditableApprovalWorkflow;
|
workflow: EditableApprovalWorkflow;
|
||||||
onWorkflowChange: (workflow: EditableApprovalWorkflow) => void;
|
onWorkflowChange: (workflow: EditableApprovalWorkflow) => void;
|
||||||
entityApprovers: (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[];
|
entityApprovers: (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[];
|
||||||
entityAvailableFormIntakers: (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[];
|
entityAvailableFormIntakers?: (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isRedirecting: boolean;
|
isRedirecting?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WorkflowForm({ workflow, onWorkflowChange, entityApprovers, entityAvailableFormIntakers, isLoading, isRedirecting }: Props) {
|
export default function WorkflowForm({ workflow, onWorkflowChange, entityApprovers, entityAvailableFormIntakers, isLoading, isRedirecting }: Props) {
|
||||||
@@ -75,19 +74,15 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityApprove
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleReorder = (newOrder: EditableWorkflowStep[]) => {
|
const handleReorder = (newOrder: EditableWorkflowStep[]) => {
|
||||||
const firstIndex = newOrder.findIndex((s) => s.firstStep);
|
let draggableIndex = 0;
|
||||||
if (firstIndex !== -1 && firstIndex !== 0) {
|
const updatedSteps = workflow.steps.map((step) => {
|
||||||
const [first] = newOrder.splice(firstIndex, 1);
|
if (!step.firstStep && !step.finalStep && !step.completed) {
|
||||||
newOrder.unshift(first);
|
return newOrder[draggableIndex++];
|
||||||
}
|
}
|
||||||
|
// Keep static steps as-is
|
||||||
const finalIndex = newOrder.findIndex((s) => s.finalStep);
|
return step;
|
||||||
if (finalIndex !== -1 && finalIndex !== newOrder.length - 1) {
|
});
|
||||||
const [final] = newOrder.splice(finalIndex, 1);
|
onWorkflowChange({ ...workflow, steps: renumberSteps(updatedSteps) });
|
||||||
newOrder.push(final);
|
|
||||||
}
|
|
||||||
|
|
||||||
onWorkflowChange({ ...workflow, steps: renumberSteps(newOrder) });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -118,55 +113,87 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityApprove
|
|||||||
className="flex flex-col gap-0"
|
className="flex flex-col gap-0"
|
||||||
>
|
>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{workflow.steps.map((step, index) => (
|
{workflow.steps.map((step, index) =>
|
||||||
<Reorder.Item
|
step.completed || step.firstStep || step.finalStep ? (
|
||||||
key={step.key}
|
<motion.div
|
||||||
value={step}
|
key={step.key}
|
||||||
initial={{ opacity: 0, y: -30 }}
|
layout
|
||||||
animate={{ opacity: 1, y: 0 }}
|
initial={{ opacity: 0, y: -30 }}
|
||||||
exit={{ opacity: 0, x: 30 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.20 }}
|
exit={{ opacity: 0, x: 30 }}
|
||||||
layout
|
transition={{ duration: 0.20 }}
|
||||||
drag={!(step.firstStep || step.finalStep)}
|
>
|
||||||
>
|
<WorkflowEditableStepComponent
|
||||||
|
stepNumber={index + 1}
|
||||||
<WorkflowEditableStepComponent
|
assignees={step.assignees}
|
||||||
stepNumber={index + 1}
|
finalStep={step.finalStep}
|
||||||
assignees={step.assignees}
|
onDelete={() => handleDelete(step.key)}
|
||||||
finalStep={step.finalStep}
|
onSelectChange={(numberOfSelects, idx, option) =>
|
||||||
onDelete={() => handleDelete(step.key)}
|
handleSelectChange(step.key, numberOfSelects, idx, option)
|
||||||
onSelectChange={(numberOfSelects, idx, option) => handleSelectChange(step.key, numberOfSelects, idx, option)}
|
}
|
||||||
entityApprovers={step.stepNumber === 1 ? entityAvailableFormIntakers : entityApprovers}
|
entityApprovers={
|
||||||
/>
|
step.stepNumber === 1 && entityAvailableFormIntakers
|
||||||
|
? entityAvailableFormIntakers
|
||||||
{step.finalStep &&
|
: entityApprovers
|
||||||
<Button
|
}
|
||||||
type="submit"
|
isCompleted={step.completed}
|
||||||
color="purple"
|
/>
|
||||||
variant="solid"
|
</motion.div>
|
||||||
disabled={isLoading}
|
) : (
|
||||||
className="max-w-fit text-lg font-medium flex items-center gap-2 text-left -mt-4"
|
// Render non-completed steps as draggable items
|
||||||
>
|
<Reorder.Item
|
||||||
{isRedirecting ? (
|
key={step.key}
|
||||||
<>
|
value={step}
|
||||||
<FaSpinner className="animate-spin size-5" />
|
initial={{ opacity: 0, y: -30 }}
|
||||||
Redirecting...
|
animate={{ opacity: 1, y: 0 }}
|
||||||
</>
|
exit={{ opacity: 0, x: 30 }}
|
||||||
) : isLoading ? (
|
transition={{ duration: 0.20 }}
|
||||||
<>
|
layout
|
||||||
<FaSpinner className="animate-spin size-5" />
|
drag={!step.firstStep && !step.finalStep}
|
||||||
Loading...
|
dragListener={!step.firstStep && !step.finalStep}
|
||||||
</>
|
>
|
||||||
) : (
|
<WorkflowEditableStepComponent
|
||||||
<>
|
stepNumber={index + 1}
|
||||||
<FaRegCheckCircle className="size-5" />
|
assignees={step.assignees}
|
||||||
Confirm Exam Workflow Pipeline
|
finalStep={step.finalStep}
|
||||||
</>
|
onDelete={() => handleDelete(step.key)}
|
||||||
)}
|
onSelectChange={(numberOfSelects, idx, option) =>
|
||||||
</Button>
|
handleSelectChange(step.key, numberOfSelects, idx, option)
|
||||||
}
|
}
|
||||||
</Reorder.Item>
|
entityApprovers={
|
||||||
))}
|
step.stepNumber === 1 && entityAvailableFormIntakers
|
||||||
|
? entityAvailableFormIntakers
|
||||||
|
: entityApprovers
|
||||||
|
}
|
||||||
|
isCompleted={step.completed}
|
||||||
|
/>
|
||||||
|
</Reorder.Item>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
color="purple"
|
||||||
|
variant="solid"
|
||||||
|
disabled={isLoading}
|
||||||
|
className="max-w-fit text-lg font-medium flex items-center gap-2 text-left -mt-4"
|
||||||
|
>
|
||||||
|
{isRedirecting ? (
|
||||||
|
<>
|
||||||
|
<FaSpinner className="animate-spin size-5" />
|
||||||
|
Redirecting...
|
||||||
|
</>
|
||||||
|
) : isLoading ? (
|
||||||
|
<>
|
||||||
|
<FaSpinner className="animate-spin size-5" />
|
||||||
|
Loading...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FaRegCheckCircle className="size-5" />
|
||||||
|
Confirm Exam Workflow Pipeline
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</Reorder.Group>
|
</Reorder.Group>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface Props {
|
|||||||
selects: (Option | null | undefined)[];
|
selects: (Option | null | undefined)[];
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
onSelectChange: (numberOfSelects: number, index: number, value: Option | null) => void;
|
onSelectChange: (numberOfSelects: number, index: number, value: Option | null) => void;
|
||||||
|
isCompleted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WorkflowStepSelects({
|
export default function WorkflowStepSelects({
|
||||||
@@ -13,6 +14,7 @@ export default function WorkflowStepSelects({
|
|||||||
selects,
|
selects,
|
||||||
placeholder,
|
placeholder,
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
|
isCompleted,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -39,6 +41,7 @@ export default function WorkflowStepSelects({
|
|||||||
flat
|
flat
|
||||||
isClearable
|
isClearable
|
||||||
className={classes}
|
className={classes}
|
||||||
|
disabled={isCompleted}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
if (id && approvalWorkflow) {
|
if (id && approvalWorkflow) {
|
||||||
approvalWorkflow._id = new ObjectId(id);
|
approvalWorkflow._id = new ObjectId(id);
|
||||||
await updateApprovalWorkflow("configured-workflows", approvalWorkflow);
|
await updateApprovalWorkflow("active-workflows", approvalWorkflow);
|
||||||
return res.status(204).end();
|
return res.status(204).end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ import { getApprovalWorkflow } 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 axios from "axios";
|
||||||
import { motion } from "framer-motion";
|
import { 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 { 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";
|
||||||
@@ -30,7 +29,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("configured-workflows", id);
|
const workflow: ApprovalWorkflow | null = await getApprovalWorkflow("active-workflows", id);
|
||||||
|
|
||||||
if (!workflow)
|
if (!workflow)
|
||||||
return redirect("/approval-workflows")
|
return redirect("/approval-workflows")
|
||||||
@@ -53,9 +52,6 @@ interface Props {
|
|||||||
export default function Home({ user, workflow, workflowEntityApprovers }: Props) {
|
export default function Home({ user, workflow, workflowEntityApprovers }: Props) {
|
||||||
const [updatedWorkflow, setUpdatedWorkflow] = useState<EditableApprovalWorkflow | null>(null);
|
const [updatedWorkflow, setUpdatedWorkflow] = useState<EditableApprovalWorkflow | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [isRedirecting, setIsRedirecting] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const editableSteps: EditableWorkflowStep[] = workflow.steps.map(step => ({
|
const editableSteps: EditableWorkflowStep[] = workflow.steps.map(step => ({
|
||||||
@@ -114,8 +110,7 @@ export default function Home({ user, workflow, workflowEntityApprovers }: Props)
|
|||||||
.put(`/api/approval-workflows/${updatedWorkflow.id}/edit`, filteredWorkflow)
|
.put(`/api/approval-workflows/${updatedWorkflow.id}/edit`, filteredWorkflow)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Approval Workflow edited successfully.");
|
toast.success("Approval Workflow edited successfully.");
|
||||||
setIsRedirecting(true);
|
setIsLoading(false);
|
||||||
router.push("/approval-workflows");
|
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
if (reason.response.status === 401) {
|
if (reason.response.status === 401) {
|
||||||
@@ -175,23 +170,24 @@ export default function Home({ user, workflow, workflowEntityApprovers }: Props)
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<motion.div
|
<LayoutGroup key={workflow.name}>
|
||||||
key="form"
|
<motion.div
|
||||||
initial={{ opacity: 0, y: -30 }}
|
key="form"
|
||||||
animate={{ opacity: 1, y: 0 }}
|
initial={{ opacity: 0, y: -30 }}
|
||||||
exit={{ opacity: 0, x: 60 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.20 }}
|
exit={{ opacity: 0, x: 60 }}
|
||||||
>
|
transition={{ duration: 0.20 }}
|
||||||
{/* {updatedWorkflow &&
|
>
|
||||||
<WorkflowForm
|
{updatedWorkflow &&
|
||||||
workflow={updatedWorkflow}
|
<WorkflowForm
|
||||||
onWorkflowChange={onWorkflowChange}
|
workflow={updatedWorkflow}
|
||||||
entityApprovers={workflowEntityApprovers}
|
onWorkflowChange={onWorkflowChange}
|
||||||
isLoading={isLoading}
|
entityApprovers={workflowEntityApprovers}
|
||||||
isRedirecting={isRedirecting}
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
} */}
|
}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
</LayoutGroup>
|
||||||
</form>
|
</form>
|
||||||
</Layout>
|
</Layout>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user