From aa39478318eb54c2ba711bf1e51a60a738ab3d68 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Tue, 27 Jan 2026 16:56:57 +0700 Subject: [PATCH] feat: create ApprovalStepsV2 component --- src/components/helper/ApprovalStepsV2.tsx | 201 ++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 src/components/helper/ApprovalStepsV2.tsx diff --git a/src/components/helper/ApprovalStepsV2.tsx b/src/components/helper/ApprovalStepsV2.tsx new file mode 100644 index 00000000..6b731a72 --- /dev/null +++ b/src/components/helper/ApprovalStepsV2.tsx @@ -0,0 +1,201 @@ +'use client'; + +import { useEffect, useMemo, useState } from 'react'; + +import { Icon } from '@iconify/react'; +import { BaseApproval } from '@/types/api/api-general'; +import Button from '@/components/Button'; + +import { cn, formatDate } from '@/lib/helper'; + +interface ApprovalStepsV2Props { + approvals?: BaseApproval[]; + steps: { + step_number: number; + step_name: string; + }[]; + maxVisibleSteps?: number; + className?: { + wrapper?: string; + stepsWrapper?: string; + stepsContainer?: string; + }; +} + +const ApprovalStepsV2 = ({ + approvals, + steps, + maxVisibleSteps = 2, + className, +}: ApprovalStepsV2Props) => { + const [isSeeAll, setIsSeeAll] = useState(false); + const [formattedApprovals, setFormattedApprovals] = useState< + (BaseApproval & { isActive: boolean })[] + >([]); + + const latestApprovalStepNumber = + approvals?.[approvals.length - 1].step_number ?? 0; + + const lastStepNumber = steps[steps.length - 1].step_number; + + const isLatestApprovalStepNumberLessThanLastStepNumber = + latestApprovalStepNumber < lastStepNumber; + + const slicedFormattedApprovals = useMemo(() => { + return formattedApprovals.slice(0, isSeeAll ? undefined : maxVisibleSteps); + }, [formattedApprovals, isSeeAll]); + + const seeMoreClickHandler = () => { + setIsSeeAll((prevVal) => !prevVal); + }; + + useEffect(() => { + if (approvals) { + const tempFormattedApprovals: (BaseApproval & { isActive: boolean })[] = + []; + + approvals.forEach((approval) => { + tempFormattedApprovals.push({ + ...approval, + isActive: true, + }); + }); + + if (isLatestApprovalStepNumberLessThanLastStepNumber) { + const latestApprovalStepNumberIndexInSteps = steps.findIndex( + (step) => step.step_number === latestApprovalStepNumber + ); + + const slicedSteps = steps.slice( + latestApprovalStepNumberIndexInSteps + 1 + ); + + slicedSteps.forEach((step) => { + tempFormattedApprovals.push({ + action: 'APPROVED', + action_at: new Date().toISOString(), + action_by: { + id: 0, + id_user: 0, + email: '', + name: '', + }, + step_name: step.step_name, + step_number: step.step_number, + isActive: false, + }); + }); + } + + setFormattedApprovals(tempFormattedApprovals); + } + }, [approvals]); + + return ( +
+

+ Progress Details +

+ +
+ {slicedFormattedApprovals.map((approval, idx) => { + const isApprovalActionCreated = approval.action === 'CREATED'; + const isApprovalActionUpdated = approval.action === 'UPDATED'; + const isApprovalActionRejected = approval.action === 'REJECTED'; + const isApprovalActionApproved = approval.action === 'APPROVED'; + + const approvalIcon = + isApprovalActionCreated || isApprovalActionUpdated + ? 'heroicons:clock-solid' + : isApprovalActionRejected + ? 'heroicons:x-circle-solid' + : isApprovalActionApproved + ? 'heroicons:check-badge-solid' + : 'heroicons:check-badge-solid'; + + return ( +
+
+
+ + + {idx < formattedApprovals.length - 1 && ( +
+ )} +
+
+ +
+
+ {approval.step_name} + + {(isApprovalActionCreated || isApprovalActionUpdated) && + 'Diajukan oleh '} + {isApprovalActionRejected && 'Ditolak oleh '} + {isApprovalActionApproved && 'Disetujui oleh '} + {approval.isActive ? approval.action_by.name : '...'} + +
+ + {approval.isActive && ( +

+ Created at :{' '} + {formatDate(approval.action_at, 'DD-MM-YYYY, HH:mm')} +
+ Notes : {approval.notes ?? '-'} +

+ )} +
+
+ ); + })} +
+ + +
+ ); +}; + +export default ApprovalStepsV2;