- Refactor of workflow and steps types to differentiate between editView and normalView.
- Added side panel with steps details
This commit is contained in:
@@ -28,10 +28,9 @@ export default function StartedOn({ date }: Props) {
|
||||
<div>
|
||||
<p className="pb-1 text-sm font-medium text-gray-800">Started on</p>
|
||||
<div className="flex items-center">
|
||||
{/* Display the formatted date and add a title attribute for hover */}
|
||||
<p
|
||||
className="text-xs font-medium text-gray-800"
|
||||
title={fullDateTime} // Shows full date and time on hover
|
||||
title={fullDateTime}
|
||||
>
|
||||
{yearMonthDay}
|
||||
</p>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { WorkflowStep } from "@/interfaces/approval.workflow";
|
||||
import { EditableWorkflowStep } from "@/interfaces/approval.workflow";
|
||||
import Option from "@/interfaces/option";
|
||||
import { CorporateUser, TeacherUser } from "@/interfaces/user";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { AiOutlineUserAdd } from "react-icons/ai";
|
||||
import { BsTrash } from "react-icons/bs";
|
||||
import { LuGripHorizontal } from "react-icons/lu";
|
||||
import WorkflowStepNumber from "./WorkflowStepNumber";
|
||||
import WorkflowStepSelects from "./WorkflowStepSelects";
|
||||
import Image from "next/image";
|
||||
|
||||
interface Props extends WorkflowStep {
|
||||
interface Props extends Pick<EditableWorkflowStep, 'stepNumber' | 'assignees' | 'finalStep' | 'onDelete'> {
|
||||
entityTeachers: TeacherUser[];
|
||||
entityCorporates: CorporateUser[];
|
||||
onSelectChange: (numberOfSelects: number, index: number, value: Option | null) => void;
|
||||
@@ -102,7 +102,7 @@ export default function WorkflowEditableStepComponent({
|
||||
return (
|
||||
<div className="flex w-full">
|
||||
<div className="flex flex-col items-center">
|
||||
<WorkflowStepNumber stepNumber={stepNumber} />
|
||||
<WorkflowStepNumber stepNumber={stepNumber} completed={false} selected={false} />
|
||||
|
||||
{/* Vertical Bar connecting steps */}
|
||||
{!finalStep && (
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { ApprovalWorkflow, WorkflowStep } from "@/interfaces/approval.workflow";
|
||||
import { EditableApprovalWorkflow, EditableWorkflowStep } from "@/interfaces/approval.workflow";
|
||||
import Option from "@/interfaces/option";
|
||||
import { CorporateUser, TeacherUser } from "@/interfaces/user";
|
||||
import { AnimatePresence, AnimateSharedLayout, Reorder, motion } from "framer-motion";
|
||||
import { AnimatePresence, Reorder, motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
import { FaRegCheckCircle } from "react-icons/fa";
|
||||
import { IoIosAddCircleOutline } from "react-icons/io";
|
||||
import Button from "../Low/Button";
|
||||
import WorkflowEditableStepComponent from "./WorkflowEditableStepComponent";
|
||||
import Tip from "./Tip";
|
||||
import WorkflowEditableStepComponent from "./WorkflowEditableStepComponent";
|
||||
|
||||
interface Props {
|
||||
workflow: ApprovalWorkflow;
|
||||
onWorkflowChange: (workflow: ApprovalWorkflow) => void;
|
||||
workflow: EditableApprovalWorkflow;
|
||||
onWorkflowChange: (workflow: EditableApprovalWorkflow) => void;
|
||||
entityTeachers: TeacherUser[];
|
||||
entityCorporates: CorporateUser[];
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityTeacher
|
||||
const [stepCounter, setStepCounter] = useState<number>(3); // to guarantee unique keys used for animations
|
||||
const lastStep = workflow.steps[workflow.steps.length - 1];
|
||||
|
||||
const renumberSteps = (steps: WorkflowStep[]): WorkflowStep[] => {
|
||||
const renumberSteps = (steps: EditableWorkflowStep[]): EditableWorkflowStep[] => {
|
||||
return steps.map((step, index) => ({
|
||||
...step,
|
||||
stepNumber: index + 1,
|
||||
@@ -28,12 +28,13 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityTeacher
|
||||
};
|
||||
|
||||
const addStep = () => {
|
||||
const newStep: WorkflowStep = {
|
||||
const newStep: EditableWorkflowStep = {
|
||||
key: stepCounter,
|
||||
stepType: "approval-by",
|
||||
stepNumber: workflow.steps.length,
|
||||
completed: false,
|
||||
assignees: [null],
|
||||
firstStep: false,
|
||||
finalStep: false,
|
||||
};
|
||||
setStepCounter((count) => count + 1);
|
||||
|
||||
@@ -73,7 +74,7 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityTeacher
|
||||
onWorkflowChange({ ...workflow, steps: updatedSteps });
|
||||
};
|
||||
|
||||
const handleReorder = (newOrder: WorkflowStep[]) => {
|
||||
const handleReorder = (newOrder: EditableWorkflowStep[]) => {
|
||||
const firstIndex = newOrder.findIndex((s) => s.firstStep);
|
||||
if (firstIndex !== -1 && firstIndex !== 0) {
|
||||
const [first] = newOrder.splice(firstIndex, 1);
|
||||
|
||||
@@ -16,21 +16,20 @@ export default function WorkflowStepComponent({
|
||||
stepNumber,
|
||||
completed,
|
||||
completedBy,
|
||||
assignees,
|
||||
finalStep,
|
||||
currentStep,
|
||||
selected = false,
|
||||
assignees,
|
||||
assigneesType,
|
||||
onClick,
|
||||
}: Props) {
|
||||
|
||||
const completedByUser = workflowAssignees.find((assignee) => assignee.id === completedBy);
|
||||
const assigneesUsers = workflowAssignees.filter(user => assignees!.includes(user.id));
|
||||
const assigneesUsers = workflowAssignees.filter(user => assignees.includes(user.id));
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={clsx("flex flex-row gap-5 w-[700px] p-6 my-4 rounded-2xl transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer", {
|
||||
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,
|
||||
})}
|
||||
>
|
||||
@@ -85,9 +84,9 @@ export default function WorkflowStepComponent({
|
||||
) : (
|
||||
stepType === "approval-by" && (
|
||||
<>
|
||||
<p className="text-sm font-medium text-gray-800">Approval: {getUserTypeLabel(assigneesType)}</p>
|
||||
{completed && completedBy ? (
|
||||
<div className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
|
||||
<p className="text-sm font-medium text-gray-800">Approval: {getUserTypeLabel(completedByUser!.type)} Approval</p>
|
||||
<UserWithProfilePic
|
||||
prefix={`Approved by: ${getUserTypeLabelShort(completedByUser!.type)}`}
|
||||
name={completedByUser!.name}
|
||||
@@ -96,6 +95,7 @@ export default function WorkflowStepComponent({
|
||||
</div>
|
||||
) : !completed && currentStep ? (
|
||||
<div className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
|
||||
<p className="text-sm font-medium text-gray-800">Approval: </p>
|
||||
In Progress... Assignees:
|
||||
<div className="flex flex-row flex-wrap gap-3 items-center">
|
||||
{assigneesUsers.map(user => (
|
||||
@@ -111,6 +111,7 @@ export default function WorkflowStepComponent({
|
||||
</div>
|
||||
) : (
|
||||
<div className={clsx("text-xs font-medium", { "text-mti-purple-ultradark": selected, "text-gray-800": !selected })}>
|
||||
<p className="text-sm font-medium text-gray-800">Approval: </p>
|
||||
Waiting for previous steps...
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,9 @@ import { WorkflowStep } from "@/interfaces/approval.workflow";
|
||||
import clsx from "clsx";
|
||||
import { IoCheckmarkDoneSharp, IoCheckmarkSharp } from "react-icons/io5";
|
||||
|
||||
export default function WorkflowStepNumber({ stepNumber, selected = false, completed, finalStep }: WorkflowStep) {
|
||||
type Props = Pick<WorkflowStep, 'stepNumber' | 'completed' | 'finalStep' | 'selected'>
|
||||
|
||||
export default function WorkflowStepNumber({ stepNumber, selected = false, completed, finalStep }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
||||
@@ -16,22 +16,26 @@
|
||||
"stepNumber": 1,
|
||||
"completed": true,
|
||||
"completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"completedDate": 1737712243906,
|
||||
"assignees": [
|
||||
"fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"c5fc1514-1a94-4f8c-a046-a62099097a50"
|
||||
]
|
||||
],
|
||||
"comments": "This is a random comment\nThis is a random comment\nThis is a random comment\nThis is a random comment\nThis is a random comment\n"
|
||||
},
|
||||
{
|
||||
"stepType": "approval-by",
|
||||
"stepNumber": 2,
|
||||
"completed": true,
|
||||
"completedBy": "c5fc1514-1a94-4f8c-a046-a62099097a50",
|
||||
"completedDate": 1737712243906,
|
||||
"assignees": [
|
||||
"fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"c5fc1514-1a94-4f8c-a046-a62099097a50"
|
||||
]
|
||||
],
|
||||
"comments": "This is a random comment"
|
||||
},
|
||||
{
|
||||
"stepType": "approval-by",
|
||||
@@ -41,7 +45,8 @@
|
||||
"fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"c5fc1514-1a94-4f8c-a046-a62099097a50"
|
||||
]
|
||||
],
|
||||
"comments": "This is a random comment"
|
||||
},
|
||||
{
|
||||
"stepType": "approval-by",
|
||||
@@ -51,7 +56,8 @@
|
||||
"fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"c5fc1514-1a94-4f8c-a046-a62099097a50"
|
||||
]
|
||||
],
|
||||
"comments": "This is a random comment"
|
||||
},
|
||||
{
|
||||
"stepType": "approval-by",
|
||||
@@ -61,7 +67,8 @@
|
||||
"fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"c5fc1514-1a94-4f8c-a046-a62099097a50"
|
||||
]
|
||||
],
|
||||
"comments": "This is a random comment"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -85,55 +92,65 @@
|
||||
"stepNumber": 1,
|
||||
"completed": true,
|
||||
"completedBy": "fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"completedDate": 1737712243906,
|
||||
"assignees": [
|
||||
"fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"c5fc1514-1a94-4f8c-a046-a62099097a50"
|
||||
]
|
||||
],
|
||||
"comments": "This is a random comment"
|
||||
},
|
||||
{
|
||||
"stepType": "approval-by",
|
||||
"stepNumber": 2,
|
||||
"completed": true,
|
||||
"completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"completedDate": 1737712243906,
|
||||
"assignees": [
|
||||
"fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"c5fc1514-1a94-4f8c-a046-a62099097a50"
|
||||
]
|
||||
],
|
||||
"comments": "This is a random comment"
|
||||
},
|
||||
{
|
||||
"stepType": "approval-by",
|
||||
"stepNumber": 3,
|
||||
"completed": true,
|
||||
"completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"completedDate": 1737712243906,
|
||||
"assignees": [
|
||||
"fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"c5fc1514-1a94-4f8c-a046-a62099097a50"
|
||||
]
|
||||
],
|
||||
"comments": "This is a random comment"
|
||||
},
|
||||
{
|
||||
"stepType": "approval-by",
|
||||
"stepNumber": 4,
|
||||
"completed": true,
|
||||
"completedBy": "231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"completedDate": 1737712243906,
|
||||
"assignees": [
|
||||
"fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"c5fc1514-1a94-4f8c-a046-a62099097a50"
|
||||
]
|
||||
],
|
||||
"comments": "This is a random comment"
|
||||
},
|
||||
{
|
||||
"stepType": "approval-by",
|
||||
"stepNumber": 5,
|
||||
"completed": true,
|
||||
"completedBy": "c5fc1514-1a94-4f8c-a046-a62099097a50",
|
||||
"completedDate": 1737712243906,
|
||||
"assignees": [
|
||||
"fd5fce42-4bcc-4150-a143-b484e750b265",
|
||||
"231c84b2-a65a-49a9-803c-c664d84b13e0",
|
||||
"c5fc1514-1a94-4f8c-a046-a62099097a50"
|
||||
]
|
||||
],
|
||||
"comments": "This is a random comment"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,6 +12,17 @@ export interface ApprovalWorkflow {
|
||||
steps: WorkflowStep[],
|
||||
}
|
||||
|
||||
export interface EditableApprovalWorkflow {
|
||||
id: string,
|
||||
name: string,
|
||||
entityId: string,
|
||||
requester: User["id"],
|
||||
startDate: number,
|
||||
modules: Module[],
|
||||
status: ApprovalWorkflowStatus,
|
||||
steps: EditableWorkflowStep[],
|
||||
}
|
||||
|
||||
export type StepType = "form-intake" | "approval-by";
|
||||
export const StepTypeLabel: Record<StepType, string> = {
|
||||
"form-intake": "Form Intake",
|
||||
@@ -19,19 +30,28 @@ export const StepTypeLabel: Record<StepType, string> = {
|
||||
};
|
||||
|
||||
export interface WorkflowStep {
|
||||
key?: number,
|
||||
stepType?: StepType,
|
||||
stepType: StepType,
|
||||
stepNumber: number,
|
||||
completed?: boolean,
|
||||
completed: boolean,
|
||||
completedBy?: User["id"],
|
||||
assignees?: (User["id"] | null | undefined)[]; // bit of an hack, but allowing null or undefined values allows us to match one to one the select input components with the assignees array. And since select inputs allow undefined or null values, it is allowed here too, but must validate required input before form submission
|
||||
assigneesType?: Type,
|
||||
completedDate?: number,
|
||||
assignees: (User["id"])[];
|
||||
firstStep?: boolean,
|
||||
currentStep?: boolean,
|
||||
finalStep?: boolean,
|
||||
selected?: boolean,
|
||||
selected: boolean,
|
||||
comments?: string,
|
||||
onClick: React.MouseEventHandler<HTMLDivElement>
|
||||
}
|
||||
|
||||
export interface EditableWorkflowStep {
|
||||
key: number,
|
||||
stepType: StepType,
|
||||
stepNumber: number,
|
||||
assignees: (User["id"] | null | undefined)[]; // bit of an hack, but allowing null or undefined values allows us to match one to one the select input components with the assignees array. And since select inputs allow undefined or null values, it is allowed here too, but must validate required input before form submission
|
||||
firstStep: boolean,
|
||||
finalStep?: boolean,
|
||||
onDelete?: () => void;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>
|
||||
}
|
||||
|
||||
export function getUserTypeLabel(type: Type | undefined): string {
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import RequestedBy from "@/components/ApprovalWorkflows/RequestedBy";
|
||||
import StartedOn from "@/components/ApprovalWorkflows/StartedOn";
|
||||
import Status from "@/components/ApprovalWorkflows/Status";
|
||||
import UserWithProfilePic from "@/components/ApprovalWorkflows/UserWithProfilePic";
|
||||
import WorkflowStepComponent from "@/components/ApprovalWorkflows/WorkflowStepComponent";
|
||||
import Layout from "@/components/High/Layout";
|
||||
import { ApprovalWorkflow, getUserTypeLabelShort } from "@/interfaces/approval.workflow";
|
||||
import { ApprovalWorkflow, getUserTypeLabelShort, WorkflowStep } from "@/interfaces/approval.workflow";
|
||||
import { User } from "@/interfaces/user";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import { redirect, serialize } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||
import { getSpecificUsers, getUser } from "@/utils/users.be";
|
||||
import { withIronSessionSsr } from "iron-session/next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { BsChevronLeft } from "react-icons/bs";
|
||||
import { FaWpforms } from "react-icons/fa6";
|
||||
import { MdOutlineDoubleArrow } from "react-icons/md";
|
||||
import { RiThumbUpLine } from "react-icons/ri";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
|
||||
import approvalWorkflowsData from '../../demo/approval_workflows.json'; // to test locally
|
||||
|
||||
import RequestedBy from "@/components/ApprovalWorkflows/RequestedBy";
|
||||
import StartedOn from "@/components/ApprovalWorkflows/StartedOn";
|
||||
import Status from "@/components/ApprovalWorkflows/Status";
|
||||
import WorkflowStepComponent from "@/components/ApprovalWorkflows/WorkflowStepComponent";
|
||||
import { User } from "@/interfaces/user";
|
||||
import { getSpecificUsers, getUser } from "@/utils/users.be";
|
||||
import { useState } from "react";
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => {
|
||||
const user = await requestUser(req, res);
|
||||
if (!user) return redirect("/login")
|
||||
@@ -29,7 +32,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }
|
||||
|
||||
const { id } = params as { id: string };
|
||||
|
||||
// replace later with await getApprovalWorkflow(id). Don't think a hook is needed here;
|
||||
// replace later with await getApprovalWorkflow(id).
|
||||
const approvalWorkflowsDataAsWorkflows = approvalWorkflowsData as ApprovalWorkflow[];
|
||||
const workflow: ApprovalWorkflow | undefined = approvalWorkflowsDataAsWorkflows.find(workflow => workflow.id === id);
|
||||
|
||||
@@ -40,7 +43,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }
|
||||
...new Set(
|
||||
workflow.steps
|
||||
.map(step => step.assignees)
|
||||
.flat() as string[] // we are sure assignees coming from a db workflow are all valid strings.
|
||||
.flat()
|
||||
)
|
||||
];
|
||||
|
||||
@@ -68,10 +71,20 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
||||
if (currentStep === -1)
|
||||
currentStep = steps.length - 1;
|
||||
|
||||
const [selectedIndex, setSelectedIndex] = useState(currentStep);
|
||||
const [selectedStepIndex, setSelectedStepIndex] = useState<number>(currentStep);
|
||||
const [selectedStep, setSelectedStep] = useState<WorkflowStep>(steps[selectedStepIndex]);
|
||||
const [isPanelOpen, setIsPanelOpen] = useState(true);
|
||||
const [comments, setComments] = useState<string>(selectedStep.comments || "");
|
||||
|
||||
const handleStepClick = (index: number, stepInfo: WorkflowStep) => {
|
||||
setSelectedStep(stepInfo);
|
||||
setSelectedStepIndex(index);
|
||||
setComments(stepInfo.comments || "");
|
||||
setIsPanelOpen(true);
|
||||
};
|
||||
|
||||
const saveComments = () => {
|
||||
|
||||
const handleStepClick = (index: number) => {
|
||||
setSelectedIndex(index);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -88,16 +101,16 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
||||
<ToastContainer />
|
||||
{user && (
|
||||
<Layout user={user} className="gap-6">
|
||||
<section className="flex flex-col gap-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
href="/approval-workflows"
|
||||
className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl">
|
||||
<BsChevronLeft />
|
||||
</Link>
|
||||
<h1 className="text-2xl font-semibold">{workflow.name}</h1>
|
||||
</div>
|
||||
|
||||
<section className="flex items-center gap-2">
|
||||
<Link
|
||||
href="/approval-workflows"
|
||||
className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl">
|
||||
<BsChevronLeft />
|
||||
</Link>
|
||||
<h1 className="text-2xl font-semibold">{workflow.name}</h1>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-6">
|
||||
<div className="flex flex-row gap-6">
|
||||
<RequestedBy
|
||||
@@ -113,6 +126,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-0">
|
||||
{steps.map((step, index) => (
|
||||
<WorkflowStepComponent
|
||||
@@ -123,14 +137,95 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
|
||||
stepNumber={step.stepNumber}
|
||||
stepType={step.stepType}
|
||||
assignees={step.assignees}
|
||||
assigneesType={step.assigneesType}
|
||||
finalStep={index === steps.length - 1}
|
||||
currentStep={currentStep === index}
|
||||
selected={index === selectedIndex}
|
||||
onClick={() => handleStepClick(index)}
|
||||
currentStep={index === currentStep}
|
||||
selected={index === selectedStepIndex}
|
||||
onClick={() => handleStepClick(index, step)}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className={`absolute inset-y-0 right-0 h-full bg-mti-purple-ultralight bg-opacity-50 shadow-xl shadow-mti-purple transition-all duration-300 overflow-hidden ${isPanelOpen ? 'w-2/5' : 'w-0'}`}>
|
||||
{isPanelOpen && (
|
||||
<div className="relative inset-y-0 right-0 h-full p-6">
|
||||
<div className="flex flex-row gap-2">
|
||||
<p className="text-2xl font-medium text-left align-middle">Step {selectedStepIndex + 1}</p>
|
||||
<div className="ml-auto flex flex-row">
|
||||
<button
|
||||
className="min-w-fit max-h-fit text-lg font-medium flex items-center gap-2 text-left"
|
||||
onClick={() => setIsPanelOpen(false)}
|
||||
>
|
||||
Collapse
|
||||
<MdOutlineDoubleArrow size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-4 h-[4px] bg-mti-purple-ultralight rounded-full w-full" />
|
||||
|
||||
<div>
|
||||
<div className="my-8 flex flex-row gap-4 items-center text-lg font-medium">
|
||||
{selectedStep.stepType === "approval-by" ? (
|
||||
<>
|
||||
<RiThumbUpLine size={30} />
|
||||
Approval Step
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaWpforms size={30} />
|
||||
Form Intake Step
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{selectedStep.completed ? (
|
||||
<div className={"text-base font-medium text-gray-500 my-7"}>
|
||||
Approved on {new Date(selectedStep.completedDate!).toLocaleString("en-CA", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false,
|
||||
}).replace(", ", " at ")}
|
||||
<p className="my-4 text-sm">No additional actions are required.</p>
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<div className={"text-base font-medium text-gray-500"}>
|
||||
One assignee is required to sign off to complete this step:
|
||||
<div className="flex flex-col gap-2 mt-3">
|
||||
{workflowAssignees.map(user => (
|
||||
<span key={user.id}>
|
||||
<UserWithProfilePic
|
||||
prefix={getUserTypeLabelShort(user.type)}
|
||||
name={user.name}
|
||||
profileImage={user.profilePicture}
|
||||
/>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<hr className="my-4 h-[4px] bg-mti-purple-ultralight rounded-full w-full" />
|
||||
|
||||
<textarea
|
||||
value={comments}
|
||||
onChange={(e) => setComments(e.target.value)}
|
||||
placeholder="Input comments here"
|
||||
className="w-full h-80 p-2 border-2 rounded-xl focus:border-mti-purple focus:outline-none mt-4"
|
||||
/>
|
||||
<button onClick={saveComments} className="mt-4 px-6 py-2 bg-mti-purple-dark text-white rounded-full">
|
||||
Save Comments
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</Layout>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import Layout from "@/components/High/Layout";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import { redirect, serialize } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||
import { withIronSessionSsr } from "iron-session/next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { BsChevronLeft, BsTrash } from "react-icons/bs";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import Tip from "@/components/ApprovalWorkflows/Tip";
|
||||
import WorkflowForm from "@/components/ApprovalWorkflows/WorkflowForm";
|
||||
import Layout from "@/components/High/Layout";
|
||||
import Button from "@/components/Low/Button";
|
||||
import Input from "@/components/Low/Input";
|
||||
import Select from "@/components/Low/Select";
|
||||
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
|
||||
import { EditableApprovalWorkflow } from "@/interfaces/approval.workflow";
|
||||
import { Entity } from "@/interfaces/entity";
|
||||
import { CorporateUser, TeacherUser, User } from "@/interfaces/user";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import { redirect, serialize } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { getEntities } from "@/utils/entities.be";
|
||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||
import { getEntitiesUsers } from "@/utils/users.be";
|
||||
import { LayoutGroup, motion } from "framer-motion";
|
||||
import { withIronSessionSsr } from "iron-session/next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { BsChevronLeft, BsTrash } from "react-icons/bs";
|
||||
import { MdFormatListBulletedAdd } from "react-icons/md";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
const user = await requestUser(req, res)
|
||||
@@ -51,7 +50,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers, userEntitiesCorporates }: Props) {
|
||||
const [workflows, setWorkflows] = useState<ApprovalWorkflow[]>([]);
|
||||
const [workflows, setWorkflows] = useState<EditableApprovalWorkflow[]>([]);
|
||||
const [selectedWorkflowId, setSelectedWorkflowId] = useState<string | undefined>(undefined);
|
||||
const [entityId, setEntityId] = useState<string | null | undefined>(null);
|
||||
const [entityTeachers, setEntityTeachers] = useState<TeacherUser[]>([]);
|
||||
@@ -80,7 +79,7 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
|
||||
const ENTITY_OPTIONS = userEntitiesWithLabel.map(entity => ({
|
||||
label: entity.label,
|
||||
value: entity.id,
|
||||
filter: (x: ApprovalWorkflow) => x.entityId === entity.id,
|
||||
filter: (x: EditableApprovalWorkflow) => x.entityId === entity.id,
|
||||
}));
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
@@ -90,7 +89,7 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
|
||||
|
||||
const handleAddNewWorkflow = () => {
|
||||
const newId = uuidv4();
|
||||
const newWorkflow: ApprovalWorkflow = {
|
||||
const newWorkflow: EditableApprovalWorkflow = {
|
||||
id: newId,
|
||||
name: "",
|
||||
entityId: "",
|
||||
@@ -99,15 +98,15 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
|
||||
startDate: Date.now(),
|
||||
status: "pending",
|
||||
steps: [
|
||||
{ key: Date.now(), completed: false, stepType: "form-intake", stepNumber: 1, firstStep: true, assignees: [null] },
|
||||
{ key: Date.now() + 1, completed: false, stepType: "approval-by", stepNumber: 2, finalStep: true, assignees: [null] },
|
||||
{ key: Date.now(), stepType: "form-intake", stepNumber: 1, firstStep: true, finalStep: false, assignees: [null] },
|
||||
{ key: Date.now() + 1, stepType: "approval-by", stepNumber: 2, firstStep: false, finalStep: true, assignees: [null] },
|
||||
],
|
||||
};
|
||||
setWorkflows((prev) => [...prev, newWorkflow]);
|
||||
handleSelectWorkflow(newId);
|
||||
};
|
||||
|
||||
const onWorkflowChange = (updatedWorkflow: ApprovalWorkflow) => {
|
||||
const onWorkflowChange = (updatedWorkflow: EditableApprovalWorkflow) => {
|
||||
setWorkflows(prev =>
|
||||
prev.map(wf => (wf.id === updatedWorkflow.id ? updatedWorkflow : wf))
|
||||
);
|
||||
|
||||
@@ -34,12 +34,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type))
|
||||
return redirect("/")
|
||||
|
||||
/* const entityIDS = mapBy(user.entities, "id");
|
||||
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDS);
|
||||
const allowedEntities = findAllowedEntities(user, entities, "view_approval_workflows"); */
|
||||
|
||||
|
||||
// replace later with await getApprovalWorkflow(id). Don't think a hook is needed here;
|
||||
// replace later with useApprovalWorkflows()
|
||||
const workflows = approvalWorkflowsData as ApprovalWorkflow[];
|
||||
|
||||
const allAssigneeIds: string[] = [
|
||||
@@ -47,8 +42,8 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
workflows
|
||||
.map(workflow => workflow.steps
|
||||
.map(step => step.assignees)
|
||||
.flat() as string[] // we are sure assignees coming from a db workflow are all valid strings.
|
||||
).flat() as string[]
|
||||
.flat()
|
||||
).flat()
|
||||
)
|
||||
];
|
||||
|
||||
@@ -166,10 +161,6 @@ export default function ApprovalWorkflows({ user, workflows, workflowsAssignees,
|
||||
};
|
||||
|
||||
const columns = [
|
||||
/* columnHelper.accessor("id", {
|
||||
header: "ID",
|
||||
cell: (info) => info.getValue(),
|
||||
}), */
|
||||
columnHelper.accessor("name", {
|
||||
header: "NAME",
|
||||
cell: (info) => (
|
||||
@@ -216,7 +207,7 @@ export default function ApprovalWorkflows({ user, workflows, workflowsAssignees,
|
||||
const steps = info.row.original.steps;
|
||||
const currentStep = steps.find((step) => !step.completed);
|
||||
|
||||
const assignees = currentStep?.assignees!.map((assigneeId) => {
|
||||
const assignees = currentStep?.assignees.map((assigneeId) => {
|
||||
const assignee = workflowsAssignees.find((user) => user.id === assigneeId);
|
||||
return assignee?.name || "Unknown Assignee";
|
||||
});
|
||||
@@ -245,7 +236,7 @@ export default function ApprovalWorkflows({ user, workflows, workflowsAssignees,
|
||||
return (
|
||||
<span className="font-medium">
|
||||
{currentStep
|
||||
? `Step ${currentStep.stepNumber}: ${StepTypeLabel[currentStep.stepType!]}`
|
||||
? `Step ${currentStep.stepNumber}: ${StepTypeLabel[currentStep.stepType]}`
|
||||
: "Completed"}
|
||||
</span>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user