work on non editable approval workflow steps view

This commit is contained in:
Joao Correia
2025-01-21 20:42:03 +00:00
parent 48187fc7f2
commit 73e2e95449
8 changed files with 155 additions and 42 deletions

View File

@@ -1,3 +1,4 @@
import Image from "next/image";
import React from "react"; import React from "react";
import { FaRegUser } from "react-icons/fa"; import { FaRegUser } from "react-icons/fa";
@@ -16,9 +17,11 @@ export default function RequestedBy({ name, profileImage }: Props) {
<p className="text-sm font-medium text-gray-800">Requested by</p> <p className="text-sm font-medium text-gray-800">Requested by</p>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<p className="text-xs font-medium text-gray-800">{name}</p> <p className="text-xs font-medium text-gray-800">{name}</p>
<img <Image
src={profileImage} src={profileImage}
alt={name} alt={name}
width={24}
height={24}
className="w-6 h-6 rounded-full" className="w-6 h-6 rounded-full"
/> />
</div> </div>

View File

@@ -39,7 +39,7 @@ export default function WorkflowEditableStepComponent({
return ( return (
<div className="flex w-full"> <div className="flex w-full">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<WorkflowStepNumber number={stepNumber} /> <WorkflowStepNumber stepNumber={stepNumber} />
{/* Vertical Bar connecting steps */} {/* Vertical Bar connecting steps */}
{!finalStep && ( {!finalStep && (

View File

@@ -1,25 +1,81 @@
import { WorkflowStep } from "@/interfaces/approval.workflow"; import { getUserTypeLabel, WorkflowStep } from "@/interfaces/approval.workflow";
import WorkflowStepNumber from "./WorkflowStepNumber"; import WorkflowStepNumber from "./WorkflowStepNumber";
import clsx from "clsx";
import { RiThumbUpLine } from "react-icons/ri";
import { FaWpforms } from "react-icons/fa6";
export default function WorkflowStepComponent({ export default function WorkflowStepComponent({
stepType, stepType,
stepNumber, stepNumber,
completed, completed,
editView = false, completedBy,
finalStep, finalStep,
isSelected = false, currentStep,
requestedBy, selected = false,
onDelete, assignees,
assigneesType,
onClick,
}: WorkflowStep) { }: WorkflowStep) {
return ( return (
<div className="flex w-full"> <div
<div className="flex flex-col items-center"> onClick={onClick}
<WorkflowStepNumber number={stepNumber} isSelected={isSelected} /> className={clsx("flex flex-row gap-5 w-[600px] p-6 my-4 rounded-2xl transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer", {
"bg-mti-purple-ultralight": selected,
})}
>
<div className="relative flex flex-col items-center">
<WorkflowStepNumber stepNumber={stepNumber} selected={selected} completed={completed} finalStep={finalStep} />
{/* Vertical Bar connecting steps */} {/* Vertical Bar connecting steps */}
{!finalStep && ( {!finalStep && (
<div className="w-1 h-10 bg-mti-purple-dark"></div> <div className="absolute w-1 bg-mti-purple-dark -bottom-20 h-20"></div>
)}
</div>
<div className="mt-1.5">
{stepType === "approval-by" ? (
<RiThumbUpLine size={25} />
) : (
<FaWpforms size={25} />
)
}
</div>
<div className="mt-1 flex flex-col gap-0">
{stepType === "form-intake" ? (
<>
<p className="text-sm font-medium text-gray-800">Form: Intake</p>
{completed && (
<p className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
Completed by {completedBy}
</p>
)}
{!completed && (
<p className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
In progress...
</p>
)}
</>
) : (
stepType === "approval-by" && (
<>
<p className="text-sm font-medium text-gray-800">Approval: {getUserTypeLabel(assigneesType)}</p>
{completed ? (
<p className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
Approved by {completedBy}
</p>
) : !completed && currentStep ? (
<p className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
In progress...
</p>
) : (
<p className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
Waiting for previous steps...
</p>
)}
</>
)
)} )}
</div> </div>
</div> </div>

View File

@@ -1,25 +1,25 @@
import { ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel } from "@/interfaces/approval.workflow"; import { WorkflowStep } from "@/interfaces/approval.workflow";
import clsx from "clsx"; import clsx from "clsx";
import React from "react"; import { IoCheckmarkDoneSharp, IoCheckmarkSharp } from "react-icons/io5";
import { RiProgress5Line } from "react-icons/ri";
interface Props { export default function WorkflowStepNumber({ stepNumber, selected = false, completed, finalStep }: WorkflowStep) {
number: number;
isSelected?: boolean;
}
export default function WorkflowStepNumber({ number, isSelected = false }: Props) {
return ( return (
<div <div
className={clsx( className={clsx(
'flex items-center justify-center w-11 h-11 rounded-full', 'flex items-center justify-center w-11 h-11 rounded-full',
{ {
'bg-mti-purple text-mti-purple-ultralight': isSelected, 'bg-mti-purple-dark text-mti-purple-ultralight': selected,
'bg-mti-purple-ultralight text-gray-500': !isSelected, 'bg-mti-purple-ultralight text-gray-500': !selected,
} }
)} )}
> >
<span className="text-lg font-semibold">{number}</span> {completed && finalStep ? (
<IoCheckmarkDoneSharp className="text-xl font-bold" size={25} />
) : completed && !finalStep ? (
<IoCheckmarkSharp className="text-xl font-bold" size={25} />
) : (
<span className="text-lg font-semibold">{stepNumber}</span>
)}
</div> </div>
); );
}; };

View File

@@ -18,7 +18,8 @@
"Prof. X", "Prof. X",
"Prof. Y", "Prof. Y",
"Prof. Z" "Prof. Z"
] ],
"assigneesType": "teacher"
}, },
{ {
"stepType": "approval-by", "stepType": "approval-by",
@@ -29,7 +30,8 @@
"Prof. X", "Prof. X",
"Prof. Y", "Prof. Y",
"Prof. Z" "Prof. Z"
] ],
"assigneesType": "teacher"
}, },
{ {
"stepType": "approval-by", "stepType": "approval-by",
@@ -39,7 +41,8 @@
"Prof. X", "Prof. X",
"Prof. Y", "Prof. Y",
"Prof. Z" "Prof. Z"
] ],
"assigneesType": "teacher"
}, },
{ {
"stepType": "approval-by", "stepType": "approval-by",
@@ -49,17 +52,19 @@
"Prof. X", "Prof. X",
"Prof. Y", "Prof. Y",
"Prof. Z" "Prof. Z"
] ],
"assigneesType": "teacher"
}, },
{ {
"stepType": "approval-by", "stepType": "approval-by",
"stepNumber": 5, "stepNumber": 5,
"completed": false, "completed": false,
"assignees": [ "assignees": [
"Prof. X", "Dir. X",
"Prof. Y", "Dir. Y",
"Prof. Z" "Dir. Z"
] ],
"assigneesType": "corporate"
} }
] ]
} }

