Merge branch 'development' of https://gitlab.com/mbugroup/lti-web-client into dev/randy

This commit is contained in:
randy-ar
2025-10-30 21:24:20 +07:00
7 changed files with 185 additions and 41 deletions
+143 -26
View File
@@ -3,11 +3,24 @@ import Steps from '@/components/steps/Steps';
import StepItem from '@/components/steps/StepItem'; import StepItem from '@/components/steps/StepItem';
import Tooltip from '@/components/Tooltip'; import Tooltip from '@/components/Tooltip';
import { formatDate } from '@/lib/helper'; import { cn, formatDate } from '@/lib/helper';
import { ApprovalsLine } from '@/types/api/api-general'; import { BaseApproval, BaseGroupedApproval } from '@/types/api/api-general';
import { ApprovalLine } from '@/types/config/constant';
export type ApprovalStepStatus = 'APPROVED' | 'REJECTED' | 'WAITING' | 'IDLE';
export type ApprovalStepLog = {
action_by?: string;
date?: string;
notes?: string | null;
};
interface ApprovalStepsProps { interface ApprovalStepsProps {
approvals: ApprovalsLine; approvals: {
name?: string;
status: ApprovalStepStatus;
logs?: ApprovalStepLog[];
}[];
} }
const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => { const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => {
@@ -15,17 +28,23 @@ const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => {
<Steps direction='vertical' className='w-full md:steps-horizontal'> <Steps direction='vertical' className='w-full md:steps-horizontal'>
{approvals.map((approval, idx) => { {approvals.map((approval, idx) => {
const stepItemColor = const stepItemColor =
approval.status === 'approved' approval.status === 'APPROVED'
? 'success' ? 'success'
: approval.status === 'rejected' : approval.status === 'REJECTED'
? 'error' ? 'error'
: approval.status === 'WAITING'
? 'warning'
: undefined; : undefined;
const stepItemIcon = const stepItemIcon =
approval.status === 'approved' approval.status === 'APPROVED'
? 'material-symbols:check-rounded' ? 'material-symbols:check-rounded'
: approval.status === 'rejected' : approval.status === 'REJECTED'
? 'material-symbols:close-rounded' ? 'material-symbols:close-rounded'
: approval.status === 'WAITING'
? 'pajamas:dash-circle'
: approval.logs && approval.logs.length > 0
? 'material-symbols:info-outline-rounded'
: 'bxs:hourglass'; : 'bxs:hourglass';
return ( return (
@@ -33,27 +52,53 @@ const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => {
key={idx} key={idx}
color={stepItemColor} color={stepItemColor}
icon={ icon={
approval.status !== 'waiting' && ( <Tooltip
<Tooltip color={stepItemColor}
color={stepItemColor} position='right'
position='right' className={{
className={{ wrapper: 'md:tooltip-bottom',
wrapper: 'md:tooltip-bottom', }}
}} content={
content={ <>
<div className='flex flex-col text-base'> {approval.logs && approval.logs.length > 0 && (
<span>{formatDate(approval.date, 'YYYY-MM-DD')}</span> <div className='flex flex-col gap-2'>
<span>Oleh: {approval.action_by}</span> {approval.logs?.map((approvalLog, logIdx) => (
<span>Catatan: {approval.notes}</span> <div
</div> key={logIdx}
} className='flex flex-col text-base text-start'
> >
<Icon icon={stepItemIcon} width={24} height={24} /> {approvalLog.date && (
</Tooltip> <span>
) {formatDate(
approvalLog.date,
'YYYY-MM-DD, HH:mm:ss'
)}
</span>
)}
<span>Oleh: {approvalLog.action_by ?? '-'}</span>
<span>Catatan: {approvalLog.notes ?? '-'}</span>
</div>
))}
</div>
)}
</>
}
>
<Icon
icon={stepItemIcon}
width={24}
height={24}
className={cn({
invisible:
approval.status === 'IDLE' &&
(!approval.logs ||
(approval.logs && approval.logs.length === 0)),
})}
/>
</Tooltip>
} }
> >
{approval.role} {approval.name}
</StepItem> </StepItem>
); );
})} })}
@@ -61,4 +106,76 @@ const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => {
); );
}; };
export const formatGroupedApprovalsToApprovalSteps = (
approvalLine: ApprovalLine,
groupedApprovals: BaseGroupedApproval[],
latestApproval: BaseApproval
): ApprovalStepsProps['approvals'] => {
const formattedApprovalSteps: ApprovalStepsProps['approvals'] =
approvalLine.map((approvalLineItem) => {
const approvalGroup = groupedApprovals.find(
(approvalGroupItem) =>
approvalGroupItem.step_number === approvalLineItem.step_number
);
const currentStepNumber = approvalLineItem.step_number;
const lastStepNumber =
groupedApprovals[groupedApprovals.length - 1].step_number;
if (!approvalGroup && currentStepNumber <= lastStepNumber) {
throw new Error(
`Approval dengan ${approvalLineItem.step_name} tidak ditemukan!`
);
}
if (!approvalGroup) {
const isWaiting = currentStepNumber === latestApproval.step_number + 1;
return {
name: approvalLineItem.step_name,
status: isWaiting ? 'WAITING' : 'IDLE',
};
}
let approvalStatus: ApprovalStepStatus;
if (approvalGroup.step_number <= latestApproval.step_number) {
switch (approvalGroup.approvals[0].action) {
case 'CREATED':
case 'APPROVED':
approvalStatus = 'APPROVED';
break;
case 'REJECTED':
approvalStatus = 'REJECTED';
break;
default:
approvalStatus = 'IDLE';
break;
}
} else if (approvalGroup.step_number === latestApproval.step_number + 1) {
approvalStatus = 'WAITING';
} else {
approvalStatus = 'IDLE';
}
const approvalLogs: ApprovalStepLog[] = approvalGroup.approvals.map(
(approval) => ({
action_by: approval.action_by.name,
date: approval.action_at,
notes: approval.notes,
})
);
return {
name: approvalGroup.step_name,
status: approvalStatus,
logs: approvalLogs,
};
});
return formattedApprovalSteps;
};
export default ApprovalSteps; export default ApprovalSteps;
+8 -1
View File
@@ -24,7 +24,14 @@ const StepItem = ({ children, icon, className, color }: StepItemProps) => {
return ( return (
<li className={cn(stepItemBaseClassName, className)}> <li className={cn(stepItemBaseClassName, className)}>
<span className='step-icon'>{icon}</span> <span
className={cn('step-icon', {
'transition-shadow shadow-[0_0_10px_2px_var(--color-warning)] hover:shadow-[0_0_15px_5px_var(--color-warning)]':
color === 'warning',
})}
>
{icon}
</span>
<div>{children}</div> <div>{children}</div>
</li> </li>
+12
View File
@@ -0,0 +1,12 @@
import { ApprovalLine } from '@/types/config/constant';
export const PROJECT_FLOCK_APPROVAL_LINE: ApprovalLine = [
{
step_number: 1,
step_name: 'Pengajuan',
},
{
step_number: 2,
step_name: 'Aktif',
},
] as const;
+3 -4
View File
@@ -19,7 +19,7 @@ export const cn = (...inputs: ClassValue[]) => {
export const formatNumber = ( export const formatNumber = (
value: number | bigint | Intl.StringNumericLiteral, value: number | bigint | Intl.StringNumericLiteral,
locale = 'en-US', locale = 'id-ID',
minimumFractionDigits = 0, minimumFractionDigits = 0,
maximumFractionDigits = 2 maximumFractionDigits = 2
) => { ) => {
@@ -29,11 +29,10 @@ export const formatNumber = (
}).format(value); }).format(value);
}; };
export const formatCurrency = ( export const formatCurrency = (
value: number | bigint | Intl.StringNumericLiteral, value: number | bigint | Intl.StringNumericLiteral,
currency = 'USD', currency = 'IDR',
locale = 'en-US', locale = 'id-ID',
minimumFractionDigits = 0, minimumFractionDigits = 0,
maximumFractionDigits = 2 maximumFractionDigits = 2
) => { ) => {
+5
View File
@@ -9,6 +9,11 @@
--step-fg: var(--color-error-content); --step-fg: var(--color-error-content);
} }
.step.step-warning::before {
--step-bg: var(--color-warning);
--step-fg: var(--color-warning-content);
}
.table :where(th, td) { .table :where(th, td) {
vertical-align: top; vertical-align: top;
} }
+10 -10
View File
@@ -97,21 +97,21 @@ export type flags =
| 'FINISHER' | 'FINISHER'
| 'OVK'; | 'OVK';
export type ApprovalsLine = {
action_by?: string;
date?: string;
notes?: string;
role?: string;
status: 'approved' | 'rejected' | 'waiting';
}[];
export type BaseApproval = { export type BaseApproval = {
step_number: number; step_number: number;
step_name: string; step_name: string;
action: string; action: string;
notes: string | null; notes?: string | null;
action_by: CreatedUser; action_by: CreatedUser;
action_at: string; action_at: string;
}; };
export type ApproveAction = 'APPROVED' | 'REJECTED'; export type BaseGroupedApproval = {
step_number: number;
step_name: string;
approvals: BaseApproval[];
};
export type Approvals = BaseApiResponse<BaseApproval>;
export type GroupedApprovals = BaseApiResponse<BaseGroupedApproval[]>;
+4
View File
@@ -0,0 +1,4 @@
export type ApprovalLine = {
step_number: number;
step_name: string;
}[];