Finish Approval Workflow builder for the most part. TODO: implement permissions

This commit is contained in:
Joao Correia
2025-01-24 00:33:45 +00:00
parent dcd25465fd
commit f6b0c96b3b
3 changed files with 80 additions and 37 deletions

View File

@@ -0,0 +1,14 @@
import { MdTipsAndUpdates } from "react-icons/md";
interface Props {
text: string;
}
export default function Tip({ text }: Props) {
return (
<div className="flex flex-row gap-3 text-gray-500 font-medium">
<MdTipsAndUpdates size={25} />
<p>{text}</p>
</div>
);
};

View File

@@ -1,19 +1,13 @@
import { ApprovalWorkflow, WorkflowStep } from "@/interfaces/approval.workflow";
import Option from "@/interfaces/option"; import Option from "@/interfaces/option";
import { AnimatePresence, Reorder } from "framer-motion"; import { CorporateUser, TeacherUser } from "@/interfaces/user";
import { useEffect, useState } from "react"; import { AnimatePresence, AnimateSharedLayout, Reorder, motion } from "framer-motion";
import { useState } from "react";
import { FaRegCheckCircle } from "react-icons/fa"; import { FaRegCheckCircle } 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";
import WorkflowEditableStepComponent from "./WorkflowEditableStepComponent"; import WorkflowEditableStepComponent from "./WorkflowEditableStepComponent";
import { ApprovalWorkflow, WorkflowStep } from "@/interfaces/approval.workflow"; import Tip from "./Tip";
import { CorporateUser, TeacherUser } from "@/interfaces/user";
// Variants for animating steps when they are added/removed
const itemVariants = {
initial: { opacity: 0, y: -20 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, x: 20 },
};
interface Props { interface Props {
workflow: ApprovalWorkflow; workflow: ApprovalWorkflow;
@@ -99,17 +93,26 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityTeacher
return ( return (
<> <>
{workflow.entityId && workflow.name && {workflow.entityId && workflow.name &&
<div className="flex flex-col gap-6"> <div>
<Button <motion.div
color="purple" className="flex flex-col gap-6"
variant="solid" initial={{ opacity: 0, y: 20 }}
onClick={addStep} animate={{ opacity: 1, y: 0 }}
type="button" exit={{ opacity: 0, x: 20 }}
className="max-w-fit text-lg font-medium flex items-center gap-2 text-left mb-9" transition={{ duration: 0.3 }}
> >
<IoIosAddCircleOutline className="size-6" /> <Tip text="Introduce here all the steps associated with this instance." />
Add Step <Button
</Button> color="purple"
variant="solid"
onClick={addStep}
type="button"
className="max-w-fit text-lg font-medium flex items-center gap-2 text-left mb-7"
>
<IoIosAddCircleOutline className="size-6" />
Add Step
</Button>
</motion.div>
<Reorder.Group <Reorder.Group
axis="y" axis="y"
@@ -122,10 +125,9 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityTeacher
<Reorder.Item <Reorder.Item
key={step.key} key={step.key}
value={step} value={step}
variants={itemVariants} initial={{ opacity: 0, y: 20 }}
initial="initial" animate={{ opacity: 1, y: 0 }}
animate="animate" exit={{ opacity: 0, x: 20 }}
exit="exit"
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
layout layout
drag={!(step.firstStep || step.finalStep)} drag={!(step.firstStep || step.finalStep)}
@@ -146,7 +148,7 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityTeacher
type="submit" type="submit"
color="purple" color="purple"
variant="solid" variant="solid"
className="max-w-fit text-lg font-medium flex items-center gap-2 text-left mt-7" className="max-w-fit text-lg font-medium flex items-center gap-2 text-left -mt-4"
> >
<FaRegCheckCircle className="size-5" /> <FaRegCheckCircle className="size-5" />
Confirm Exam Workflow Pipeline Confirm Exam Workflow Pipeline

View File

@@ -10,6 +10,7 @@ import { BsChevronLeft, BsTrash } from "react-icons/bs";
import { ToastContainer } from "react-toastify"; import { ToastContainer } from "react-toastify";
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import Tip from "@/components/ApprovalWorkflows/Tip";
import WorkflowForm from "@/components/ApprovalWorkflows/WorkflowForm"; import WorkflowForm from "@/components/ApprovalWorkflows/WorkflowForm";
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import Input from "@/components/Low/Input"; import Input from "@/components/Low/Input";
@@ -19,6 +20,7 @@ import { Entity } from "@/interfaces/entity";
import { CorporateUser, TeacherUser, User } from "@/interfaces/user"; import { CorporateUser, TeacherUser, User } from "@/interfaces/user";
import { getEntities } from "@/utils/entities.be"; import { getEntities } from "@/utils/entities.be";
import { getEntitiesUsers } from "@/utils/users.be"; import { getEntitiesUsers } from "@/utils/users.be";
import { LayoutGroup, motion } from "framer-motion";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { MdFormatListBulletedAdd } from "react-icons/md"; import { MdFormatListBulletedAdd } from "react-icons/md";
@@ -116,9 +118,12 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
const handleDeleteWorkflow = (id: string) => { const handleDeleteWorkflow = (id: string) => {
if (!confirm(`Are you sure you want to delete this Approval Workflow?`)) return; if (!confirm(`Are you sure you want to delete this Approval Workflow?`)) return;
setWorkflows(prev => prev.filter(wf => wf.id !== id)); const updatedWorkflows = workflows.filter(wf => wf.id !== id);
setWorkflows(updatedWorkflows);
if (selectedWorkflowId === id) { if (selectedWorkflowId === id) {
workflows.length > 0 ? setSelectedWorkflowId(workflows[0].id) : setSelectedWorkflowId(undefined); setSelectedWorkflowId(updatedWorkflows.find(wf => wf.id)?.id);
} }
}; };
@@ -212,15 +217,15 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
: ENTITY_OPTIONS.find(option => option.value === currentWorkflow.entityId) : ENTITY_OPTIONS.find(option => option.value === currentWorkflow.entityId)
} }
onChange={(selectedEntity) => { onChange={(selectedEntity) => {
if (selectedEntity?.value) { if (!currentWorkflow.entityId && selectedEntity?.value) {
setEntityId(selectedEntity.value); setEntityId(selectedEntity.value);
const updatedWorkflow = { const updatedWorkflow = {
...currentWorkflow, ...currentWorkflow,
entityId: selectedEntity.value, entityId: selectedEntity.value,
}; };
onWorkflowChange(updatedWorkflow); onWorkflowChange(updatedWorkflow);
} else if (selectedEntity === null) { } else {
if (!confirm("Clearing entity will reset this workflow. Are you sure you want to proceed?")) return; if (!confirm("Clearing or changing entity will reset this workflow. Are you sure you want to proceed?")) return;
handleResetWorkflow(currentWorkflow.id); handleResetWorkflow(currentWorkflow.id);
} }
}} }}
@@ -239,12 +244,34 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
</Button> </Button>
</div> </div>
<WorkflowForm <LayoutGroup>
workflow={currentWorkflow} {(!currentWorkflow.name || !currentWorkflow.entityId) && (
onWorkflowChange={onWorkflowChange} <motion.div
entityTeachers={entityTeachers} key={0}
entityCorporates={entityCorporates} initial={{ opacity: 0, y: -20 }}
/> animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
<Tip text="Please fill workflow name and associated entity to start configuring workflow." />
</motion.div>
)}
<motion.div
key={1}
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
<WorkflowForm
workflow={currentWorkflow}
onWorkflowChange={onWorkflowChange}
entityTeachers={entityTeachers}
entityCorporates={entityCorporates}
/>
</motion.div>
</LayoutGroup>
</> </>
)} )}
</form> </form>