Work on workflows table

This commit is contained in:
Joao Correia
2025-01-22 00:30:14 +00:00
parent 73e2e95449
commit 8f8d5e5640
5 changed files with 211 additions and 58 deletions

View File

@@ -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"
}
]
} }
] ]

View File

@@ -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",
}; };

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>
)} )}
</> </>