diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index d5021403..b84b5081 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -23,6 +23,7 @@ import { } from '@/services/api/production'; import { LocationApi } from '@/services/api/master-data'; import { ProductWarehouseApi } from '@/services/api/inventory'; +import { ApprovalApi } from '@/services/api/approval'; import { CreateGrowingRecordingPayload, @@ -31,7 +32,11 @@ import { UpdateLayingRecordingPayload, Recording, } from '@/types/api/production/recording'; -import { type BaseApiResponse, FormStepStatus } from '@/types/api/api-general'; +import { + type BaseApiResponse, + BaseApproval, + BaseGroupedApproval, +} from '@/types/api/api-general'; import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { Kandang } from '@/types/api/master-data/kandang'; @@ -50,7 +55,8 @@ import { import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; import { formatDate } from '@/lib/helper'; import toast from 'react-hot-toast'; -import StepItem from '@/components/steps/StepItem'; +import ApprovalSteps from '@/components/pages/ApprovalSteps'; +import { RECORDING_APPROVAL_LINE } from '@/config/approval-line'; interface RecordingFormProps { type?: 'add' | 'edit' | 'detail'; @@ -85,7 +91,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); - const [formSteps, setFormSteps] = useState(null); const [recordingFormErrorMessage, setRecordingFormErrorMessage] = useState(''); const [isDeleteLoading, setIsDeleteLoading] = useState(false); @@ -330,7 +335,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { location_id: selectedLocation.value.toString(), }); - // Add kandang_id parameter if available from lookup if (projectFlockKandangLookup?.kandang?.id) { params.append( 'kandang_id', @@ -351,7 +355,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { location_id: selectedLocation.value.toString(), }); - // Add kandang_id parameter if available from lookup if (projectFlockKandangLookup?.kandang?.id) { params.append( 'kandang_id', @@ -366,7 +369,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const today = new Date().toISOString().split('T')[0]; const existingRecordingsUrl = useMemo(() => { - return `${RecordingApi.basePath}?record_date=${today}`; + const params = new URLSearchParams({ + record_date: today, + }); + return `${RecordingApi.basePath}?${params.toString()}`; }, [today]); const { data: existingRecordings } = useSWR( @@ -407,6 +413,231 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ProductWarehouseApi.getAllFetcher ); + // ===== APPROVAL DATA FETCHING ===== + const approvalHistoryUrl = useMemo(() => { + if (!initialValues?.id || type !== 'detail') return null; + const params = new URLSearchParams({ + module_name: 'RECORDINGS', + module_id: initialValues.id.toString(), + group_step_number: 'true', + }); + return `${ApprovalApi.basePath}?${params.toString()}`; + }, [initialValues?.id, type]); + + const { data: approvalHistoryData } = useSWR( + approvalHistoryUrl, + approvalHistoryUrl ? ApprovalApi.getAllFetcher : null + ); + + // Helper functions for approval data processing + const groupApprovalsByStep = useCallback( + (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); + }, + [] + ); + + const isGroupedApprovalData = useCallback( + ( + 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) + ); + }, + [] + ); + + // Process approval data + const { groupedApprovals, latestApproval } = useMemo(() => { + const latest = initialValues?.approval; + + const rawData = isResponseSuccess(approvalHistoryData) + ? approvalHistoryData.data + : undefined; + + let processedGroupedApprovals: BaseGroupedApproval[] = []; + + if (rawData) { + if (isGroupedApprovalData(rawData)) { + processedGroupedApprovals = rawData as BaseGroupedApproval[]; + } else { + processedGroupedApprovals = groupApprovalsByStep( + rawData as BaseApproval[] + ); + } + } + + if ( + latest && + !processedGroupedApprovals.find( + (g) => g.step_number === latest.step_number + ) + ) { + processedGroupedApprovals.push({ + step_number: latest.step_number, + step_name: latest.step_name, + approvals: [latest], + }); + + processedGroupedApprovals.sort((a, b) => a.step_number - b.step_number); + } + + return { + groupedApprovals: processedGroupedApprovals, + latestApproval: latest, + }; + }, [ + approvalHistoryData, + initialValues?.approval, + isGroupedApprovalData, + groupApprovalsByStep, + ]); + + // Format approval steps for display + const approvalStepsData = useMemo(() => { + if (type !== 'detail') { + return []; + } + + try { + return RECORDING_APPROVAL_LINE.map( + ( + approvalLineItem + ): { + name: string; + status: 'APPROVED' | 'REJECTED' | 'WAITING' | 'IDLE'; + logs: Array<{ + action_by?: string; + date?: string; + notes?: string | null; + }>; + } => { + const approvalGroup = groupedApprovals.find( + (approvalGroupItem) => + approvalGroupItem.step_number === approvalLineItem.step_number + ); + + if (!latestApproval) { + return { + name: approvalLineItem.step_name, + status: 'IDLE', + logs: approvalGroup + ? approvalGroup.approvals.map((approval) => ({ + action_by: approval.action_by.name, + date: approval.action_at, + notes: approval.notes, + })) + : [], + }; + } + + const currentStepNumber = approvalLineItem.step_number; + const latestStepNumber = latestApproval.step_number; + + if (!approvalGroup) { + if (currentStepNumber === latestStepNumber + 1) { + return { + name: approvalLineItem.step_name, + status: 'WAITING', + logs: [], + }; + } + return { + name: approvalLineItem.step_name, + status: 'IDLE', + logs: [], + }; + } + + let approvalStatus: 'APPROVED' | 'REJECTED' | 'WAITING' | 'IDLE' = + 'IDLE'; + + if (currentStepNumber <= latestStepNumber) { + if (approvalGroup.approvals && approvalGroup.approvals.length > 0) { + const latestApprovalInGroup = approvalGroup.approvals.sort( + (a, b) => + new Date(b.action_at).getTime() - + new Date(a.action_at).getTime() + )[0]; + + switch (latestApprovalInGroup.action) { + case 'CREATED': + case 'APPROVED': + approvalStatus = 'APPROVED'; + break; + case 'REJECTED': + approvalStatus = 'REJECTED'; + break; + case 'UPDATED': + if (currentStepNumber === latestStepNumber) { + switch (latestApproval.action) { + case 'CREATED': + case 'APPROVED': + approvalStatus = 'APPROVED'; + break; + case 'REJECTED': + approvalStatus = 'REJECTED'; + break; + default: + approvalStatus = 'WAITING'; + break; + } + } else { + approvalStatus = 'APPROVED'; + } + break; + default: + approvalStatus = + currentStepNumber === latestStepNumber + ? 'WAITING' + : 'APPROVED'; + break; + } + } + } else if (currentStepNumber === latestStepNumber + 1) { + approvalStatus = 'WAITING'; + } else { + approvalStatus = 'IDLE'; + } + + const approvalLogs = 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, + }; + } + ); + } catch (error) { + console.warn('Gagal memformat approval steps:', error); + return []; + } + }, [groupedApprovals, latestApproval, type]); + // ===== DATA PROCESSING ===== const locationOptions = useMemo(() => { let options: OptionType[] = []; @@ -1058,26 +1289,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setIsRejectLoading(false); }; - useEffect(() => { - if (isLayingCategory) { - const steps: FormStepStatus[] = [ - { - name: 'Recording', - isCompleted: type === 'detail', - isCurrent: type !== 'detail', - }, - { - name: 'Grading', - isCompleted: false, - isCurrent: type === 'detail', - }, - ]; - setFormSteps(steps); - } else { - setFormSteps(null); - } - }, [isLayingCategory, type]); - // Body Weights Handlers const addBodyWeight = () => { const newBodyWeights = [ @@ -1311,23 +1522,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, [isLayingCategory, type]); useEffect(() => { - if (isLayingCategory) { - const steps: FormStepStatus[] = [ - { - name: 'Recording', - isCompleted: type === 'detail', - isCurrent: type !== 'detail', - }, - { - name: 'Grading', - isCompleted: false, - isCurrent: type === 'detail', - }, - ]; - setFormSteps(steps); - } else { - setFormSteps(null); - } if (type !== 'add') { setNewRecordingData(null); } @@ -1434,38 +1628,31 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { {/* Project Flock Info Card */} {(projectFlockKandangLookup || projectFlockKandangDetail) && (
- {/* Form Steps for LAYING Category */} - {formSteps && ( + {/* Approval Steps for all categories */} + {type === 'detail' && approvalStepsData.length > 0 && (
-
-
    - {formSteps.map((step, idx) => ( - - ) : ( - idx + 1 - ) - } - > - {step.name} - - ))} -
-
+ +
+ )} + {/* Default approval steps for add/edit modes */} + {type !== 'detail' && ( +
+
)}
@@ -1558,10 +1745,56 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }} >
-
- Recording ID -

