- Refactor of workflow and steps types to differentiate between editView and normalView.

- Added side panel with steps details
This commit is contained in:
Joao Correia
2025-01-25 03:44:50 +00:00
parent 1f7639a30e
commit f71a7182dd
10 changed files with 225 additions and 100 deletions

View File

@@ -28,10 +28,9 @@ export default function StartedOn({ date }: Props) {
<div>
<p className="pb-1 text-sm font-medium text-gray-800">Started on</p>
<div className="flex items-center">
{/* Display the formatted date and add a title attribute for hover */}
<p
className="text-xs font-medium text-gray-800"
title={fullDateTime} // Shows full date and time on hover
title={fullDateTime}
>
{yearMonthDay}
</p>

View File

@@ -1,15 +1,15 @@
import { WorkflowStep } from "@/interfaces/approval.workflow";
import { EditableWorkflowStep } from "@/interfaces/approval.workflow";
import Option from "@/interfaces/option";
import { CorporateUser, TeacherUser } from "@/interfaces/user";
import Image from "next/image";
import { useEffect, useMemo, useState } from "react";
import { AiOutlineUserAdd } from "react-icons/ai";
import { BsTrash } from "react-icons/bs";
import { LuGripHorizontal } from "react-icons/lu";
import WorkflowStepNumber from "./WorkflowStepNumber";
import WorkflowStepSelects from "./WorkflowStepSelects";
import Image from "next/image";
interface Props extends WorkflowStep {
interface Props extends Pick<EditableWorkflowStep, 'stepNumber' | 'assignees' | 'finalStep' | 'onDelete'> {
entityTeachers: TeacherUser[];
entityCorporates: CorporateUser[];
onSelectChange: (numberOfSelects: number, index: number, value: Option | null) => void;
@@ -102,7 +102,7 @@ export default function WorkflowEditableStepComponent({
return (
<div className="flex w-full">
<div className="flex flex-col items-center">
<WorkflowStepNumber stepNumber={stepNumber} />
<WorkflowStepNumber stepNumber={stepNumber} completed={false} selected={false} />
{/* Vertical Bar connecting steps */}
{!finalStep && (

View File

@@ -1,17 +1,17 @@
import { ApprovalWorkflow, WorkflowStep } from "@/interfaces/approval.workflow";
import { EditableApprovalWorkflow, EditableWorkflowStep } from "@/interfaces/approval.workflow";
import Option from "@/interfaces/option";
import { CorporateUser, TeacherUser } from "@/interfaces/user";
import { AnimatePresence, AnimateSharedLayout, Reorder, motion } from "framer-motion";
import { AnimatePresence, Reorder, motion } from "framer-motion";
import { useState } from "react";
import { FaRegCheckCircle } from "react-icons/fa";
import { IoIosAddCircleOutline } from "react-icons/io";
import Button from "../Low/Button";
import WorkflowEditableStepComponent from "./WorkflowEditableStepComponent";
import Tip from "./Tip";
import WorkflowEditableStepComponent from "./WorkflowEditableStepComponent";
interface Props {
workflow: ApprovalWorkflow;
onWorkflowChange: (workflow: ApprovalWorkflow) => void;
workflow: EditableApprovalWorkflow;
onWorkflowChange: (workflow: EditableApprovalWorkflow) => void;
entityTeachers: TeacherUser[];
entityCorporates: CorporateUser[];
}
@@ -20,7 +20,7 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityTeacher
const [stepCounter, setStepCounter] = useState<number>(3); // to guarantee unique keys used for animations
const lastStep = workflow.steps[workflow.steps.length - 1];
const renumberSteps = (steps: WorkflowStep[]): WorkflowStep[] => {
const renumberSteps = (steps: EditableWorkflowStep[]): EditableWorkflowStep[] => {
return steps.map((step, index) => ({
...step,
stepNumber: index + 1,
@@ -28,12 +28,13 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityTeacher
};
const addStep = () => {
const newStep: WorkflowStep = {
const newStep: EditableWorkflowStep = {
key: stepCounter,
stepType: "approval-by",
stepNumber: workflow.steps.length,
completed: false,
assignees: [null],
firstStep: false,
finalStep: false,
};
setStepCounter((count) => count + 1);
@@ -73,7 +74,7 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityTeacher
onWorkflowChange({ ...workflow, steps: updatedSteps });
};
const handleReorder = (newOrder: WorkflowStep[]) => {
const handleReorder = (newOrder: EditableWorkflowStep[]) => {
const firstIndex = newOrder.findIndex((s) => s.firstStep);
if (firstIndex !== -1 && firstIndex !== 0) {
const [first] = newOrder.splice(firstIndex, 1);

View File

@@ -16,21 +16,20 @@ export default function WorkflowStepComponent({
stepNumber,
completed,
completedBy,
assignees,
finalStep,
currentStep,
selected = false,
assignees,
assigneesType,
onClick,
}: Props) {
const completedByUser = workflowAssignees.find((assignee) => assignee.id === completedBy);
const assigneesUsers = workflowAssignees.filter(user => assignees!.includes(user.id));
const assigneesUsers = workflowAssignees.filter(user => assignees.includes(user.id));
return (
<div
onClick={onClick}
className={clsx("flex flex-row gap-5 w-[700px] p-6 my-4 rounded-2xl transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer", {
className={clsx("flex flex-row gap-5 w-[600px] p-6 my-4 rounded-2xl transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer", {
"bg-mti-purple-ultralight": selected,
})}
>
@@ -85,9 +84,9 @@ export default function WorkflowStepComponent({
) : (
stepType === "approval-by" && (
<>
<p className="text-sm font-medium text-gray-800">Approval: {getUserTypeLabel(assigneesType)}</p>
{completed && completedBy ? (
<div className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
<p className="text-sm font-medium text-gray-800">Approval: {getUserTypeLabel(completedByUser!.type)} Approval</p>
<UserWithProfilePic
prefix={`Approved by: ${getUserTypeLabelShort(completedByUser!.type)}`}
name={completedByUser!.name}
@@ -96,6 +95,7 @@ export default function WorkflowStepComponent({
</div>
) : !completed && currentStep ? (
<div className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
<p className="text-sm font-medium text-gray-800">Approval: </p>
In Progress... Assignees:
<div className="flex flex-row flex-wrap gap-3 items-center">
{assigneesUsers.map(user => (
@@ -111,6 +111,7 @@ export default function WorkflowStepComponent({
</div>
) : (
<div className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
<p className="text-sm font-medium text-gray-800">Approval: </p>
Waiting for previous steps...
</div>
)}

View File

@@ -2,7 +2,9 @@ import { WorkflowStep } from "@/interfaces/approval.workflow";
import clsx from "clsx";
import { IoCheckmarkDoneSharp, IoCheckmarkSharp } from "react-icons/io5";
export default function WorkflowStepNumber({ stepNumber, selected = false, completed, finalStep }: WorkflowStep) {
type Props = Pick<WorkflowStep, 'stepNumber' | 'completed' | 'finalStep' | 'selected'>
export default function WorkflowStepNumber({ stepNumber, selected = false, completed, finalStep }: Props) {
return (
<div
className={clsx(

View File

@@ -16,22 +16,26 @@
"stepNumber": 1,
"completed": true,
"completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0",
"completedDate": 1737712243906,
"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": "c5fc1514-1a94-4f8c-a046-a62099097a50",
"completedDate": 1737712243906,
"assignees": [
"fd5fce42-4bcc-4150-a143-b484e750b265",
"231c84b2-a65a-49a9-803c-c664d84b13e0",
"c5fc1514-1a94-4f8c-a046-a62099097a50"
]
],
"comments": "This is a random comment"
},
{
"stepType": "approval-by",
@@ -41,7 +45,8 @@
"fd5fce42-4bcc-4150-a143-b484e750b265",
"231c84b2-a65a-49a9-803c-c664d84b13e0",
"c5fc1514-1a94-4f8c-a046-a62099097a50"
]
],
"comments": "This is a random comment"
},
{
"stepType": "approval-by",
@@ -51,7 +56,8 @@
"fd5fce42-4bcc-4150-a143-b484e750b265",
"231c84b2-a65a-49a9-803c-c664d84b13e0",
"c5fc1514-1a94-4f8c-a046-a62099097a50"
]
],
"comments": "This is a random comment"
},
{
"stepType": "approval-by",
@@ -61,7 +67,8 @@
"fd5fce42-4bcc-4150-a143-b484e750b265",
"231c84b2-a65a-49a9-803c-c664d84b13e0",
"c5fc1514-1a94-4f8c-a046-a62099097a50"
]
],
"comments": "This is a random comment"
}
]
},
@@ -85,55 +92,65 @@
"stepNumber": 1,
"completed": true,
"completedBy": "fd5fce42-4bcc-4150-a143-b484e750b265",
"completedDate": 1737712243906,
"assignees": [
"fd5fce42-4bcc-4150-a143-b484e750b265",
"231c84b2-a65a-49a9-803c-c664d84b13e0",
"c5fc1514-1a94-4f8c-a046-a62099097a50"
]
],
"comments": "This is a random comment"
},
{
"stepType": "approval-by",
"stepNumber": 2,
"completed": true,
"completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0",
"completedDate": 1737712243906,
"assignees": [
"fd5fce42-4bcc-4150-a143-b484e750b265",
"231c84b2-a65a-49a9-803c-c664d84b13e0",
"c5fc1514-1a94-4f8c-a046-a62099097a50"
]
],
"comments": "This is a random comment"
},
{
"stepType": "approval-by",
"stepNumber": 3,
"completed": true,
"completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0",
"completedDate": 1737712243906,
"assignees": [
"fd5fce42-4bcc-4150-a143-b484e750b265",
"231c84b2-a65a-49a9-803c-c664d84b13e0",
"c5fc1514-1a94-4f8c-a046-a62099097a50"
]
],
"comments": "This is a random comment"
},
{
"stepType": "approval-by",
"stepNumber": 4,
"completed": true,
"completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0",
"completedDate": 1737712243906,
"assignees": [
"fd5fce42-4bcc-4150-a143-b484e750b265",
"231c84b2-a65a-49a9-803c-c664d84b13e0",
"c5fc1514-1a94-4f8c-a046-a62099097a50"
]
],
"comments": "This is a random comment"
},
{
"stepType": "approval-by",
"stepNumber": 5,
"completed": true,
"completedBy": "c5fc1514-1a94-4f8c-a046-a62099097a50",
"completedDate": 1737712243906,
"assignees": [
"fd5fce42-4bcc-4150-a143-b484e750b265",
"231c84b2-a65a-49a9-803c-c664d84b13e0",
"c5fc1514-1a94-4f8c-a046-a62099097a50"
]
],
"comments": "This is a random comment"
}
]
}

View File

@@ -12,6 +12,17 @@ export interface ApprovalWorkflow {
steps: WorkflowStep[],
}
export interface EditableApprovalWorkflow {
id: string,
name: string,
entityId: string,
requester: User["id"],
startDate: number,
modules: Module[],
status: ApprovalWorkflowStatus,
steps: EditableWorkflowStep[],
}
export type StepType = "form-intake" | "approval-by";
export const StepTypeLabel: Record<StepType, string> = {
"form-intake": "Form Intake",
@@ -19,19 +30,28 @@ export const StepTypeLabel: Record<StepType, string> = {
};
export interface WorkflowStep {
key?: number,
stepType?: StepType,
stepType: StepType,
stepNumber: number,
completed?: boolean,
completed: boolean,
completedBy?: User["id"],
assignees?: (User["id"] | null | undefined)[]; // bit of an hack, but allowing null or undefined values allows us to match one to one the select input components with the assignees array. And since select inputs allow undefined or null values, it is allowed here too, but must validate required input before form submission
assigneesType?: Type,
completedDate?: number,
assignees: (User["id"])[];
firstStep?: boolean,
currentStep?: boolean,
finalStep?: boolean,
selected?: boolean,
selected: boolean,
comments?: string,
onClick: React.MouseEventHandler<HTMLDivElement>
}
export interface EditableWorkflowStep {
key: number,
stepType: StepType,
stepNumber: number,
assignees: (User["id"] | null | undefined)[]; // bit of an hack, but allowing null or undefined values allows us to match one to one the select input components with the assignees array. And since select inputs allow undefined or null values, it is allowed here too, but must validate required input before form submission
firstStep: boolean,
finalStep?: boolean,
onDelete?: () => void;
onClick?: React.MouseEventHandler<HTMLDivElement>
}
export function getUserTypeLabel(type: Type | undefined): string {

View File

@@ -1,25 +1,28 @@
import RequestedBy from "@/components/ApprovalWorkflows/RequestedBy";
import StartedOn from "@/components/ApprovalWorkflows/StartedOn";
import Status from "@/components/ApprovalWorkflows/Status";
import UserWithProfilePic from "@/components/ApprovalWorkflows/UserWithProfilePic";
import WorkflowStepComponent from "@/components/ApprovalWorkflows/WorkflowStepComponent";
import Layout from "@/components/High/Layout";
import { ApprovalWorkflow, getUserTypeLabelShort } from "@/interfaces/approval.workflow";
import { ApprovalWorkflow, getUserTypeLabelShort, WorkflowStep } from "@/interfaces/approval.workflow";
import { User } from "@/interfaces/user";
import { sessionOptions } from "@/lib/session";
import { redirect, serialize } from "@/utils";
import { requestUser } from "@/utils/api";
import { shouldRedirectHome } from "@/utils/navigation.disabled";
import { getSpecificUsers, getUser } from "@/utils/users.be";
import { withIronSessionSsr } from "iron-session/next";
import Head from "next/head";
import Link from "next/link";
import { useState } from "react";
import { BsChevronLeft } from "react-icons/bs";
import { FaWpforms } from "react-icons/fa6";
import { MdOutlineDoubleArrow } from "react-icons/md";
import { RiThumbUpLine } from "react-icons/ri";
import { ToastContainer } from "react-toastify";
import approvalWorkflowsData from '../../demo/approval_workflows.json'; // to test locally
import RequestedBy from "@/components/ApprovalWorkflows/RequestedBy";
import StartedOn from "@/components/ApprovalWorkflows/StartedOn";
import Status from "@/components/ApprovalWorkflows/Status";
import WorkflowStepComponent from "@/components/ApprovalWorkflows/WorkflowStepComponent";
import { User } from "@/interfaces/user";
import { getSpecificUsers, getUser } from "@/utils/users.be";
import { useState } from "react";
export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => {
const user = await requestUser(req, res);
if (!user) return redirect("/login")
@@ -29,7 +32,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }
const { id } = params as { id: string };
// replace later with await getApprovalWorkflow(id). Don't think a hook is needed here;
// replace later with await getApprovalWorkflow(id).
const approvalWorkflowsDataAsWorkflows = approvalWorkflowsData as ApprovalWorkflow[];
const workflow: ApprovalWorkflow | undefined = approvalWorkflowsDataAsWorkflows.find(workflow => workflow.id === id);
@@ -40,7 +43,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }
...new Set(
workflow.steps
.map(step => step.assignees)
.flat() as string[] // we are sure assignees coming from a db workflow are all valid strings.
.flat()
)
];
@@ -68,10 +71,20 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
if (currentStep === -1)
currentStep = steps.length - 1;
const [selectedIndex, setSelectedIndex] = useState(currentStep);
const [selectedStepIndex, setSelectedStepIndex] = useState<number>(currentStep);
const [selectedStep, setSelectedStep] = useState<WorkflowStep>(steps[selectedStepIndex]);
const [isPanelOpen, setIsPanelOpen] = useState(true);
const [comments, setComments] = useState<string>(selectedStep.comments || "");
const handleStepClick = (index: number, stepInfo: WorkflowStep) => {
setSelectedStep(stepInfo);
setSelectedStepIndex(index);
setComments(stepInfo.comments || "");
setIsPanelOpen(true);
};
const saveComments = () => {
const handleStepClick = (index: number) => {
setSelectedIndex(index);
};
return (
@@ -88,16 +101,16 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
<ToastContainer />
{user && (
<Layout user={user} className="gap-6">
<section className="flex flex-col gap-0">
<div className="flex items-center gap-2">
<Link
href="/approval-workflows"
className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl">
<BsChevronLeft />
</Link>
<h1 className="text-2xl font-semibold">{workflow.name}</h1>
</div>
<section className="flex items-center gap-2">
<Link
href="/approval-workflows"
className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl">
<BsChevronLeft />
</Link>
<h1 className="text-2xl font-semibold">{workflow.name}</h1>
</section>
<section className="flex flex-col gap-6">
<div className="flex flex-row gap-6">
<RequestedBy
@@ -113,6 +126,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
/>
</div>
</section>
<section className="flex flex-col gap-0">
{steps.map((step, index) => (
<WorkflowStepComponent
@@ -123,14 +137,95 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
stepNumber={step.stepNumber}
stepType={step.stepType}
assignees={step.assignees}
assigneesType={step.assigneesType}
finalStep={index === steps.length - 1}
currentStep={currentStep === index}
selected={index === selectedIndex}
onClick={() => handleStepClick(index)}
currentStep={index === currentStep}
selected={index === selectedStepIndex}
onClick={() => handleStepClick(index, step)}
/>
))}
</section>
<section className={`absolute inset-y-0 right-0 h-full bg-mti-purple-ultralight bg-opacity-50 shadow-xl shadow-mti-purple transition-all duration-300 overflow-hidden ${isPanelOpen ? 'w-2/5' : 'w-0'}`}>
{isPanelOpen && (
<div className="relative inset-y-0 right-0 h-full p-6">
<div className="flex flex-row gap-2">
<p className="text-2xl font-medium text-left align-middle">Step {selectedStepIndex + 1}</p>
<div className="ml-auto flex flex-row">
<button
className="min-w-fit max-h-fit text-lg font-medium flex items-center gap-2 text-left"
onClick={() => setIsPanelOpen(false)}
>
Collapse
<MdOutlineDoubleArrow size={20} />
</button>
</div>
</div>
<hr className="my-4 h-[4px] bg-mti-purple-ultralight rounded-full w-full" />
<div>
<div className="my-8 flex flex-row gap-4 items-center text-lg font-medium">
{selectedStep.stepType === "approval-by" ? (
<>
<RiThumbUpLine size={30} />
Approval Step
</>
) : (
<>
<FaWpforms size={30} />
Form Intake Step
</>
)
}
</div>
{selectedStep.completed ? (
<div className={"text-base font-medium text-gray-500 my-7"}>
Approved on {new Date(selectedStep.completedDate!).toLocaleString("en-CA", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}).replace(", ", " at ")}
<p className="my-4 text-sm">No additional actions are required.</p>
</div>
) : (
<div className={"text-base font-medium text-gray-500"}>
One assignee is required to sign off to complete this step:
<div className="flex flex-col gap-2 mt-3">
{workflowAssignees.map(user => (
<span key={user.id}>
<UserWithProfilePic
prefix={getUserTypeLabelShort(user.type)}
name={user.name}
profileImage={user.profilePicture}
/>
</span>
))}
</div>
</div>
)}
<hr className="my-4 h-[4px] bg-mti-purple-ultralight rounded-full w-full" />
<textarea
value={comments}
onChange={(e) => setComments(e.target.value)}
placeholder="Input comments here"
className="w-full h-80 p-2 border-2 rounded-xl focus:border-mti-purple focus:outline-none mt-4"
/>
<button onClick={saveComments} className="mt-4 px-6 py-2 bg-mti-purple-dark text-white rounded-full">
Save Comments
</button>
</div>
</div>
)}
</section>
</Layout>
)}
</>

View File

@@ -1,28 +1,27 @@
import Layout from "@/components/High/Layout";
import { sessionOptions } from "@/lib/session";
import { redirect, serialize } from "@/utils";
import { requestUser } from "@/utils/api";
import { shouldRedirectHome } from "@/utils/navigation.disabled";
import { withIronSessionSsr } from "iron-session/next";
import Head from "next/head";
import Link from "next/link";
import { BsChevronLeft, BsTrash } from "react-icons/bs";
import { ToastContainer } from "react-toastify";
import { v4 as uuidv4 } from 'uuid';
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 } from "@/interfaces/approval.workflow";
import { EditableApprovalWorkflow } 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 { 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, BsTrash } from "react-icons/bs";
import { MdFormatListBulletedAdd } from "react-icons/md";
import { ToastContainer } from "react-toastify";
import { v4 as uuidv4 } from 'uuid';
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res)
@@ -51,7 +50,7 @@ interface Props {
}
export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers, userEntitiesCorporates }: Props) {
const [workflows, setWorkflows] = useState<ApprovalWorkflow[]>([]);
const [workflows, setWorkflows] = useState<EditableApprovalWorkflow[]>([]);
const [selectedWorkflowId, setSelectedWorkflowId] = useState<string | undefined>(undefined);
const [entityId, setEntityId] = useState<string | null | undefined>(null);
const [entityTeachers, setEntityTeachers] = useState<TeacherUser[]>([]);
@@ -80,7 +79,7 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
const ENTITY_OPTIONS = userEntitiesWithLabel.map(entity => ({
label: entity.label,
value: entity.id,
filter: (x: ApprovalWorkflow) => x.entityId === entity.id,
filter: (x: EditableApprovalWorkflow) => x.entityId === entity.id,
}));
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
@@ -90,7 +89,7 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
const handleAddNewWorkflow = () => {
const newId = uuidv4();
const newWorkflow: ApprovalWorkflow = {
const newWorkflow: EditableApprovalWorkflow = {
id: newId,
name: "",
entityId: "",
@@ -99,15 +98,15 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
startDate: Date.now(),
status: "pending",
steps: [
{ key: Date.now(), completed: false, stepType: "form-intake", stepNumber: 1, firstStep: true, assignees: [null] },
{ key: Date.now() + 1, completed: false, stepType: "approval-by", stepNumber: 2, finalStep: true, assignees: [null] },
{ key: Date.now(), stepType: "form-intake", stepNumber: 1, firstStep: true, finalStep: false, assignees: [null] },
{ key: Date.now() + 1, stepType: "approval-by", stepNumber: 2, firstStep: false, finalStep: true, assignees: [null] },
],
};
setWorkflows((prev) => [...prev, newWorkflow]);
handleSelectWorkflow(newId);
};
const onWorkflowChange = (updatedWorkflow: ApprovalWorkflow) => {
const onWorkflowChange = (updatedWorkflow: EditableApprovalWorkflow) => {
setWorkflows(prev =>
prev.map(wf => (wf.id === updatedWorkflow.id ? updatedWorkflow : wf))
);

View File

@@ -34,12 +34,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type))
return redirect("/")
/* const entityIDS = mapBy(user.entities, "id");
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDS);
const allowedEntities = findAllowedEntities(user, entities, "view_approval_workflows"); */
// replace later with await getApprovalWorkflow(id). Don't think a hook is needed here;
// replace later with useApprovalWorkflows()
const workflows = approvalWorkflowsData as ApprovalWorkflow[];
const allAssigneeIds: string[] = [
@@ -47,8 +42,8 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
workflows
.map(workflow => workflow.steps
.map(step => step.assignees)
.flat() as string[] // we are sure assignees coming from a db workflow are all valid strings.
).flat() as string[]
.flat()
).flat()
)
];
@@ -166,10 +161,6 @@ export default function ApprovalWorkflows({ user, workflows, workflowsAssignees,
};
const columns = [
/* columnHelper.accessor("id", {
header: "ID",
cell: (info) => info.getValue(),
}), */
columnHelper.accessor("name", {
header: "NAME",
cell: (info) => (
@@ -216,7 +207,7 @@ export default function ApprovalWorkflows({ user, workflows, workflowsAssignees,
const steps = info.row.original.steps;
const currentStep = steps.find((step) => !step.completed);
const assignees = currentStep?.assignees!.map((assigneeId) => {
const assignees = currentStep?.assignees.map((assigneeId) => {
const assignee = workflowsAssignees.find((user) => user.id === assigneeId);
return assignee?.name || "Unknown Assignee";
});
@@ -245,7 +236,7 @@ export default function ApprovalWorkflows({ user, workflows, workflowsAssignees,
return (
<span className="font-medium">
{currentStep
? `Step ${currentStep.stepNumber}: ${StepTypeLabel[currentStep.stepType!]}`
? `Step ${currentStep.stepNumber}: ${StepTypeLabel[currentStep.stepType]}`
: "Completed"}
</span>
);