From 82c1645d923c15e45272912ff45c96e99ea8668a Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 30 Oct 2025 10:45:41 +0700 Subject: [PATCH 1/8] chore(FE-91): rework ApprovalSteps and create helper function for formatting approval workflow --- src/components/pages/ApprovalSteps.tsx | 169 +++++++++++++++++++++---- 1 file changed, 143 insertions(+), 26 deletions(-) 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; From b720c1411b15d9ca4ff7e5b0ce7615f2d8a9c0d3 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 30 Oct 2025 10:47:29 +0700 Subject: [PATCH 2/8] chore(FE-91): make warning step icon glow --- src/components/steps/StepItem.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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}
  • From bce58c585d89ee15a2ba338a8294e6a594c8c43c Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 30 Oct 2025 10:47:51 +0700 Subject: [PATCH 3/8] feat(FE-91): create approval-line config file --- src/config/approval-line.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/config/approval-line.ts 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; From dd3a0079db959d099951939ba5ac00bb2f27453e Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 30 Oct 2025 10:48:18 +0700 Subject: [PATCH 4/8] chore(FE-91): set formatNumber locale to id-ID as default --- src/lib/helper.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/helper.ts b/src/lib/helper.ts index e3bfda65..002be5a3 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,7 +29,6 @@ export const formatNumber = ( }).format(value); }; - export const formatCurrency = ( value: number | bigint | Intl.StringNumericLiteral, currency = 'USD', From 5c3b1c489f9163fe38276f30621786b72711e256 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 30 Oct 2025 10:48:37 +0700 Subject: [PATCH 5/8] chore(FE-91): set color for step-warning --- src/styles/daisyui.css | 5 +++++ 1 file changed, 5 insertions(+) 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; } From c9c343b840093df80e96d9f2f63ced4231956c97 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 30 Oct 2025 10:49:36 +0700 Subject: [PATCH 6/8] chore(FE-91): create BaseGroupedApproval, Approvals, and GroupedApprovals api types --- src/types/api/api-general.d.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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; From f7eb89c11337eca261eded16e40d83d642dd887a Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 30 Oct 2025 10:49:50 +0700 Subject: [PATCH 7/8] feat(FE-91): create constant type file --- src/types/config/constant.d.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/types/config/constant.d.ts 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; +}[]; From 79cfcad026f1c0fa5540c95340244b51f7d678d7 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 30 Oct 2025 11:03:22 +0700 Subject: [PATCH 8/8] chore(FE-91): set formatCurrency default currency to indonesian currency --- src/lib/helper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/helper.ts b/src/lib/helper.ts index 002be5a3..aeda46f6 100644 --- a/src/lib/helper.ts +++ b/src/lib/helper.ts @@ -31,8 +31,8 @@ export const formatNumber = ( export const formatCurrency = ( value: number | bigint | Intl.StringNumericLiteral, - currency = 'USD', - locale = 'en-US', + currency = 'IDR', + locale = 'id-ID', minimumFractionDigits = 0, maximumFractionDigits = 2 ) => {