#{initialValues.id}

-
+ {initialValues.approval && ( +
+ + Status Approval + +
+ + {(() => { + const actionText = (() => { + switch (initialValues.approval.action) { + case 'APPROVED': + return 'Disetujui'; + case 'REJECTED': + return 'Ditolak'; + case 'CREATED': + return 'Dibuat'; + case 'UPDATED': + return 'Diperbarui'; + default: + return initialValues.approval.action; + } + })(); + + const stepName = initialValues.approval.step_name; + + if (stepName === actionText) { + return stepName; + } + + return `${stepName} - ${actionText}`; + })()} + +
+
+ )}
Lokasi

diff --git a/src/config/approval-line.ts b/src/config/approval-line.ts index 0a02d1f1..4e5a5743 100644 --- a/src/config/approval-line.ts +++ b/src/config/approval-line.ts @@ -10,3 +10,18 @@ export const PROJECT_FLOCK_APPROVAL_LINE: ApprovalLine = [ step_name: 'Aktif', }, ] as const; + +export const RECORDING_APPROVAL_LINE: ApprovalLine = [ + { + step_number: 1, + step_name: 'Grading-Telur', + }, + { + step_number: 2, + step_name: 'Pengajuan', + }, + { + step_number: 3, + step_name: 'Disetujui', + }, +] as const; diff --git a/src/types/api/api-general.d.ts b/src/types/api/api-general.d.ts index f2a22363..af9144e2 100644 --- a/src/types/api/api-general.d.ts +++ b/src/types/api/api-general.d.ts @@ -112,12 +112,6 @@ export type BaseGroupedApproval = { approvals: BaseApproval[]; }; -export type FormStepStatus = { - name: string; - isCompleted: boolean; - isCurrent: boolean; -}; - export type Approvals = BaseApiResponse; export type GroupedApprovals = BaseApiResponse;