diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 15fbd2cd..8e0f7055 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -1,11 +1,13 @@ 'use client'; -import { useCallback, useState } from 'react'; +import { useCallback, useState, useMemo, useEffect } from 'react'; +import { RefObject } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; import { SortingState, CellContext } from '@tanstack/react-table'; import { cn, formatDate } from '@/lib/helper'; import { useModal } from '@/components/Modal'; +import Modal from '@/components/Modal'; import Button from '@/components/Button'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import { OptionType, useSelect } from '@/components/input/SelectInput'; @@ -21,6 +23,7 @@ import { RecordingApi } from '@/services/api/production'; import { AreaApi } from '@/services/api/master-data'; import { LocationApi } from '@/services/api/master-data'; import { KandangApi } from '@/services/api/master-data'; +import { ApprovalApi } from '@/services/api/approval'; import { isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import toast from 'react-hot-toast'; @@ -30,6 +33,7 @@ import TextArea from '@/components/input/TextArea'; import { Area } from '@/types/api/master-data/area'; import { Location } from '@/types/api/master-data/location'; import { Kandang } from '@/types/api/master-data/kandang'; +import { BaseApproval, BaseApiResponse } from '@/types/api/api-general'; const RowOptionsMenu = ({ type = 'dropdown', @@ -100,6 +104,209 @@ const RowOptionsMenu = ({ ); }; +const ApprovalHistoryModal = ({ + ref, + currentApproval, + module_name = 'RECORDINGS', +}: { + ref: RefObject; + currentApproval?: BaseApproval; + module_name?: string; +}) => { + const [isModalOpen, setIsModalOpen] = useState(false); + + const approvalHistoryUrl = useMemo(() => { + if (!isModalOpen) return null; + return `${ApprovalApi.basePath}?${new URLSearchParams({ + module_name: module_name, + group_step_number: 'true', + }).toString()}`; + }, [module_name, isModalOpen]); + + type GroupedApprovalResponse = { + step_number: number; + step_name: string; + approvals: BaseApproval[]; + }; + + const fetchGroupedApprovals = async ( + url: string + ): Promise> => { + return (await ApprovalApi.getAllFetcher(url)) as BaseApiResponse< + GroupedApprovalResponse[] + >; + }; + + const { data: approvalHistoryData, isLoading } = useSWR< + BaseApiResponse + >(approvalHistoryUrl, fetchGroupedApprovals); + + useEffect(() => { + const checkModalOpen = () => { + const isOpen = ref.current?.open || false; + setIsModalOpen(isOpen); + }; + + checkModalOpen(); + + const observer = new MutationObserver(checkModalOpen); + if (ref.current) { + observer.observe(ref.current, { + attributes: true, + attributeFilter: ['open'], + }); + } + + return () => observer.disconnect(); + }, [ref]); + + const approvalHistory = useMemo(() => { + if (!approvalHistoryData || approvalHistoryData.status !== 'success') + return []; + + const groupedData = approvalHistoryData.data || []; + const flattenedApprovals: BaseApproval[] = []; + + groupedData.forEach((group) => { + group.approvals.forEach((approval) => { + flattenedApprovals.push(approval); + }); + }); + + return flattenedApprovals; + }, [approvalHistoryData]); + + const closeModalHandler = () => { + ref.current?.close(); + }; + + return ( + +
+ {/* Header */} +
+

Riwayat Approval

+ +
+ + {isLoading ? ( +
+ +
+ ) : ( + <> + {/* Current Status */} + {currentApproval && ( +
+

Status Saat Ini

+
+ + {currentApproval.step_name} + + + {currentApproval.action === 'APPROVED' && 'Disetujui'} + {currentApproval.action === 'REJECTED' && 'Ditolak'} + {currentApproval.action === 'CREATED' && 'Dibuat'} + +
+ {currentApproval.notes && ( +

+ Catatan:{' '} + {currentApproval.notes} +

+ )} +

+ Oleh: {currentApproval.action_by.name} •{' '} + {formatDate(currentApproval.action_at, 'DD MMMM YYYY HH:mm')} +

+
+ )} + + {/* Full History */} + {approvalHistory.length > 0 && ( +
+

Riwayat Lengkap

+
+ + + + + + + + + + + + {approvalHistory + .sort( + (a: BaseApproval, b: BaseApproval) => + new Date(b.action_at).getTime() - + new Date(a.action_at).getTime() + ) + .map((approval: BaseApproval, index: number) => ( + + + + + + + + ))} + +
TahapAksiCatatanOlehWaktu
{approval.step_name} + + {approval.action === 'APPROVED' && 'Disetujui'} + {approval.action === 'REJECTED' && 'Ditolak'} + {approval.action === 'CREATED' && 'Dibuat'} + + +
+ {approval.notes || '-'} +
+
{approval.action_by.name} + {formatDate( + approval.action_at, + 'DD MMMM YYYY HH:mm' + )} +
+
+
+ )} + + )} +
+
+ ); +}; + const RecordingTable = () => { const { state: tableFilterState, @@ -142,6 +349,7 @@ const RecordingTable = () => { const singleDeleteModal = useModal(); const approveModal = useModal(); const rejectModal = useModal(); + const approvalHistoryModal = useModal(); // State for selected values const [selectedArea, setSelectedArea] = useState(null); @@ -472,11 +680,47 @@ const RecordingTable = () => { }, { header: 'Status Approval', - cell: (props) => props.row.original.approval?.step_name || '-', + cell: (props) => { + const approval = props.row.original.approval; + if (!approval) return '-'; + + const statusColor = + approval.action === 'APPROVED' + ? 'success' + : approval.action === 'REJECTED' + ? 'error' + : 'info'; + + const openApprovalHistory = () => { + setSelectedRecording(props.row.original); + approvalHistoryModal.openModal(); + }; + + return ( + + ); + }, }, { header: 'Catatan Approval', - cell: (props) => props.row.original.approval?.notes || '-', + cell: (props) => { + const approval = props.row.original.approval; + if (!approval?.notes) return '-'; + + return ( +
+

{approval.notes}

+
+ ); + }, }, { header: 'Status Grading Telur', @@ -652,6 +896,12 @@ const RecordingTable = () => { rows={3} /> + + ); }; diff --git a/src/services/api/approval.ts b/src/services/api/approval.ts new file mode 100644 index 00000000..1debace3 --- /dev/null +++ b/src/services/api/approval.ts @@ -0,0 +1,6 @@ +import { BaseApiService } from '@/services/api/base'; +import { BaseApproval } from '@/types/api/api-general'; + +export const ApprovalApi = new BaseApiService( + '/approvals' +);