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 { Module, ModuleTypeLabels } from "@/interfaces"; import { ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel, StepTypeLabel } from "@/interfaces/approval.workflow"; import { Entity, EntityWithRoles } from "@/interfaces/entity"; import { User } from "@/interfaces/user"; import { sessionOptions } from "@/lib/session"; import { redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; import { getApprovalWorkflows } from "@/utils/approval.workflows.be"; import { getEntities } from "@/utils/entities.be"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { getSpecificUsers } from "@/utils/users.be"; import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"; import axios from "axios"; import clsx from "clsx"; import { withIronSessionSsr } from "iron-session/next"; import Head from "next/head"; import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { BsTrash } from "react-icons/bs"; import { FaRegEdit } from "react-icons/fa"; import { FaRegClone } from "react-icons/fa6"; import { IoIosAddCircleOutline } from "react-icons/io"; import { toast, ToastContainer } from "react-toastify"; const columnHelper = createColumnHelper(); export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { 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 workflows = await getApprovalWorkflows("configured-workflows"); const allAssigneeIds: string[] = [ ...new Set( workflows .map(workflow => workflow.steps .map(step => step.assignees) .flat() ).flat() ) ]; return { props: serialize({ user, workflows, workflowsAssignees: await getSpecificUsers(allAssigneeIds), userEntitiesWithLabel: await getEntities(user.entities.map(entity => entity.id)), }), }; }, sessionOptions); const StatusClassNames: { [key in ApprovalWorkflowStatus]: string } = { approved: "bg-green-100 text-green-800 border border-green-300 before:content-[''] before:w-2 before:h-2 before:bg-green-500 before:rounded-full before:inline-block before:mr-2", pending: "bg-orange-100 text-orange-800 border border-orange-300 before:content-[''] before:w-2 before:h-2 before:bg-orange-500 before:rounded-full before:inline-block before:mr-2", rejected: "bg-red-100 text-red-800 border border-red-300 before:content-[''] before:w-2 before:h-2 before:bg-red-500 before:rounded-full before:inline-block before:mr-2", }; type CustomStatus = ApprovalWorkflowStatus | undefined; type CustomEntity = EntityWithRoles["id"] | undefined; const STATUS_OPTIONS = [ { label: "Approved", value: "approved", filter: (x: ApprovalWorkflow) => x.status === "approved", }, { label: "Pending", value: "pending", filter: (x: ApprovalWorkflow) => x.status === "pending", }, { label: "Rejected", value: "rejected", filter: (x: ApprovalWorkflow) => x.status === "rejected", }, ]; interface Props { user: User, workflows: ApprovalWorkflow[], workflowsAssignees: User[], userEntitiesWithLabel: Entity[], } export default function ApprovalWorkflows({ user, workflows, workflowsAssignees, userEntitiesWithLabel }: Props) { const ENTITY_OPTIONS = [ ...userEntitiesWithLabel .map(entity => ({ label: entity.label, value: entity.id, filter: (x: ApprovalWorkflow) => x.entityId === entity.id, })) .sort((a, b) => a.label.localeCompare(b.label)), ]; const [filteredWorkflows, setFilteredWorkflows] = useState([]); const [statusFilter, setStatusFilter] = useState(undefined); const [entityFilter, setEntityFilter] = useState(undefined); const [nameFilter, setNameFilter] = useState(""); const router = useRouter(); useEffect(() => { const filters: Array<(workflow: ApprovalWorkflow) => boolean> = []; if (statusFilter && statusFilter !== undefined) { const statusOption = STATUS_OPTIONS.find((x) => x.value === statusFilter); if (statusOption && statusOption.filter) { filters.push(statusOption.filter); } } if (entityFilter && entityFilter !== undefined) { const entityOption = ENTITY_OPTIONS.find((x) => x.value === entityFilter); if (entityOption && entityOption.filter) { filters.push(entityOption.filter); } } if (nameFilter.trim() !== "") { const nameFilterFunction = (workflow: ApprovalWorkflow) => workflow.name.toLowerCase().includes(nameFilter.toLowerCase()); filters.push(nameFilterFunction); } // Apply all filters const filtered = workflows.filter(workflow => filters.every(filterFn => filterFn(workflow))); setFilteredWorkflows(filtered); // eslint-disable-next-line react-hooks/exhaustive-deps }, [workflows, statusFilter, entityFilter, nameFilter]); const handleNameFilterChange = (name: ApprovalWorkflow["name"]) => { setNameFilter(name); }; const deleteApprovalWorkflow = (id: string | undefined, name: string) => { if (id === undefined) return; if (!confirm(`Are you sure you want to delete this Approval Workflow?`)) return; axios .delete(`/api/approval-workflows/${id}`) .then(() => { toast.success(`Successfully deleted ${name} Approval Workflow.`); router.reload(); }) .catch((reason) => { if (reason.response.status === 404) { toast.error("Approval Workflow not found!"); } else if (reason.response.status === 403) { toast.error("You do not have permission to delete an Approval Workflow!"); } else { toast.error("Something went wrong, please try again later."); } return; }) }; const columns = [ columnHelper.accessor("name", { header: "NAME", cell: (info) => ( {info.getValue()} ), }), columnHelper.accessor("modules", { header: "MODULES", cell: (info) => (
{info.getValue().map((module: Module, index: number) => ( {ModuleTypeLabels[module]} ))}
), }), columnHelper.accessor("status", { header: "STATUS", cell: (info) => ( {ApprovalWorkflowStatusLabel[info.getValue()]} ), }), columnHelper.accessor("entityId", { header: "ENTITY", cell: (info) => ( {userEntitiesWithLabel.find((entity) => entity.id === info.getValue())?.label} ), }), columnHelper.accessor("steps", { id: "currentAssignees", header: "CURRENT ASSIGNEES", cell: (info) => { const steps = info.row.original.steps; const currentStep = steps.find((step) => !step.completed); const rejected = steps.find((step) => step.rejected); if(rejected) return ""; const assignees = currentStep?.assignees.map((assigneeId) => { const assignee = workflowsAssignees.find((user) => user.id === assigneeId); return assignee?.name || "Unknown Assignee"; }); return (
{assignees?.map((assigneeName: string, index: number) => ( {assigneeName} ))}
); }, }), columnHelper.accessor("steps", { id: "currentStep", header: "CURRENT STEP", cell: (info) => { const steps = info.row.original.steps; const currentStep = steps.find((step) => !step.completed); const rejected = steps.find((step) => step.rejected); return ( {currentStep && !rejected ? `Step ${currentStep.stepNumber}: ${StepTypeLabel[currentStep.stepType]}` : "Completed"} ); }, }), columnHelper.accessor("steps", { header: "ACTIONS", id: "actions", cell: ({ row }) => { const steps = row.original.steps; const currentStep = steps.find((step) => !step.completed); const rejected = steps.find((step) => step.rejected); return (
e.stopPropagation()} data-tip="Clone" href={`/approval-workflows/${row.original._id?.toString()}/clone`} className="cursor-pointer tooltip" > {currentStep && !rejected && ( e.stopPropagation()} data-tip="Edit" href={`/approval-workflows/${row.original._id?.toString()}/edit`} className="cursor-pointer tooltip" > )}
); }, }) ]; const table = useReactTable({ data: filteredWorkflows, columns: columns, getCoreRowModel: getCoreRowModel(), }); return ( <> Approval Workflows Panel | EnCoach {user && (

Approval Workflows

x.value === entityFilter)} onChange={(value) => setEntityFilter((value?.value as CustomEntity) ?? undefined)} isClearable placeholder="Filter by entity..." />
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} ))} {table.getRowModel().rows.map((row) => ( window.location.href = `/approval-workflows/${row.original._id?.toString()}`} style={{ cursor: "pointer" }} className="bg-purple-50" > {row.getVisibleCells().map((cell, cellIndex) => { const lastCellIndex = row.getVisibleCells().length - 1; let cellClasses = "pl-3 pr-4 py-2 border-y-2 border-mti-purple-light border-opacity-60"; if (cellIndex === 0) { cellClasses += " border-l-2 rounded-l-2xl"; } if (cellIndex === lastCellIndex) { cellClasses += " border-r-2 rounded-r-2xl"; } return ( ); })} ))}
{header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
)} ); }