Files
lti-web-client/src/components/helper/ApprovalStepsV2.tsx
T
2026-02-06 09:43:34 +07:00

206 lines
6.4 KiB
TypeScript

'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 {
title?: string;
approvals?: BaseApproval[];
steps: {
step_number: number;
step_name: string;
}[];
maxVisibleSteps?: number;
className?: {
wrapper?: string;
stepsWrapper?: string;
stepsContainer?: string;
};
}
const ApprovalStepsV2 = ({
title = 'Progress Details',
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 (
<div
className={cn(
'w-full p-4 flex flex-col border-b border-base-content/10',
className?.wrapper
)}
>
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
{title}
</h4>
<div
className={cn(
'mt-6 mb-8 flex flex-col gap-10',
className?.stepsWrapper
)}
>
{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 (
<div key={idx} className='w-full flex flex-row items-stretch gap-3'>
<div className='w-fit self-stretch relative'>
<div className='w-fit h-fit flex flex-col items-start'>
<Icon
icon={approvalIcon}
width={24}
height={24}
className={cn({
'text-warning':
isApprovalActionCreated || isApprovalActionUpdated,
'text-error': isApprovalActionRejected,
'text-success': isApprovalActionApproved,
'text-base-content/20': !approval.isActive,
})}
/>
{idx < formattedApprovals.length - 1 && (
<div className='absolute top-6 left-1/2 -translate-x-1/2 w-0 min-h-full h-[calc(100%)] mx-auto my-2 border border-dashed border-base-content/10' />
)}
</div>
</div>
<div
className={cn('w-full flex flex-col gap-1 text-base-content', {
'text-base-content/20': !approval.isActive,
})}
>
<div className='flex flex-col'>
<span className='text-xs'>{approval.step_name}</span>
<span className='text-sm font-semibold'>
{(isApprovalActionCreated || isApprovalActionUpdated) &&
'Diajukan oleh '}
{isApprovalActionRejected && 'Ditolak oleh '}
{isApprovalActionApproved && 'Disetujui oleh '}
{approval.isActive ? approval.action_by.name : '...'}
</span>
</div>
{approval.isActive && (
<p className='w-full max-w-60 p-3 bg-base-content/5 rounded-xl text-xs text-base-content/50'>
Created at :{' '}
{formatDate(approval.action_at, 'DD-MM-YYYY, HH:mm')}
<br />
Notes : {approval.notes ?? '-'}
</p>
)}
</div>
</div>
);
})}
</div>
{formattedApprovals.length > maxVisibleSteps && (
<Button
variant='outline'
color='none'
onClick={seeMoreClickHandler}
className={cn(
'px-3 py-2 gap-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-lg transition-all'
)}
>
<Icon
icon='heroicons-outline:chevron-double-down'
width={20}
height={20}
className={cn('transition-all duration-300', {
'-rotate-180': isSeeAll,
})}
/>
See {isSeeAll ? 'Less' : 'More'}
</Button>
)}
</div>
);
};
export default ApprovalStepsV2;