work on non editable approval workflow steps view
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user