small fixes and animate side panel content

This commit is contained in:
Joao Correia
2025-01-25 15:47:33 +00:00
parent 2c0153e055
commit ac072b0a5a
3 changed files with 115 additions and 96 deletions

View File

@@ -17,7 +17,7 @@ export default function UserWithProfilePic({ prefix, name, profileImage, textSiz
alt={name} alt={name}
width={24} width={24}
height={24} height={24}
className="w-6 h-6 rounded-full" className="rounded-full"
/> />
</div> </div>
); );

View File

@@ -23,6 +23,7 @@ import { ToastContainer } from "react-toastify";
import approvalWorkflowsData from '../../demo/approval_workflows.json'; // to test locally import approvalWorkflowsData from '../../demo/approval_workflows.json'; // to test locally
import Tip from "@/components/ApprovalWorkflows/Tip"; import Tip from "@/components/ApprovalWorkflows/Tip";
import { AnimatePresence, LayoutGroup, motion } from "framer-motion";
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);
@@ -149,106 +150,118 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques
))} ))}
</section> </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'}`}> {/* Side panel */}
{isPanelOpen && ( <AnimatePresence mode="wait">
<div className="relative inset-y-0 right-0 h-full p-6"> <LayoutGroup key="sidePanel">
<div className="flex flex-row gap-2"> <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'}`}>
<p className="text-2xl font-medium text-left align-middle">Step {selectedStepIndex + 1}</p> {isPanelOpen && selectedStep && (
<div className="ml-auto flex flex-row"> <motion.div
<button className="p-6"
className="min-w-fit max-h-fit text-lg font-medium flex items-center gap-2 text-left" key={selectedStep.stepNumber}
onClick={() => setIsPanelOpen(false)} initial={{ opacity: 0, x: 30 }}
> animate={{ opacity: 1, x: 0 }}
Collapse exit={{ opacity: 0, x: 30 }}
<MdOutlineDoubleArrow size={20} /> transition={{ duration: 0.2 }}
</button> >
</div> <div className="flex flex-row gap-2">
</div> <p className="text-2xl font-medium text-left align-middle">Step {selectedStepIndex + 1}</p>
<div className="ml-auto flex flex-row">
<hr className="my-4 h-[4px] bg-mti-purple-ultralight rounded-full w-full" /> <button
className="min-w-fit max-h-fit text-lg font-medium flex items-center gap-2 text-left"
<div> onClick={() => setIsPanelOpen(false)}
<div className="my-8 flex flex-row gap-4 items-center text-lg font-medium"> >
{selectedStep.stepType === "approval-by" ? ( Collapse
<> <MdOutlineDoubleArrow size={20} />
<RiThumbUpLine size={30} /> </button>
Approval Step
</>
) : (
<>
<FaWpforms size={30} />
Form Intake Step
</>
)
}
</div>
{selectedStep.completed ? (
<div className={"text-base font-medium text-gray-500 flex flex-col gap-6"}>
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 ")}
<div className="flex flex-row gap-1 text-sm">
<p className="text-base">Approved by:</p>
{(() => {
const assignee = workflowAssignees.find(
(assignee) => assignee.id === selectedStep.completedBy
);
return assignee ? (
<UserWithProfilePic
textSize="text-base"
prefix={getUserTypeLabelShort(assignee.type)}
name={assignee.name}
profileImage={assignee.profilePicture}
/>
) : (
"Unknown"
);
})()}
</div>
<p className="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
textSize="text-sm"
prefix={getUserTypeLabelShort(user.type)}
name={user.name}
profileImage={user.profilePicture}
/>
</span>
))}
</div> </div>
</div> </div>
)}
<hr className="my-4 h-[4px] bg-mti-purple-ultralight rounded-full w-full" /> <hr className="my-4 h-[4px] bg-mti-purple-ultralight rounded-full w-full" />
<textarea <div>
value={comments} <div className="my-8 flex flex-row gap-4 items-center text-lg font-medium">
onChange={(e) => setComments(e.target.value)} {selectedStep.stepType === "approval-by" ? (
placeholder="Input comments here" <>
className="w-full h-80 p-2 border-2 rounded-xl shadow-lg focus:border-mti-purple focus:outline-none mt-4" <RiThumbUpLine size={30} />
/> Approval Step
<button onClick={saveComments} className="mt-4 px-6 py-2 bg-mti-purple-dark text-white rounded-full"> </>
Save Comments ) : (
</button> <>
<FaWpforms size={30} />
Form Intake Step
</>
)
}
</div>
</div> {selectedStep.completed ? (
</div> <div className={"text-base font-medium text-gray-500 flex flex-col gap-6"}>
)} Approved on {new Date(selectedStep.completedDate!).toLocaleString("en-CA", {
</section> year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}).replace(", ", " at ")}
<div className="flex flex-row gap-1 text-sm">
<p className="text-base">Approved by:</p>
{(() => {
const assignee = workflowAssignees.find(
(assignee) => assignee.id === selectedStep.completedBy
);
return assignee ? (
<UserWithProfilePic
textSize="text-base"
prefix={getUserTypeLabelShort(assignee.type)}
name={assignee.name}
profileImage={assignee.profilePicture}
/>
) : (
"Unknown"
);
})()}
</div>
<p className="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
textSize="text-sm"
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 shadow-lg 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>
</motion.div>
)}
</section>
</LayoutGroup>
</AnimatePresence>
</Layout> </Layout>
)} )}
</> </>

View File

@@ -55,6 +55,7 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
const [entityId, setEntityId] = useState<string | null | undefined>(null); const [entityId, setEntityId] = useState<string | null | undefined>(null);
const [entityTeachers, setEntityTeachers] = useState<TeacherUser[]>([]); const [entityTeachers, setEntityTeachers] = useState<TeacherUser[]>([]);
const [entityCorporates, setEntityCorporates] = useState<CorporateUser[]>([]); const [entityCorporates, setEntityCorporates] = useState<CorporateUser[]>([]);
const [isAdding, setIsAdding] = useState(false); // used to temporary timeout new workflow button. With animations, clicking too fast might cause state inconsistencies between renders.
useEffect(() => { useEffect(() => {
if (entityId) { if (entityId) {
@@ -88,6 +89,9 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
}; };
const handleAddNewWorkflow = () => { const handleAddNewWorkflow = () => {
if (isAdding) return;
setIsAdding(true);
const newId = uuidv4(); const newId = uuidv4();
const newWorkflow: EditableApprovalWorkflow = { const newWorkflow: EditableApprovalWorkflow = {
id: newId, id: newId,
@@ -104,6 +108,8 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesTeachers
}; };
setWorkflows((prev) => [...prev, newWorkflow]); setWorkflows((prev) => [...prev, newWorkflow]);
handleSelectWorkflow(newId); handleSelectWorkflow(newId);
setTimeout(() => setIsAdding(false), 300);
}; };
const onWorkflowChange = (updatedWorkflow: EditableApprovalWorkflow) => { const onWorkflowChange = (updatedWorkflow: EditableApprovalWorkflow) => {