import { Icon } from '@iconify/react'; import Steps from '@/components/steps/Steps'; import StepItem from '@/components/steps/StepItem'; import Tooltip from '@/components/Tooltip'; import { cn, formatDate } from '@/lib/helper'; import { BaseApiResponse, BaseApproval, BaseGroupedApproval, } from '@/types/api/api-general'; import { ApprovalLine } from '@/types/config/constant'; import useSWR from 'swr'; import { httpClientFetcher } from '@/services/http/client'; import { isResponseSuccess } from '@/lib/api-helper'; import { useCallback, useMemo } from 'react'; export type ApprovalStepStatus = 'APPROVED' | 'REJECTED' | 'WAITING' | 'IDLE'; export type ApprovalStepLog = { action: string; action_by?: string; date?: string; notes?: string | null; }; interface ApprovalStepsProps { approvals: { name?: string; status: ApprovalStepStatus; logs?: ApprovalStepLog[]; }[]; } const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => { return ( {approvals.map((approval, idx) => { const stepItemColor = approval.status === 'APPROVED' ? 'success' : approval.status === 'REJECTED' ? 'error' : approval.status === 'WAITING' ? 'warning' : undefined; const stepItemIcon = approval.status === 'APPROVED' ? 'material-symbols:check-rounded' : 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 ( {approval.logs && approval.logs.length > 0 && (
{approval.logs?.map((approvalLog, logIdx) => { const action = approvalLog.action === 'CREATED' ? 'Dibuat' : approvalLog.action === 'UPDATED' ? 'Diperbarui' : approvalLog.action === 'APPROVED' ? 'Disetujui' : approvalLog.action === 'REJECTED' ? 'Ditolak' : '-'; return (
{approvalLog.date && ( {formatDate( approvalLog.date, 'YYYY-MM-DD, HH:mm:ss' )} )} Aksi: {action} Oleh: {approvalLog.action_by ?? '-'} Catatan: {approvalLog.notes ?? '-'}
); })}
)} } > } > {approval.name}
); })}
); }; export const formatGroupedApprovalsToApprovalSteps = ( approvalLine: ApprovalLine, groupedApprovals: BaseGroupedApproval[] | undefined, latestApproval: BaseApproval | undefined ): 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; const isLatestApprovalRejected = latestApproval?.action === 'REJECTED'; // Only throw error if we have a valid lastStepNumber to compare against if ( !approvalGroup && lastStepNumber !== undefined && currentStepNumber <= lastStepNumber ) { // throw new Error( // `Approval dengan ${approvalLineItem.step_name} tidak ditemukan!` // ); } if (!approvalGroup) { // Check if this step is waiting (only if we have latestApproval) const isWaiting = latestApproval?.step_number !== undefined && currentStepNumber === latestApproval.step_number + 1; // Check if previous approval was rejected const isPreviousApprovalRejected = groupedApprovals && groupedApprovals.length > 0 && groupedApprovals[groupedApprovals.length - 1]?.approvals?.[0] ?.action === 'REJECTED'; return { name: approvalLineItem.step_name, status: isPreviousApprovalRejected ? 'IDLE' : isWaiting ? 'WAITING' : 'IDLE', }; } let approvalStatus: ApprovalStepStatus = 'IDLE'; // Only compare if latestApproval and its step_number exist if ( latestApproval?.step_number !== undefined && approvalGroup.step_number <= latestApproval.step_number ) { if (approvalGroup.approvals) { switch (approvalGroup?.approvals[0]?.action) { case 'CREATED': case 'UPDATED': case 'APPROVED': approvalStatus = 'APPROVED'; break; case 'REJECTED': approvalStatus = 'REJECTED'; break; default: approvalStatus = 'IDLE'; break; } } } else if ( latestApproval?.step_number !== undefined && approvalGroup.step_number === latestApproval.step_number + 1 && !isLatestApprovalRejected ) { approvalStatus = 'WAITING'; } else { approvalStatus = 'IDLE'; } const approvalLogs: ApprovalStepLog[] = approvalGroup.approvals ? approvalGroup.approvals.map((approval) => ({ action_by: approval.action_by.name, date: approval.action_at, notes: approval.notes, action: approval.action, })) : []; return { name: approvalGroup.step_name, status: approvalStatus, logs: approvalLogs, }; }); return formattedApprovalSteps; }; export default ApprovalSteps; /** * Mengubah array BaseApproval (datar) menjadi BaseGroupedApproval (berkelompok). */ const groupApprovalsByStep = ( approvals: BaseApproval[] ): BaseGroupedApproval[] => { const groups: Record = {}; for (const approval of approvals) { if (!groups[approval.step_number]) { groups[approval.step_number] = { step_number: approval.step_number, step_name: approval.step_name, approvals: [], }; } groups[approval.step_number].approvals.push(approval); } return Object.values(groups); }; /** * Mengubah array BaseGroupedApproval (berkelompok) kembali menjadi BaseApproval[] (datar). */ const flattenGroupedApprovals = ( groupedApprovals: BaseGroupedApproval[] ): BaseApproval[] => { return groupedApprovals.flatMap((group) => group.approvals); }; /** * Type guard untuk memeriksa apakah data adalah BaseGroupedApproval[]. */ const isGroupedApprovalData = ( data: BaseApproval[] | BaseGroupedApproval[] ): data is BaseGroupedApproval[] => { if (!data || data.length === 0) { return true; } const firstElement = data[0]; return ( typeof firstElement === 'object' && firstElement !== null && 'approvals' in firstElement && Array.isArray(firstElement.approvals) ); }; const useApprovalSteps = ({ latestApproval, approvalLines, moduleName, moduleId, params, }: { latestApproval: BaseApproval | undefined; approvalLines: ApprovalLine; moduleName: string; moduleId: string; params?: { page?: number; limit?: number | string; search?: string; group_step_number?: boolean; order_by_date?: 'ASC' | 'DESC'; }; }) => { // Membuat URL Parameters const paramString = new URLSearchParams({ page: params?.page?.toString() || '', limit: params?.limit?.toString() || '', search: params?.search || '', group_step_number: params?.group_step_number?.toString() || '', order_by_date: params?.order_by_date || '', }).toString(); // fetching data approvals const SWR_KEY_APPROVALS = moduleName && moduleId ? `/approvals?module_name=${moduleName}&module_id=${moduleId}${ params ? `&${paramString}` : '' }` : null; const { data: approvalData, isLoading: approvalIsLoading, mutate: mutateApprovals, } = useSWR(SWR_KEY_APPROVALS, async (url) => { return await httpClientFetcher< BaseApiResponse >(url); }); // Fungsi Refresh const refresh = useCallback(async () => { await mutateApprovals(); }, [mutateApprovals]); const { groupedApprovals } = useMemo(() => { const rawData = isResponseSuccess(approvalData) ? approvalData.data : undefined; let processedGroupedApprovals: BaseGroupedApproval[] = []; if (rawData) { if (isGroupedApprovalData(rawData)) { processedGroupedApprovals = rawData; } else { processedGroupedApprovals = groupApprovalsByStep( rawData as BaseApproval[] ); } } return { groupedApprovals: processedGroupedApprovals, }; }, [approvalData]); const isLoading = approvalIsLoading; // Formatting Akhir const approvals = useMemo(() => { if (isLoading || !approvalLines.length) { return []; } // Try to derive latestApproval from groupedApprovals if not provided let effectiveLatestApproval = latestApproval; if (!effectiveLatestApproval && groupedApprovals.length > 0) { // Get all approvals from grouped data const allApprovals = groupedApprovals.flatMap((group) => group.approvals); if (allApprovals.length > 0) { // Use the most recent approval (last in array) effectiveLatestApproval = allApprovals[allApprovals.length - 1]; } } // If still no latestApproval, return empty if (!effectiveLatestApproval) { return []; } try { return formatGroupedApprovalsToApprovalSteps( approvalLines, groupedApprovals, effectiveLatestApproval ); } catch (error) { console.warn('Gagal memformat approval steps:', error); return []; } }, [isLoading, approvalLines, groupedApprovals, latestApproval]); // Raw Data Approvals const rawDataApprovals = useMemo(() => { const rawData = isResponseSuccess(approvalData) ? approvalData.data : undefined; if (!rawData) { return undefined; } const isDataCurrentlyGrouped = isGroupedApprovalData(rawData); const wantsGrouped = params?.group_step_number !== false; if (wantsGrouped) { if (isDataCurrentlyGrouped) { return rawData as BaseGroupedApproval[]; } else { return groupApprovalsByStep(rawData as BaseApproval[]); } } else { if (isDataCurrentlyGrouped) { return flattenGroupedApprovals(rawData as BaseGroupedApproval[]); } else { return rawData as BaseApproval[]; } } }, [approvalData, params?.group_step_number]); // Return Hook return { approvals, isLoading, rawDataApprovals: rawDataApprovals, refresh, }; }; export { useApprovalSteps };