diff --git a/src/components/pages/ApprovalSteps.tsx b/src/components/pages/ApprovalSteps.tsx index 4022e254..184b3f20 100644 --- a/src/components/pages/ApprovalSteps.tsx +++ b/src/components/pages/ApprovalSteps.tsx @@ -3,11 +3,24 @@ import Steps from '@/components/steps/Steps'; import StepItem from '@/components/steps/StepItem'; import Tooltip from '@/components/Tooltip'; -import { formatDate } from '@/lib/helper'; -import { ApprovalsLine } from '@/types/api/api-general'; +import { cn, formatDate } from '@/lib/helper'; +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 { - approvals: ApprovalsLine; + approvals: { + name?: string; + status: ApprovalStepStatus; + logs?: ApprovalStepLog[]; + }[]; } const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => { @@ -15,17 +28,23 @@ const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => { {approvals.map((approval, idx) => { const stepItemColor = - approval.status === 'approved' + approval.status === 'APPROVED' ? 'success' - : approval.status === 'rejected' + : approval.status === 'REJECTED' ? 'error' + : approval.status === 'WAITING' + ? 'warning' : undefined; const stepItemIcon = - approval.status === 'approved' + approval.status === 'APPROVED' ? 'material-symbols:check-rounded' - : approval.status === 'rejected' + : approval.status === 'REJECTED' ? 'material-symbols:close-rounded' + : approval.status === 'WAITING' + ? 'pajamas:dash-circle' + : approval.logs && approval.logs.length > 0 + ? 'material-symbols:info-outline-rounded' : 'bxs:hourglass'; return ( @@ -33,27 +52,53 @@ const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => { key={idx} color={stepItemColor} icon={ - approval.status !== 'waiting' && ( - - {formatDate(approval.date, 'YYYY-MM-DD')} - Oleh: {approval.action_by} - Catatan: {approval.notes} - - } - > - - - ) + + {approval.logs && approval.logs.length > 0 && ( +
+ {approval.logs?.map((approvalLog, logIdx) => ( +
+ {approvalLog.date && ( + + {formatDate( + approvalLog.date, + 'YYYY-MM-DD, HH:mm:ss' + )} + + )} + Oleh: {approvalLog.action_by ?? '-'} + Catatan: {approvalLog.notes ?? '-'} +
+ ))} +
+ )} + + } + > + +
} > - {approval.role} + {approval.name} ); })} @@ -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; diff --git a/src/components/steps/StepItem.tsx b/src/components/steps/StepItem.tsx index 85ec4f3e..26ce1ce8 100644 --- a/src/components/steps/StepItem.tsx +++ b/src/components/steps/StepItem.tsx @@ -24,7 +24,14 @@ const StepItem = ({ children, icon, className, color }: StepItemProps) => { return (
  • - {icon} + + {icon} +
    {children}
  • diff --git a/src/config/approval-line.ts b/src/config/approval-line.ts new file mode 100644 index 00000000..0a02d1f1 --- /dev/null +++ b/src/config/approval-line.ts @@ -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; diff --git a/src/lib/helper.ts b/src/lib/helper.ts index e3bfda65..aeda46f6 100644 --- a/src/lib/helper.ts +++ b/src/lib/helper.ts @@ -19,7 +19,7 @@ export const cn = (...inputs: ClassValue[]) => { export const formatNumber = ( value: number | bigint | Intl.StringNumericLiteral, - locale = 'en-US', + locale = 'id-ID', minimumFractionDigits = 0, maximumFractionDigits = 2 ) => { @@ -29,11 +29,10 @@ export const formatNumber = ( }).format(value); }; - export const formatCurrency = ( value: number | bigint | Intl.StringNumericLiteral, - currency = 'USD', - locale = 'en-US', + currency = 'IDR', + locale = 'id-ID', minimumFractionDigits = 0, maximumFractionDigits = 2 ) => { diff --git a/src/styles/daisyui.css b/src/styles/daisyui.css index dadaa2fa..fc87399f 100644 --- a/src/styles/daisyui.css +++ b/src/styles/daisyui.css @@ -9,6 +9,11 @@ --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) { vertical-align: top; } diff --git a/src/types/api/api-general.d.ts b/src/types/api/api-general.d.ts index a42eaa3f..af9144e2 100644 --- a/src/types/api/api-general.d.ts +++ b/src/types/api/api-general.d.ts @@ -97,21 +97,21 @@ export type flags = | 'FINISHER' | 'OVK'; -export type ApprovalsLine = { - action_by?: string; - date?: string; - notes?: string; - role?: string; - status: 'approved' | 'rejected' | 'waiting'; -}[]; - export type BaseApproval = { step_number: number; step_name: string; action: string; - notes: string | null; + notes?: string | null; action_by: CreatedUser; action_at: string; }; -export type ApproveAction = 'APPROVED' | 'REJECTED'; +export type BaseGroupedApproval = { + step_number: number; + step_name: string; + approvals: BaseApproval[]; +}; + +export type Approvals = BaseApiResponse; + +export type GroupedApprovals = BaseApiResponse; diff --git a/src/types/config/constant.d.ts b/src/types/config/constant.d.ts new file mode 100644 index 00000000..3e4371be --- /dev/null +++ b/src/types/config/constant.d.ts @@ -0,0 +1,4 @@ +export type ApprovalLine = { + step_number: number; + step_name: string; +}[];