refactor(FE-238-239-240): implement approval workflow chickin & project flock, membuat custom hook useApprovals, dan handling error format approvals

This commit is contained in:
randy-ar
2025-11-12 15:24:44 +07:00
parent 5dccaf40cb
commit b2f4317c08
13 changed files with 258 additions and 200 deletions
+143 -22
View File
@@ -4,8 +4,17 @@ import StepItem from '@/components/steps/StepItem';
import Tooltip from '@/components/Tooltip';
import { cn, formatDate } from '@/lib/helper';
import { BaseApproval, BaseGroupedApproval } from '@/types/api/api-general';
import { ApprovalLine } from '@/types/config/constant';
import {
BaseApiResponse,
BaseApproval,
BaseGroupedApproval,
ModuleWithApproval,
} from '@/types/api/api-general';
import { AppConfigData, 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';
@@ -120,7 +129,7 @@ export const formatGroupedApprovalsToApprovalSteps = (
const currentStepNumber = approvalLineItem.step_number;
const lastStepNumber =
groupedApprovals[groupedApprovals.length - 1].step_number;
groupedApprovals[groupedApprovals.length - 1]?.step_number;
if (!approvalGroup && currentStepNumber <= lastStepNumber) {
throw new Error(
@@ -137,22 +146,24 @@ export const formatGroupedApprovalsToApprovalSteps = (
};
}
let approvalStatus: ApprovalStepStatus;
let approvalStatus: ApprovalStepStatus = 'IDLE';
if (approvalGroup.step_number <= latestApproval.step_number) {
switch (approvalGroup.approvals[0].action) {
case 'CREATED':
case 'APPROVED':
approvalStatus = 'APPROVED';
break;
if (approvalGroup.approvals) {
switch (approvalGroup?.approvals[0]?.action) {
case 'CREATED':
case 'APPROVED':
approvalStatus = 'APPROVED';
break;
case 'REJECTED':
approvalStatus = 'REJECTED';
break;
case 'REJECTED':
approvalStatus = 'REJECTED';
break;
default:
approvalStatus = 'IDLE';
break;
default:
approvalStatus = 'IDLE';
break;
}
}
} else if (approvalGroup.step_number === latestApproval.step_number + 1) {
approvalStatus = 'WAITING';
@@ -160,13 +171,13 @@ export const formatGroupedApprovalsToApprovalSteps = (
approvalStatus = 'IDLE';
}
const approvalLogs: ApprovalStepLog[] = approvalGroup.approvals.map(
(approval) => ({
action_by: approval.action_by.name,
date: approval.action_at,
notes: approval.notes,
})
);
const approvalLogs: ApprovalStepLog[] = approvalGroup.approvals
? approvalGroup.approvals.map((approval) => ({
action_by: approval.action_by.name,
date: approval.action_at,
notes: approval.notes,
}))
: [];
return {
name: approvalGroup.step_name,
@@ -179,3 +190,113 @@ export const formatGroupedApprovalsToApprovalSteps = (
};
export default ApprovalSteps;
const useApprovalSteps = <T extends ModuleWithApproval>({
moduleUrl,
moduleName,
moduleId,
params,
}: {
moduleUrl: string;
moduleName: string;
moduleId: string;
params?: {
page: number;
limit: number;
search?: string;
};
}) => {
const paramString = new URLSearchParams({
page: params?.page?.toString() || '',
limit: params?.limit?.toString() || '',
search: params?.search || '',
}).toString();
const SWR_KEY_CONSTANTS = '/constants';
const SWR_KEY_APPROVALS =
moduleName && moduleId
? `/approvals?module_name=${moduleName}&module_id=${moduleId}&group_step_number=true${
params ? `&${paramString}` : ''
}`
: null;
const SWR_KEY_CURRENT_DATA = moduleUrl;
// Get Approval Lines dari GET /constant
const { data: constData, isLoading: constIsLoading } = useSWR(
SWR_KEY_CONSTANTS,
async (url) => {
return await httpClientFetcher<AppConfigData>(url);
}
);
// Get Grouped Data dari GET /approvals
const {
data: approvalData,
isLoading: approvalIsLoading,
mutate: mutateApprovals,
} = useSWR(SWR_KEY_APPROVALS, async (url) => {
return await httpClientFetcher<BaseApiResponse<BaseGroupedApproval[]>>(url);
});
// Get latest approval
const {
data: currentData,
isLoading: currentIsLoading,
mutate: mutateCurrentData,
} = useSWR(SWR_KEY_CURRENT_DATA, async (url) => {
return await httpClientFetcher<BaseApiResponse<T>>(url);
});
// Fungsi Refresh
const refresh = useCallback(async () => {
await Promise.all([mutateApprovals(), mutateCurrentData()]);
}, [mutateApprovals, mutateCurrentData]);
const { approvalLine, groupedApprovals, latestApproval } = useMemo(() => {
const line = constData
? (constData.approval_workflows.find((approval) => {
return approval.key === moduleName;
})?.steps ?? [])
: [];
const grouped = isResponseSuccess(approvalData) ? approvalData.data : [];
const latest = isResponseSuccess(currentData)
? currentData.data?.approval
: undefined;
return {
approvalLine: line,
groupedApprovals: grouped,
latestApproval: latest,
};
}, [constData, approvalData, currentData, moduleName]);
const isLoading = constIsLoading || approvalIsLoading || currentIsLoading;
const approvals = useMemo(() => {
if (isLoading || !approvalLine.length || !latestApproval) {
return [];
}
try {
return formatGroupedApprovalsToApprovalSteps(
approvalLine,
groupedApprovals,
latestApproval as BaseApproval
);
} catch (error) {
console.warn('Gagal memformat approval steps:', error);
return [];
}
}, [isLoading, approvalLine, groupedApprovals, latestApproval]);
// Return Hook
return {
approvals,
isLoading,
rawData: isResponseSuccess(currentData) ? currentData.data : undefined,
refresh,
};
};
export { useApprovalSteps };