Add local test data, implement [id].tsx for approval workflows
This commit is contained in:
@@ -1 +1,42 @@
|
|||||||
[]
|
[
|
||||||
|
{
|
||||||
|
"id": "local-test-id-1",
|
||||||
|
"name": "name-1",
|
||||||
|
"module": "reading",
|
||||||
|
"status": "approved",
|
||||||
|
"approvers": "prof-1",
|
||||||
|
"step": "Concluded"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "local-test-id-2",
|
||||||
|
"name": "name-2",
|
||||||
|
"module": "reading",
|
||||||
|
"status": "pending",
|
||||||
|
"approvers": "prof-2",
|
||||||
|
"step": "Concluded"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "local-test-id-3",
|
||||||
|
"name": "name-3",
|
||||||
|
"module": "listening",
|
||||||
|
"status": "rejected",
|
||||||
|
"approvers": "prof-3",
|
||||||
|
"step": "Concluded"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "local-test-id-4",
|
||||||
|
"name": "name-4",
|
||||||
|
"module": "writing",
|
||||||
|
"status": "approved",
|
||||||
|
"approvers": "prof-4",
|
||||||
|
"step": "Concluded"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "local-test-id-5",
|
||||||
|
"name": "name-5",
|
||||||
|
"module": "reading",
|
||||||
|
"status": "approved",
|
||||||
|
"approvers": "prof-5",
|
||||||
|
"step": "Concluded"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
import approvalWorkflowsData from '../demo/approval_workflows.json' // to test locally
|
import approvalWorkflowsData from '../demo/approval_workflows.json' // to test locally
|
||||||
|
|
||||||
export default function useApprovalWorkflows() {
|
export default function useApprovalWorkflows() {
|
||||||
const [approvalWorkflows, setApprovalWorkflows] = useState<ApprovalWorkflow[]>(approvalWorkflowsData);
|
const [approvalWorkflows, setApprovalWorkflows] = useState<ApprovalWorkflow[]>(approvalWorkflowsData as ApprovalWorkflow[]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isError, setIsError] = useState(false);
|
const [isError, setIsError] = useState(false);
|
||||||
|
|
||||||
|
|||||||
68
src/pages/approval-workflows/[id].tsx
Normal file
68
src/pages/approval-workflows/[id].tsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
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 { GroupWithUsers, User } from "@/interfaces/user";
|
||||||
|
import { sessionOptions } from "@/lib/session";
|
||||||
|
import { redirect } from "@/utils";
|
||||||
|
import { requestUser } from "@/utils/api";
|
||||||
|
import { getApprovalWorkflow } from "@/utils/approval.workflows.be";
|
||||||
|
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";
|
||||||
|
|
||||||
|
import approvalWorkflowsData from '../../demo/approval_workflows.json' // to test locally
|
||||||
|
|
||||||
|
export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => {
|
||||||
|
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 { id } = params as { id: string };
|
||||||
|
|
||||||
|
const approvalWorkflow = approvalWorkflowsData.find(workflow => workflow.id === id); // await getApprovalWorkflow(id);
|
||||||
|
if (!approvalWorkflow)
|
||||||
|
return redirect("/approval-workflows")
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: { user },
|
||||||
|
};
|
||||||
|
}, sessionOptions);
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const { user } = useUser({ redirectTo: "/login" });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Approval Workflows | 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>
|
||||||
|
|
||||||
|
|
||||||
|
</Layout>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,11 +9,15 @@ import { redirect } from "@/utils";
|
|||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||||
import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||||
|
import axios from "axios";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { withIronSessionSsr } from "iron-session/next";
|
import { withIronSessionSsr } from "iron-session/next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
import Link from "next/link";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ToastContainer } from "react-toastify";
|
import { BsTrash } from "react-icons/bs";
|
||||||
|
import { FaRegEdit } from "react-icons/fa";
|
||||||
|
import { toast, ToastContainer } from "react-toastify";
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<ApprovalWorkflow>();
|
const columnHelper = createColumnHelper<ApprovalWorkflow>();
|
||||||
|
|
||||||
@@ -30,14 +34,11 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
|||||||
}, sessionOptions);
|
}, sessionOptions);
|
||||||
|
|
||||||
const StatusClassNames: { [key in ApprovalWorkflowStatus]: string } = {
|
const StatusClassNames: { [key in ApprovalWorkflowStatus]: string } = {
|
||||||
approved: "bg-mti-green-light",
|
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-mti-orange-light",
|
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-mti-red-light",
|
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",
|
||||||
};
|
};
|
||||||
|
|
||||||
const escapedURL = process.env.NEXT_PUBLIC_WEBSITE_URL || "".replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
||||||
const fromHomepage = [new RegExp(`^${escapedURL}`), /\/contact$/];
|
|
||||||
|
|
||||||
type CustomStatus = ApprovalWorkflowStatus | "all" | "pending";
|
type CustomStatus = ApprovalWorkflowStatus | "all" | "pending";
|
||||||
|
|
||||||
const STATUS_OPTIONS = [
|
const STATUS_OPTIONS = [
|
||||||
@@ -65,12 +66,12 @@ const STATUS_OPTIONS = [
|
|||||||
|
|
||||||
export default function ApprovalWorkflows() {
|
export default function ApprovalWorkflows() {
|
||||||
const [filteredApprovalWorkflows, setFilteredApprovalWorkflows] = useState<ApprovalWorkflow[]>([]);
|
const [filteredApprovalWorkflows, setFilteredApprovalWorkflows] = useState<ApprovalWorkflow[]>([]);
|
||||||
const [selectedApprovalWorkflow, setSelectedApprovalWorkflow] = useState<ApprovalWorkflow>();
|
/* const [selectedApprovalWorkflow, setSelectedApprovalWorkflow] = useState<ApprovalWorkflow>(); */
|
||||||
|
|
||||||
const [statusFilter, setStatusFilter] = useState<CustomStatus>("pending");
|
const [statusFilter, setStatusFilter] = useState<CustomStatus>("all");
|
||||||
|
|
||||||
const { user } = useUser({ redirectTo: "/login" });
|
const { user } = useUser({ redirectTo: "/login" });
|
||||||
|
|
||||||
const { approvalWorkflows/* , reload */ } = useApprovalWorkflows();
|
const { approvalWorkflows/* , reload */ } = useApprovalWorkflows();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -83,6 +84,32 @@ export default function ApprovalWorkflows() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [approvalWorkflows, statusFilter]);
|
}, [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 = [
|
const columns = [
|
||||||
/* columnHelper.accessor("id", {
|
/* columnHelper.accessor("id", {
|
||||||
header: "ID",
|
header: "ID",
|
||||||
@@ -99,7 +126,7 @@ export default function ApprovalWorkflows() {
|
|||||||
columnHelper.accessor("status", {
|
columnHelper.accessor("status", {
|
||||||
header: "Status",
|
header: "Status",
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<span className={clsx("rounded-lg p-1 px-2 text-white", StatusClassNames[info.getValue()])}>
|
<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()]}
|
{ApprovalWorkflowStatusLabel[info.getValue()]}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -112,6 +139,22 @@ export default function ApprovalWorkflows() {
|
|||||||
header: "Step",
|
header: "Step",
|
||||||
cell: (info) => info.getValue(),
|
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({
|
const table = useReactTable({
|
||||||
@@ -165,11 +208,10 @@ export default function ApprovalWorkflows() {
|
|||||||
{table.getRowModel().rows.map((row) => (
|
{table.getRowModel().rows.map((row) => (
|
||||||
<tr
|
<tr
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"even:bg-mti-purple-ultralight/40 hover:bg-mti-purple-ultralight cursor-pointer rounded-lg py-2 odd:bg-white",
|
"even:bg-mti-purple-ultralight/40 rounded-lg py-2 odd:bg-white"
|
||||||
"transition duration-300 ease-in-out",
|
|
||||||
)}
|
)}
|
||||||
onClick={() => setSelectedApprovalWorkflow(row.original)}
|
|
||||||
key={row.id}>
|
key={row.id}>
|
||||||
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<td className="w-fit items-center px-4 py-2" key={cell.id}>
|
<td className="w-fit items-center px-4 py-2" key={cell.id}>
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
8
src/utils/approval.workflows.be.ts
Normal file
8
src/utils/approval.workflows.be.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
||||||
|
import client from "@/lib/mongodb";
|
||||||
|
|
||||||
|
const db = client.db(process.env.MONGODB_DB);
|
||||||
|
|
||||||
|
export const getApprovalWorkflow = async (id: string) => {
|
||||||
|
return await db.collection("approval-workflows").findOne<ApprovalWorkflow>({ id });
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user