Work on workflows table
This commit is contained in:
@@ -1,13 +1,12 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "kajhfakscbka-asacaca-acawesae",
|
"id": "kajhfakscbka-asacaca-acawesae",
|
||||||
"name": "name-1",
|
"name": "English Exam 1st Quarter 2025",
|
||||||
"modules": [
|
"modules": [
|
||||||
"reading",
|
"reading",
|
||||||
"writing"
|
"writing"
|
||||||
],
|
],
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"approvers": "prof-1",
|
|
||||||
"steps": [
|
"steps": [
|
||||||
{
|
{
|
||||||
"stepType": "form-intake",
|
"stepType": "form-intake",
|
||||||
@@ -67,5 +66,79 @@
|
|||||||
"assigneesType": "corporate"
|
"assigneesType": "corporate"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aaaaaakscbka-asacaca-acawesae",
|
||||||
|
"name": "English Exam 2nd Quarter 2025",
|
||||||
|
"modules": [
|
||||||
|
"reading",
|
||||||
|
"writing",
|
||||||
|
"level",
|
||||||
|
"speaking",
|
||||||
|
"listening"
|
||||||
|
],
|
||||||
|
"status": "approved",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"stepType": "form-intake",
|
||||||
|
"stepNumber": 1,
|
||||||
|
"completed": true,
|
||||||
|
"completedBy": "Prof. X",
|
||||||
|
"assignees": [
|
||||||
|
"Prof. X",
|
||||||
|
"Prof. Y",
|
||||||
|
"Prof. Z"
|
||||||
|
],
|
||||||
|
"assigneesType": "teacher"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"stepType": "approval-by",
|
||||||
|
"stepNumber": 2,
|
||||||
|
"completed": true,
|
||||||
|
"completedBy": "Prof. Y",
|
||||||
|
"assignees": [
|
||||||
|
"Prof. X",
|
||||||
|
"Prof. Y",
|
||||||
|
"Prof. Z"
|
||||||
|
],
|
||||||
|
"assigneesType": "teacher"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"stepType": "approval-by",
|
||||||
|
"stepNumber": 3,
|
||||||
|
"completed": true,
|
||||||
|
"completedBy": "Prof. Y",
|
||||||
|
"assignees": [
|
||||||
|
"Prof. X",
|
||||||
|
"Prof. Y",
|
||||||
|
"Prof. Z"
|
||||||
|
],
|
||||||
|
"assigneesType": "teacher"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"stepType": "approval-by",
|
||||||
|
"stepNumber": 4,
|
||||||
|
"completed": true,
|
||||||
|
"completedBy": "Prof. Y",
|
||||||
|
"assignees": [
|
||||||
|
"Prof. X",
|
||||||
|
"Prof. Y",
|
||||||
|
"Prof. Z"
|
||||||
|
],
|
||||||
|
"assigneesType": "teacher"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"stepType": "approval-by",
|
||||||
|
"stepNumber": 5,
|
||||||
|
"completed": true,
|
||||||
|
"completedBy": "Prof. Y",
|
||||||
|
"assignees": [
|
||||||
|
"Dir. X",
|
||||||
|
"Dir. Y",
|
||||||
|
"Dir. Z"
|
||||||
|
],
|
||||||
|
"assigneesType": "corporate"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -10,6 +10,10 @@ export interface ApprovalWorkflow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type StepType = "form-intake" | "approval-by";
|
export type StepType = "form-intake" | "approval-by";
|
||||||
|
export const StepTypeLabel: Record<StepType, string> = {
|
||||||
|
"form-intake": "Form Intake",
|
||||||
|
"approval-by": "Approval",
|
||||||
|
};
|
||||||
|
|
||||||
type AssigneesType = TeacherUser["type"] | CorporateUser["type"] | MasterCorporateUser["type"];
|
type AssigneesType = TeacherUser["type"] | CorporateUser["type"] | MasterCorporateUser["type"];
|
||||||
|
|
||||||
@@ -38,8 +42,8 @@ export function getUserTypeLabel(type: AssigneesType | undefined): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ApprovalWorkflowStatus = "approved" | "pending" | "rejected";
|
export type ApprovalWorkflowStatus = "approved" | "pending" | "rejected";
|
||||||
export const ApprovalWorkflowStatusLabel: {[key in ApprovalWorkflowStatus]: string} = {
|
export const ApprovalWorkflowStatusLabel: Record<ApprovalWorkflowStatus, string> = {
|
||||||
approved: "Approved",
|
approved: "Approved",
|
||||||
pending: "Pending",
|
pending: "Pending",
|
||||||
rejected: "Rejected",
|
rejected: "Rejected",
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
export type Module = "reading" | "listening" | "writing" | "speaking" | "level";
|
export type Module = "reading" | "listening" | "writing" | "speaking" | "level";
|
||||||
|
export const ModuleTypeLabels: Record<Module, string> = {
|
||||||
|
reading: "Reading",
|
||||||
|
listening: "Listening",
|
||||||
|
writing: "Writing",
|
||||||
|
speaking: "Speaking",
|
||||||
|
level: "Level",
|
||||||
|
};
|
||||||
|
|
||||||
export interface Step {
|
export interface Step {
|
||||||
min: number;
|
min: number;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Layout from "@/components/High/Layout";
|
import Layout from "@/components/High/Layout";
|
||||||
import useUser from "@/hooks/useUser";
|
import useUser from "@/hooks/useUser";
|
||||||
import { ApprovalWorkflow, WorkflowStep } from "@/interfaces/approval.workflow";
|
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { redirect } from "@/utils";
|
import { redirect } from "@/utils";
|
||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
@@ -17,10 +17,8 @@ import approvalWorkflowsData from '../../demo/approval_workflows.json'; // to te
|
|||||||
import RequestedBy from "@/components/ApprovalWorkflows/RequestedBy";
|
import RequestedBy from "@/components/ApprovalWorkflows/RequestedBy";
|
||||||
import StartedOn from "@/components/ApprovalWorkflows/StartedOn";
|
import StartedOn from "@/components/ApprovalWorkflows/StartedOn";
|
||||||
import Status from "@/components/ApprovalWorkflows/Status";
|
import Status from "@/components/ApprovalWorkflows/Status";
|
||||||
import { useState } from "react";
|
|
||||||
import Button from "@/components/Low/Button";
|
|
||||||
import WorkflowEditableStepComponent from "@/components/ApprovalWorkflows/WorkflowEditableStepComponent";
|
|
||||||
import WorkflowStepComponent from "@/components/ApprovalWorkflows/WorkflowStepComponent";
|
import WorkflowStepComponent from "@/components/ApprovalWorkflows/WorkflowStepComponent";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => {
|
export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => {
|
||||||
const user = await requestUser(req, res);
|
const user = await requestUser(req, res);
|
||||||
@@ -102,8 +100,8 @@ export default function Home({ workflow }: { workflow: ApprovalWorkflow }) {
|
|||||||
assigneesType={step.assigneesType}
|
assigneesType={step.assigneesType}
|
||||||
finalStep={index === steps.length - 1}
|
finalStep={index === steps.length - 1}
|
||||||
currentStep={steps.findIndex(step => !step.completed) === index}
|
currentStep={steps.findIndex(step => !step.completed) === index}
|
||||||
selected={index === selectedIndex} // Determine selected state
|
selected={index === selectedIndex}
|
||||||
onClick={() => handleStepClick(index)} // Add click handler
|
onClick={() => handleStepClick(index)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import Button from "@/components/Low/Button";
|
|||||||
import Select from "@/components/Low/Select";
|
import Select from "@/components/Low/Select";
|
||||||
import useApprovalWorkflows from "@/hooks/useApprovalWorkflows";
|
import useApprovalWorkflows from "@/hooks/useApprovalWorkflows";
|
||||||
import useUser from "@/hooks/useUser";
|
import useUser from "@/hooks/useUser";
|
||||||
import { ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel } from "@/interfaces/approval.workflow";
|
import { Module, ModuleTypeLabels } from "@/interfaces";
|
||||||
|
import { ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel, StepTypeLabel } from "@/interfaces/approval.workflow";
|
||||||
import { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { redirect } from "@/utils";
|
import { redirect } from "@/utils";
|
||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
@@ -117,31 +118,74 @@ export default function ApprovalWorkflows() {
|
|||||||
cell: (info) => info.getValue(),
|
cell: (info) => info.getValue(),
|
||||||
}), */
|
}), */
|
||||||
columnHelper.accessor("name", {
|
columnHelper.accessor("name", {
|
||||||
header: "Name",
|
header: "NAME",
|
||||||
cell: (info) => info.getValue(),
|
|
||||||
}),
|
|
||||||
/* columnHelper.accessor("module", {
|
|
||||||
header: "Module",
|
|
||||||
cell: (info) => info.getValue(),
|
|
||||||
}), */
|
|
||||||
columnHelper.accessor("status", {
|
|
||||||
header: "Status",
|
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<span className={clsx("rounded-full px-3 py-1 text-sm font-medium inline-block text-left w-[110px]", StatusClassNames[info.getValue()])}>
|
<span className="font-medium">
|
||||||
|
{info.getValue()}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("modules", {
|
||||||
|
header: "MODULES",
|
||||||
|
cell: (info) => (
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{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"
|
||||||
|
>
|
||||||
|
{ModuleTypeLabels[module]}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("status", {
|
||||||
|
header: "STATUS",
|
||||||
|
cell: (info) => (
|
||||||
|
<span className={clsx("inline-block rounded-full px-3 py-1 text-sm font-medium text-left w-[110px]", StatusClassNames[info.getValue()])}>
|
||||||
{ApprovalWorkflowStatusLabel[info.getValue()]}
|
{ApprovalWorkflowStatusLabel[info.getValue()]}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
/* columnHelper.accessor("approvers", {
|
columnHelper.accessor("steps", {
|
||||||
header: "Approvers",
|
header: "CURRENT APPROVERS",
|
||||||
cell: (info) => info.getValue(),
|
cell: (info) => {
|
||||||
|
const steps = info.row.original.steps;
|
||||||
|
const currentStep = steps.find((step) => !step.completed);
|
||||||
|
const approvers = currentStep?.assignees || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{approvers.map((approver: string, index: number) => (
|
||||||
|
<span
|
||||||
|
key={index}
|
||||||
|
className="inline-block rounded-full px-3 py-1 text-sm font-medium bg-gray-100 border border-gray-300 text-gray-900"
|
||||||
|
>
|
||||||
|
{approver}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("steps", {
|
||||||
|
header: "CURRENT STEP",
|
||||||
|
cell: (info) => {
|
||||||
|
const steps = info.row.original.steps;
|
||||||
|
const currentStep = steps.find((step) => !step.completed);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="font-medium">
|
||||||
|
{currentStep
|
||||||
|
? `Step ${currentStep.stepNumber}: ${StepTypeLabel[currentStep.stepType!]}`
|
||||||
|
: "Completed"}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("step", {
|
|
||||||
header: "Step",
|
|
||||||
cell: (info) => info.getValue(),
|
|
||||||
}), */
|
|
||||||
{
|
{
|
||||||
header: "Actions",
|
header: "ACTIONS",
|
||||||
id: "actions",
|
id: "actions",
|
||||||
cell: ({ row }: { row: { original: ApprovalWorkflow } }) => {
|
cell: ({ row }: { row: { original: ApprovalWorkflow } }) => {
|
||||||
return (
|
return (
|
||||||
@@ -204,35 +248,62 @@ export default function ApprovalWorkflows() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table className="bg-mti-purple-ultralight/40 w-full rounded-xl">
|
<div className="px-6 pb-4 bg-purple-100 rounded-2xl">
|
||||||
<thead>
|
<table
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
className="w-full table-auto border-separate border-spacing-y-2"
|
||||||
<tr key={headerGroup.id}>
|
style={{ tableLayout: "auto" }}
|
||||||
{headerGroup.headers.map((header) => (
|
>
|
||||||
<th className="px-4 py-4 text-left" key={header.id}>
|
<thead>
|
||||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
</th>
|
<tr key={headerGroup.id}>
|
||||||
))}
|
{headerGroup.headers.map((header) => (
|
||||||
</tr>
|
<th key={header.id} className="px-2 py-3 text-left text-purple-900">
|
||||||
))}
|
{header.isPlaceholder
|
||||||
</thead>
|
? null
|
||||||
<tbody className="px-2">
|
: flexRender(
|
||||||
{table.getRowModel().rows.map((row) => (
|
header.column.columnDef.header,
|
||||||
<tr
|
header.getContext()
|
||||||
className={clsx(
|
)}
|
||||||
"even:bg-mti-purple-ultralight/40 rounded-lg py-2 odd:bg-white"
|
</th>
|
||||||
)}
|
))}
|
||||||
key={row.id}>
|
</tr>
|
||||||
|
))}
|
||||||
|
</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
|
||||||
|
*/}
|
||||||
|
{row.getVisibleCells().map((cell, cellIndex) => {
|
||||||
|
const lastCellIndex = row.getVisibleCells().length - 1;
|
||||||
|
|
||||||
|
let cellClasses = "px-4 py-2 bg-purple-50 border-y-2 border-purple-300";
|
||||||
|
if (cellIndex === 0) {
|
||||||
|
cellClasses += " border-l-2 rounded-l-2xl";
|
||||||
|
}
|
||||||
|
if (cellIndex === lastCellIndex) {
|
||||||
|
cellClasses += " border-r-2 rounded-r-2xl";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<td key={cell.id} className={cellClasses}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{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>
|
</Layout>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user