View File

@@ -1,4 +1,5 @@
import {Module} from "."; import {Module} from ".";
import { CorporateUser, MasterCorporateUser, TeacherUser, userTypeLabels } from "./user";
export interface ApprovalWorkflow { export interface ApprovalWorkflow {
id: string, id: string,
@@ -10,20 +11,30 @@ export interface ApprovalWorkflow {
export type StepType = "form-intake" | "approval-by"; export type StepType = "form-intake" | "approval-by";
type AssigneesType = TeacherUser["type"] | CorporateUser["type"] | MasterCorporateUser["type"];
export interface WorkflowStep { export interface WorkflowStep {
key?: number, key?: number,
stepType: StepType, stepType?: StepType,
stepNumber: number, stepNumber: number,
completed: boolean, completed?: boolean,
completedBy?: string, completedBy?: string,
assignees?: string[], assignees?: string[],
assigneesType?: AssigneesType,
editView?: boolean, editView?: boolean,
firstStep?: boolean, firstStep?: boolean,
currentStep?: boolean,
finalStep?: boolean, finalStep?: boolean,
isSelected?: boolean, selected?: boolean,
requestedBy?: string, requestedBy?: string,
//requestedBy: TeacherUser | CorporateUser | MasterCorporateUser, //requestedBy: TeacherUser | CorporateUser | MasterCorporateUser,
onDelete?: () => void; onDelete?: () => void;
onClick?: React.MouseEventHandler<HTMLDivElement>
}
export function getUserTypeLabel(type: AssigneesType | undefined): string {
if (type) return userTypeLabels[type];
return '';
} }
export type ApprovalWorkflowStatus = "approved" | "pending" | "rejected"; export type ApprovalWorkflowStatus = "approved" | "pending" | "rejected";

View File

@@ -170,4 +170,14 @@ export interface Code {
export type Type = "student" | "teacher" | "corporate" | "admin" | "developer" | "agent" | "mastercorporate"; export type Type = "student" | "teacher" | "corporate" | "admin" | "developer" | "agent" | "mastercorporate";
export const userTypes: Type[] = ["student", "teacher", "corporate", "admin", "developer", "agent", "mastercorporate"]; export const userTypes: Type[] = ["student", "teacher", "corporate", "admin", "developer", "agent", "mastercorporate"];
export const userTypeLabels: Record<Type, string> = {
student: "Student",
teacher: "Teacher",
corporate: "Corporate",
admin: "Admin",
developer: "Developer",
agent: "Agent",
mastercorporate: "Master Corporate",
};
export type WithUser<T> = T extends { participants: string[] } ? Omit<T, "participants"> & { participants: User[] } : T; export type WithUser<T> = T extends { participants: string[] } ? Omit<T, "participants"> & { participants: User[] } : T;

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 } from "@/interfaces/approval.workflow"; import { ApprovalWorkflow, WorkflowStep } 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,6 +17,10 @@ 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";
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);
@@ -27,24 +31,31 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }
const { id } = params as { id: string }; const { id } = params as { id: string };
const approvalWorkflow = approvalWorkflowsData.find(workflow => workflow.id === id); // await getApprovalWorkflow(id); const workflow = approvalWorkflowsData.find(workflow => workflow.id === id); // await getApprovalWorkflow(id);
if (!approvalWorkflow)
if (!workflow)
return redirect("/approval-workflows") return redirect("/approval-workflows")
return { return {
props: { user, approvalWorkflow }, props: { user, workflow },
}; };
}, sessionOptions); }, sessionOptions);
export default function Home({ approvalWorkflow }: { approvalWorkflow: ApprovalWorkflow }) { export default function Home({ workflow }: { workflow: ApprovalWorkflow }) {
const { user } = useUser({ redirectTo: "/login" }); const { user } = useUser({ redirectTo: "/login" });
const steps = workflow.steps;
const [selectedIndex, setSelectedIndex] = useState(steps.length - 1);
const handleStepClick = (index: number) => {
setSelectedIndex(index);
};
return ( return (
<> <>
<Head> <Head>
<title> {approvalWorkflow.name} | EnCoach</title> <title> {workflow.name} | EnCoach</title>
<meta <meta
name="description" name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop." content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
@@ -62,7 +73,7 @@ export default function Home({ approvalWorkflow }: { approvalWorkflow: ApprovalW
className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl"> className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl">
<BsChevronLeft /> <BsChevronLeft />
</Link> </Link>
<h1 className="text-2xl font-semibold">{approvalWorkflow.name}</h1> <h1 className="text-2xl font-semibold">{workflow.name}</h1>
</div> </div>
</section> </section>
<section className="flex flex-col gap-6"> <section className="flex flex-col gap-6">
@@ -79,6 +90,23 @@ export default function Home({ approvalWorkflow }: { approvalWorkflow: ApprovalW
/> />
</div> </div>
</section> </section>
<section className="flex flex-col gap-0">
{steps.map((step, index) => (
<WorkflowStepComponent
key={index}
completed={step.completed}
completedBy={step.completedBy}
stepNumber={step.stepNumber}
stepType={step.stepType}
assignees={step.assignees}
assigneesType={step.assigneesType}
finalStep={index === steps.length - 1}
currentStep={steps.findIndex(step => !step.completed) === index}
selected={index === selectedIndex} // Determine selected state
onClick={() => handleStepClick(index)} // Add click handler
/>
))}
</section>
</Layout> </Layout>
)} )}
</> </>