Add entityId to workflow. Allow filter workflows based on entityId. Restrict creation of workflows based on user entities.

This commit is contained in:
Joao Correia
2025-01-22 16:39:18 +00:00
parent 8f8d5e5640
commit 4895f00184
5 changed files with 124 additions and 29 deletions

View File

@@ -5,12 +5,18 @@ import useApprovalWorkflows from "@/hooks/useApprovalWorkflows";
import useUser from "@/hooks/useUser";
import { Module, ModuleTypeLabels } from "@/interfaces";
import { ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel, StepTypeLabel } from "@/interfaces/approval.workflow";
import { EntityWithRoles } from "@/interfaces/entity";
import { TeacherUser, User } from "@/interfaces/user";
import { sessionOptions } from "@/lib/session";
import { redirect } from "@/utils";
import { mapBy, redirect, serialize } from "@/utils";
import { requestUser } from "@/utils/api";
import { getEntitiesWithRoles } from "@/utils/entities.be";
import { shouldRedirectHome } from "@/utils/navigation.disabled";
import { findAllowedEntities } from "@/utils/permissions";
import { isAdmin } from "@/utils/users";
import { getUsers } from "@/utils/users.be";
import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
import axios from "axios";
import axios, { all } from "axios";
import clsx from "clsx";
import { withIronSessionSsr } from "iron-session/next";
import Head from "next/head";
@@ -30,8 +36,16 @@ 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");
return {
props: { user },
props: serialize({
user,
teachers: await getUsers({ type: "teacher" }) as TeacherUser[],
allowedEntities,
}),
};
}, sessionOptions);
@@ -41,7 +55,8 @@ const StatusClassNames: { [key in ApprovalWorkflowStatus]: string } = {
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 | "all" | "pending";
type CustomStatus = ApprovalWorkflowStatus | "all";
type CustomEntity = EntityWithRoles["label"] | "all";
const STATUS_OPTIONS = [
{
@@ -66,13 +81,37 @@ const STATUS_OPTIONS = [
},
];
export default function ApprovalWorkflows() {
interface Props {
user: User,
teachers: TeacherUser[],
allowedEntities: EntityWithRoles[],
}
export default function ApprovalWorkflows({ user, teachers, allowedEntities }: Props) {
console.log(user);
console.log(teachers);
console.log(allowedEntities);
const ENTITY_OPTIONS = [
{
label: "All",
value: "all",
filter: (x: ApprovalWorkflow) => true,
},
...allowedEntities
.map(entity => ({
label: entity.label,
value: entity.id,
filter: (x: ApprovalWorkflow) => x.entityId === entity.id,
}))
.sort((a, b) => a.label.localeCompare(b.label)),
];
const [filteredApprovalWorkflows, setFilteredApprovalWorkflows] = useState<ApprovalWorkflow[]>([]);
/* const [selectedApprovalWorkflow, setSelectedApprovalWorkflow] = useState<ApprovalWorkflow>(); */
const [statusFilter, setStatusFilter] = useState<CustomStatus>("all");
const { user } = useUser({ redirectTo: "/login" });
const [entityFilter, setEntityFilter] = useState<CustomEntity>("all");
const { approvalWorkflows/* , reload */ } = useApprovalWorkflows();
@@ -82,9 +121,14 @@ export default function ApprovalWorkflows() {
const filter = STATUS_OPTIONS.find((x) => x.value === statusFilter)?.filter;
if (filter) filters.push(filter);
}
if (entityFilter) {
const filter = ENTITY_OPTIONS.find((x) => x.value === entityFilter)?.filter;
if (filter) filters.push(filter);
}
setFilteredApprovalWorkflows([...filters.reduce((d, f) => d.filter(f), approvalWorkflows)]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [approvalWorkflows, statusFilter]);
}, [approvalWorkflows, statusFilter, entityFilter]);
const deleteApprovalWorkflow = (id: string, name: string) => {
if (!confirm(`Are you sure you want to delete this Approval Workflow?`)) return;
@@ -132,7 +176,7 @@ export default function ApprovalWorkflows() {
{info.getValue().map((module: Module, index: number) => (
<span
key={index}
className="inline-block rounded-full px-3 py-1 text-sm font-medium bg-purple-100 border border-purple-300 text-purple-900"
className="inline-block rounded-full px-3 py-1 text-sm font-medium bg-indigo-100 border border-indigo-300 text-indigo-900"
>
{ModuleTypeLabels[module]}
</span>
@@ -149,6 +193,7 @@ export default function ApprovalWorkflows() {
),
}),
columnHelper.accessor("steps", {
id: "currentApprovers",
header: "CURRENT APPROVERS",
cell: (info) => {
const steps = info.row.original.steps;
@@ -170,6 +215,7 @@ export default function ApprovalWorkflows() {
},
}),
columnHelper.accessor("steps", {
id: "currentStep",
header: "CURRENT STEP",
cell: (info) => {
const steps = info.row.original.steps;
@@ -246,6 +292,16 @@ export default function ApprovalWorkflows() {
placeholder="Status..."
/>
</div>
<div className="flex w-full flex-col gap-3">
<label className="text-mti-gray-dim text-base font-normal">Entity</label>
<Select
options={ENTITY_OPTIONS}
value={ENTITY_OPTIONS.find((x) => x.value === entityFilter)}
onChange={(value) => setEntityFilter((value?.value as string) ?? undefined)}
isClearable
placeholder="Entity..."
/>
</div>
</div>
<div className="px-6 pb-4 bg-purple-100 rounded-2xl">
@@ -271,13 +327,12 @@ export default function ApprovalWorkflows() {
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{/*
Might be an overkill way to add rounded borders to the rows, but couldn't figure out another way...
border round and margin does not seem to work properly on tr
Another way to do it was with grid but that puts the same width in all rows, which is inconvenient
Regardless, it works and all calcs are pretty simple so shouldnt be too inefficient
*/}
<tr
key={row.id}
onClick={() => window.location.href = `/approval-workflows/${row.original.id}`}
style={{ cursor: "pointer" }}
className="hover:bg-purple-100"
>
{row.getVisibleCells().map((cell, cellIndex) => {
const lastCellIndex = row.getVisibleCells().length - 1;
@@ -290,7 +345,7 @@ export default function ApprovalWorkflows() {
}
return (
<td key={cell.id} className={cellClasses}>
<td key={cellIndex} className={cellClasses}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
);
@@ -298,6 +353,7 @@ export default function ApprovalWorkflows() {
</tr>
))}
</tbody>
</table>
</div>