Add local test data, implement [id].tsx for approval workflows
This commit is contained in:
228
src/pages/approval-workflows/index.tsx
Normal file
228
src/pages/approval-workflows/index.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import Layout from "@/components/High/Layout";
|
||||
import Select from "@/components/Low/Select";
|
||||
import useApprovalWorkflows from "@/hooks/useApprovalWorkflows";
|
||||
import useUser from "@/hooks/useUser";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import { ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel } from "@/interfaces/approval.workflow";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import { redirect } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||
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 { useEffect, useState } from "react";
|
||||
import { BsTrash } from "react-icons/bs";
|
||||
import { FaRegEdit } from "react-icons/fa";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
|
||||
const columnHelper = createColumnHelper<ApprovalWorkflow>();
|
||||
|
||||
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("/")
|
||||
|
||||
return {
|
||||
props: { user },
|
||||
};
|
||||
}, 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 | "all" | "pending";
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{
|
||||
label: "All",
|
||||
value: "all",
|
||||
filter: (x: ApprovalWorkflow) => true,
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
];
|
||||
|
||||
export default function ApprovalWorkflows() {
|
||||
const [filteredApprovalWorkflows, setFilteredApprovalWorkflows] = useState<ApprovalWorkflow[]>([]);
|
||||
/* const [selectedApprovalWorkflow, setSelectedApprovalWorkflow] = useState<ApprovalWorkflow>(); */
|
||||
|
||||
const [statusFilter, setStatusFilter] = useState<CustomStatus>("all");
|
||||
|
||||
const { user } = useUser({ redirectTo: "/login" });
|
||||
|
||||
const { approvalWorkflows/* , reload */ } = useApprovalWorkflows();
|
||||
|
||||
useEffect(() => {
|
||||
const filters = [];
|
||||
if (statusFilter) {
|
||||
const filter = STATUS_OPTIONS.find((x) => x.value === statusFilter)?.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]);
|
||||
|
||||
const deleteApprovalWorkflow = (id: string, name: string) => {
|
||||
if (!confirm(`Are you sure you want to delete this Approval Workflow?`)) return;
|
||||
|
||||
axios
|
||||
.delete(`/api/approval-workflows/${id}`)
|
||||
.then(() => toast.success(`Deleted ${name} Approval Workflow.`))
|
||||
.catch((reason) => {
|
||||
if (reason.response.status === 404) {
|
||||
toast.error("Approval Workflow not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (reason.response.status === 403) {
|
||||
toast.error("You do not have permission to delete an Approval Workflow!");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.error("Something went wrong, please try again later.");
|
||||
})
|
||||
/* .finally(reload); */
|
||||
};
|
||||
|
||||
const editApprovalWorkflow = (id: string) => {
|
||||
|
||||
};
|
||||
|
||||
const columns = [
|
||||
/* columnHelper.accessor("id", {
|
||||
header: "ID",
|
||||
cell: (info) => info.getValue(),
|
||||
}), */
|
||||
columnHelper.accessor("name", {
|
||||
header: "Name",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("module", {
|
||||
header: "Module",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("status", {
|
||||
header: "Status",
|
||||
cell: (info) => (
|
||||
<span className={clsx("rounded-full px-3 py-1 text-sm font-medium inline-block text-left w-[110px]", StatusClassNames[info.getValue()])}>
|
||||
{ApprovalWorkflowStatusLabel[info.getValue()]}
|
||||
</span>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("approvers", {
|
||||
header: "Approvers",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("step", {
|
||||
header: "Step",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
{
|
||||
header: "Actions",
|
||||
id: "actions",
|
||||
cell: ({ row }: { row: { original: ApprovalWorkflow } }) => {
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
<div data-tip="Delete" className="cursor-pointer tooltip" onClick={() => deleteApprovalWorkflow(row.original.id, row.original.name)}>
|
||||
<BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
||||
</div>
|
||||
<Link data-tip="Edit" href={`/approval-workflows/${row.original.id}`} className="cursor-pointer tooltip">
|
||||
<FaRegEdit className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const table = useReactTable({
|
||||
data: filteredApprovalWorkflows,
|
||||
columns: columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Approval Workflows Panel | EnCoach</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<ToastContainer />
|
||||
{user && (
|
||||
<Layout user={user} className="gap-6">
|
||||
<h1 className="text-2xl font-semibold">Approval Workflows</h1>
|
||||
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<label className="text-mti-gray-dim text-base font-normal">Status</label>
|
||||
<Select
|
||||
options={STATUS_OPTIONS}
|
||||
value={STATUS_OPTIONS.find((x) => x.value === statusFilter)}
|
||||
onChange={(value) => setStatusFilter((value?.value as ApprovalWorkflowStatus) ?? undefined)}
|
||||
isClearable
|
||||
placeholder="Status..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table className="bg-mti-purple-ultralight/40 w-full rounded-xl">
|
||||
<thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th className="px-4 py-4 text-left" key={header.id}>
|
||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody className="px-2">
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<tr
|
||||
className={clsx(
|
||||
"even:bg-mti-purple-ultralight/40 rounded-lg py-2 odd:bg-white"
|
||||
)}
|
||||
key={row.id}>
|
||||
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td className="w-fit items-center px-4 py-2" key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Layout>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user