From ab81a1753d167283c0b1d5cc5798d8a29f23949a Mon Sep 17 00:00:00 2001 From: Joao Correia Date: Sun, 26 Jan 2025 04:31:36 +0000 Subject: [PATCH] - Implement cloning of workflow - Entity change will now only clear the assignees instead of the whole workflow - Fix bug where side panel was showing all workflow assignees instead of just selected step assignees --- src/demo/approval_workflows.json | 69 +++++- src/pages/approval-workflows/[id]/clone.tsx | 260 ++++++++++++++++++++ src/pages/approval-workflows/[id]/index.tsx | 2 +- src/pages/approval-workflows/create.tsx | 36 +-- src/pages/approval-workflows/index.tsx | 16 +- 5 files changed, 349 insertions(+), 34 deletions(-) create mode 100644 src/pages/approval-workflows/[id]/clone.tsx diff --git a/src/demo/approval_workflows.json b/src/demo/approval_workflows.json index 7a804c8d..3f8c03e7 100644 --- a/src/demo/approval_workflows.json +++ b/src/demo/approval_workflows.json @@ -17,6 +17,7 @@ "completed": true, "completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0", "completedDate": 1737712243906, + "firstStep": true, "assignees": [ "fd5fce42-4bcc-4150-a143-b484e750b265", "231c84b2-a65a-49a9-803c-c664d84b13e0", @@ -28,11 +29,11 @@ "stepType": "approval-by", "stepNumber": 2, "completed": true, - "completedBy": "c5fc1514-1a94-4f8c-a046-a62099097a50", + "completedBy": "rTh9yz6Z1WOidHlVOSGInlpoxrk1", "completedDate": 1737712243906, "assignees": [ "fd5fce42-4bcc-4150-a143-b484e750b265", - "231c84b2-a65a-49a9-803c-c664d84b13e0", + "rTh9yz6Z1WOidHlVOSGInlpoxrk1", "c5fc1514-1a94-4f8c-a046-a62099097a50" ], "comments": "This is a random comment" @@ -53,9 +54,7 @@ "stepNumber": 4, "completed": false, "assignees": [ - "fd5fce42-4bcc-4150-a143-b484e750b265", - "231c84b2-a65a-49a9-803c-c664d84b13e0", - "c5fc1514-1a94-4f8c-a046-a62099097a50" + "fd5fce42-4bcc-4150-a143-b484e750b265" ], "comments": "This is a random comment" }, @@ -65,8 +64,7 @@ "completed": false, "finalStep": true, "assignees": [ - "fd5fce42-4bcc-4150-a143-b484e750b265", - "231c84b2-a65a-49a9-803c-c664d84b13e0", + "rTh9yz6Z1WOidHlVOSGInlpoxrk1", "c5fc1514-1a94-4f8c-a046-a62099097a50" ], "comments": "This is a random comment" @@ -94,6 +92,7 @@ "completed": true, "completedBy": "fd5fce42-4bcc-4150-a143-b484e750b265", "completedDate": 1737712243906, + "firstStep": true, "assignees": [ "fd5fce42-4bcc-4150-a143-b484e750b265", "231c84b2-a65a-49a9-803c-c664d84b13e0", @@ -105,11 +104,11 @@ "stepType": "approval-by", "stepNumber": 2, "completed": true, - "completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0", + "completedBy": "rTh9yz6Z1WOidHlVOSGInlpoxrk1", "completedDate": 1737712243906, "assignees": [ "fd5fce42-4bcc-4150-a143-b484e750b265", - "231c84b2-a65a-49a9-803c-c664d84b13e0", + "rTh9yz6Z1WOidHlVOSGInlpoxrk1", "c5fc1514-1a94-4f8c-a046-a62099097a50" ], "comments": "This is a random comment" @@ -134,9 +133,7 @@ "completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0", "completedDate": 1737712243906, "assignees": [ - "fd5fce42-4bcc-4150-a143-b484e750b265", - "231c84b2-a65a-49a9-803c-c664d84b13e0", - "c5fc1514-1a94-4f8c-a046-a62099097a50" + "fd5fce42-4bcc-4150-a143-b484e750b265" ], "comments": "This is a random comment" }, @@ -147,11 +144,59 @@ "completedBy": "c5fc1514-1a94-4f8c-a046-a62099097a50", "completedDate": 1737712243906, "finalStep": true, + "assignees": [ + "rTh9yz6Z1WOidHlVOSGInlpoxrk1", + "c5fc1514-1a94-4f8c-a046-a62099097a50" + ], + "comments": "This is a random comment" + } + ] + }, + { + "id": "bbbbkscbka-asacaca-acawesae", + "name": "English Exam 3rd Quarter 2025", + "entityId": "49ed2f0c-7d0d-46e4-9576-7cf19edc4980", + "modules": [ + "reading" + ], + "requester": "rTh9yz6Z1WOidHlVOSGInlpoxrk1", + "startDate": 1737712243906, + "status": "rejected", + "steps": [ + { + "stepType": "form-intake", + "stepNumber": 1, + "completed": true, + "completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0", + "completedDate": 1737712243906, + "firstStep": true, "assignees": [ "fd5fce42-4bcc-4150-a143-b484e750b265", "231c84b2-a65a-49a9-803c-c664d84b13e0", "c5fc1514-1a94-4f8c-a046-a62099097a50" ], + "comments": "This is a random comment\nThis is a random comment\nThis is a random comment\nThis is a random comment\nThis is a random comment\n" + }, + { + "stepType": "approval-by", + "stepNumber": 2, + "completed": true, + "completedBy": "rTh9yz6Z1WOidHlVOSGInlpoxrk1", + "completedDate": 1737712243906, + "assignees": [ + "rTh9yz6Z1WOidHlVOSGInlpoxrk1", + "c5fc1514-1a94-4f8c-a046-a62099097a50" + ], + "comments": "This is a random comment" + }, + { + "stepType": "approval-by", + "stepNumber": 3, + "completed": false, + "finalStep": true, + "assignees": [ + "rTh9yz6Z1WOidHlVOSGInlpoxrk1" + ], "comments": "This is a random comment" } ] diff --git a/src/pages/approval-workflows/[id]/clone.tsx b/src/pages/approval-workflows/[id]/clone.tsx new file mode 100644 index 00000000..43cf969d --- /dev/null +++ b/src/pages/approval-workflows/[id]/clone.tsx @@ -0,0 +1,260 @@ +import Tip from "@/components/ApprovalWorkflows/Tip"; +import WorkflowForm from "@/components/ApprovalWorkflows/WorkflowForm"; +import Layout from "@/components/High/Layout"; +import Button from "@/components/Low/Button"; +import Input from "@/components/Low/Input"; +import Select from "@/components/Low/Select"; +import { ApprovalWorkflow, EditableApprovalWorkflow, EditableWorkflowStep } from "@/interfaces/approval.workflow"; +import { Entity } from "@/interfaces/entity"; +import { CorporateUser, TeacherUser, User } from "@/interfaces/user"; +import { sessionOptions } from "@/lib/session"; +import { redirect, serialize } from "@/utils"; +import { requestUser } from "@/utils/api"; +import { getEntities } from "@/utils/entities.be"; +import { shouldRedirectHome } from "@/utils/navigation.disabled"; +import { getEntitiesUsers } from "@/utils/users.be"; +import { uuidv4 } from "@firebase/util"; +import { AnimatePresence, LayoutGroup, motion } from "framer-motion"; +import { withIronSessionSsr } from "iron-session/next"; +import Head from "next/head"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { BsChevronLeft } from "react-icons/bs"; +import { GrClearOption } from "react-icons/gr"; +import { ToastContainer } from "react-toastify"; +import approvalWorkflowsData from '../../../demo/approval_workflows.json'; // to test locally + +export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => { + const user = await requestUser(req, res); + if (!user) return redirect("/login") + + if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) + return redirect("/") + + const { id } = params as { id: string }; + + // replace later with await getApprovalWorkflow(id). + const approvalWorkflowsDataAsWorkflows = approvalWorkflowsData as ApprovalWorkflow[]; + const workflow: ApprovalWorkflow | undefined = approvalWorkflowsDataAsWorkflows.find(workflow => workflow.id === id); + + if (!workflow) + return redirect("/approval-workflows") + + const userEntitiesWithLabel = await getEntities(user.entities.map(entity => entity.id)); + + return { + props: serialize({ + user, + workflow, + userEntitiesWithLabel, + userEntitiesTeachers: await getEntitiesUsers(userEntitiesWithLabel.map(entity => entity.id), { type: "teacher" }) as TeacherUser[], + userEntitiesCorporates: await getEntitiesUsers(userEntitiesWithLabel.map(entity => entity.id), { type: "corporate" }) as CorporateUser[], + }), + }; +}, sessionOptions); + +interface Props { + user: User, + workflow: ApprovalWorkflow, + userEntitiesWithLabel: Entity[], + userEntitiesTeachers: TeacherUser[], + userEntitiesCorporates: CorporateUser[], +} + +export default function Home({ user, workflow, userEntitiesWithLabel, userEntitiesTeachers, userEntitiesCorporates }: Props) { + const [cloneWorkflow, setCloneWorkflow] = useState(null); + const [entityId, setEntityId] = useState(workflow.entityId); + const [entityTeachers, setEntityTeachers] = useState([]); + const [entityCorporates, setEntityCorporates] = useState([]); + + useEffect(() => { + if (entityId) { + setEntityTeachers( + userEntitiesTeachers.filter(teacher => + teacher.entities.some(entity => entity.id === entityId) + ) + ); + setEntityCorporates( + userEntitiesCorporates.filter(corporate => + corporate.entities.some(entity => entity.id === entityId) + ) + ); + } else { + setEntityTeachers([]); + setEntityCorporates([]); + } + }, [entityId, userEntitiesTeachers, userEntitiesCorporates]); + + const ENTITY_OPTIONS = userEntitiesWithLabel.map(entity => ({ + label: entity.label, + value: entity.id, + filter: (x: EditableApprovalWorkflow) => x.entityId === entity.id, + })); + + useEffect(() => { + const editableSteps: EditableWorkflowStep[] = workflow.steps.map(step => ({ + 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, + stepNumber: step.stepNumber, + assignees: step.assignees.map(id => id), + firstStep: step.firstStep || false, + finalStep: step.finalStep || false, + onDelete: undefined, + })); + + const editableWorkflow: EditableApprovalWorkflow = { + id: uuidv4(), + name: workflow.name, + entityId: workflow.entityId, + requester: user.id, + startDate: Date.now(), + modules: workflow.modules, + status: "pending", + steps: editableSteps, + }; + + setCloneWorkflow(editableWorkflow); + }, []); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + console.log("Form submitted! Values:", cloneWorkflow); + }; + + const onWorkflowChange = (wf: EditableApprovalWorkflow) => { + setCloneWorkflow(wf); + } + + const handleEntityChange = (wf: EditableApprovalWorkflow, entityId: string) => { + const updatedWorkflow = { + ...wf, + entityId: entityId, + steps: wf.steps.map(step => ({ + ...step, + assignees: step.assignees.map(() => null) + })) + } + onWorkflowChange(updatedWorkflow); + } + + const handleResetWorkflow = () => { + if (!confirm("This action will reset all the fields in this workflow. Are you sure you want to proceed?")) return; + const newId = uuidv4(); + const newWorkflow: EditableApprovalWorkflow = { + id: newId, + name: "", + entityId: "", + modules: [], + requester: user.id, + startDate: Date.now(), + status: "pending", + steps: [ + { key: 9998, stepType: "form-intake", stepNumber: 1, firstStep: true, finalStep: false, assignees: [null] }, + { key: 9999, stepType: "approval-by", stepNumber: 2, firstStep: false, finalStep: true, assignees: [null] }, + ], + }; + setCloneWorkflow(newWorkflow); + } + + return ( + <> + + Clone Workflow | EnCoach + + + + + + {user && ( + +
+ + + +

Clone Workflow

+
+ +
+ {cloneWorkflow && ( + <> +
+ { + const updatedWorkflow = { + ...cloneWorkflow, + name: updatedName, + }; + onWorkflowChange(updatedWorkflow); + }} + /> + { @@ -223,6 +230,7 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers }} />