'use client'; import { useCallback, useMemo, useState } from 'react'; import { ColumnDef, SortingState, Row, Table as TableType, } from '@tanstack/react-table'; import ApprovalSteps, { useApprovalSteps, } from '@/components/pages/ApprovalSteps'; import Table from '@/components/Table'; import Button from '@/components/Button'; import { Icon } from '@iconify/react'; import { useModal } from '@/components/Modal'; import CheckboxInput from '@/components/input/CheckboxInput'; import Modal from '@/components/Modal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import PurchaseOrderStaffApprovalForm from '@/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm'; import PurchaseOrderAcceptApprovalForm from '@/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm'; import PurchaseOrderInvoice from '@/components/pages/purchase/order/PurchaseOrderInvoice'; import Card from '@/components/Card'; import { CreateManagerApprovalRequestPayload, CreateStaffApprovalRequestPayload, Purchase, PurchaseItem, } from '@/types/api/purchase/purchase'; import { PurchaseApi } from '@/services/api/purchase'; import { isResponseError } from '@/lib/api-helper'; import { toast } from 'react-hot-toast'; import { useSearchParams } from 'next/navigation'; import { formatCurrency, formatNumber, formatDate } from '@/lib/helper'; import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line'; const ItemPembelianDropdown = ({ onEdit }: { onEdit: () => void }) => { return ( ); }; const PenerimaanBarangDropdown = ({ onEdit }: { onEdit: () => void }) => { return ( ); }; interface PurchaseOrderDetailProps { type?: 'detail' | 'edit'; initialValues?: Purchase; refetchData?: () => void; } const PurchaseOrderDetail = ({ type = 'detail', initialValues, refetchData, }: PurchaseOrderDetailProps) => { // ===== MODAL HOOKS ===== const searchParams = useSearchParams(); const confirmationModalWithNotes = useModal(); const staffApprovalModal = useModal(); const staffRejectionModal = useModal(); const acceptApprovalModal = useModal(); const editModal = useModal(); const penerimaanBarangModal = useModal(); const deleteModal = useModal(); // ===== STATE MANAGEMENT ===== const [sorting, setSorting] = useState([]); const [rowSelection, setRowSelection] = useState>({}); const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [selectedItem, setSelectedItem] = useState(null); const selectedRowIds = Object.keys(rowSelection).map((item) => parseInt(item) ); // ===== COMPUTED VALUES ===== const purchaseOrderItems = useMemo( () => initialValues?.items || [], [initialValues?.items] ); const goodsReceiptItems = useMemo(() => { return purchaseOrderItems.filter((item) => item.received_date); }, [purchaseOrderItems]); const groupedGoodsReceiptItems = useMemo(() => { const uniqueProducts = Array.from( new Map( goodsReceiptItems.map((item) => [item.product?.id, item]) ).values() ); return uniqueProducts.map((item, index) => { const productGroupItems = goodsReceiptItems.filter( (groupItem) => groupItem.product?.id === item.product?.id ); const totalQty = productGroupItems.reduce( (sum, item) => sum + (item.total_qty || 0), 0 ); const receivedQty = productGroupItems.reduce( (sum, item) => sum + (item.sub_qty || 0), 0 ); const unreceivedQty = totalQty - receivedQty; const nominalReceived = productGroupItems.reduce( (sum, item) => sum + (item.sub_qty || 0) * (item.price || 0), 0 ); const nominalUnreceived = productGroupItems.reduce( (sum, item) => sum + unreceivedQty * (item.price || 0), 0 ); return { productIndex: index + 1, productName: item.product?.name || 'Unknown Product', productGroupItems, totalQty, receivedQty, unreceivedQty, nominalReceived, nominalUnreceived, }; }); }, [goodsReceiptItems]); const approvalStep = useMemo(() => { if (!initialValues?.latest_approval) return null; return initialValues.latest_approval.step_number; }, [initialValues?.latest_approval]); const { approvals, isLoading: approvalsLoading, rawDataApprovals, refresh: refreshApprovals, } = useApprovalSteps({ latestApproval: initialValues?.latest_approval, approvalLines: PURCHASE_ORDER_APPROVAL_LINE, moduleName: 'PURCHASES', moduleId: initialValues?.id?.toString() ?? '', params: { limit: 100, group_step_number: true, }, }); const showApprovalButton = approvalStep !== null && approvalStep >= 1 && approvalStep <= 3; const canDeleteItems = useMemo(() => { if (!initialValues?.latest_approval) return false; const currentStep = initialValues.latest_approval.step_number; const hasReachedStep5 = rawDataApprovals?.some( (approval) => approval.step_number === 5 ); return currentStep === 3 && !hasReachedStep5; }, [initialValues?.latest_approval, rawDataApprovals]); const handleApprovalClick = () => { if (!approvalStep) return; switch (approvalStep) { case 1: staffApprovalModal.openModal(); break; case 2: confirmationModalWithNotes.openModal(); break; case 3: acceptApprovalModal.openModal(); break; default: break; } }; const handleRejectionClick = () => { if (!approvalStep) return; switch (approvalStep) { case 1: staffRejectionModal.openModal(); break; default: break; } }; const canShowPurchaseOrderInvoice = useMemo(() => { if (!initialValues?.latest_approval) return false; const currentStep = initialValues.latest_approval.step_number; return currentStep >= 3; }, [initialValues?.latest_approval]); const canShowPenerimaanBarang = useMemo(() => { if (!initialValues?.latest_approval) return false; const currentStep = initialValues.latest_approval.step_number; return currentStep === 5; }, [initialValues?.latest_approval]); const totalBeforeTax = useMemo(() => { return purchaseOrderItems.reduce( (sum, item) => sum + (item.total_price || 0), 0 ); }, [purchaseOrderItems]); // ===== SUBMISSION HANDLER ===== const createStaffApprovalHandler = useCallback( async (payload: CreateStaffApprovalRequestPayload) => { const purchaseRequestId = searchParams.get('purchaseId') ? parseInt(searchParams.get('purchaseId')!) : initialValues?.id || 1; if (!purchaseRequestId) { toast.error('Purchase Request ID is required'); return; } const res = await PurchaseApi.staffApproval.create( purchaseRequestId, payload ); if (isResponseError(res)) { toast.error(res.message); return; } toast.success(res?.message as string); refreshApprovals(); refetchData?.(); }, [initialValues?.id, searchParams, refreshApprovals, refetchData] ); const createManagerApprovalHandler = useCallback( async (payload: CreateManagerApprovalRequestPayload) => { const purchaseRequestId = searchParams.get('purchaseId') ? parseInt(searchParams.get('purchaseId')!) : initialValues?.id || 1; if (!purchaseRequestId) { toast.error('Purchase Request ID is required'); return; } const res = await PurchaseApi.managerApproval.create( purchaseRequestId, payload ); if (isResponseError(res)) { toast.error(res.message); return; } toast.success(res?.message as string); refetchData?.(); }, [initialValues?.id, searchParams, refetchData] ); // ===== MODAL HANDLERS ===== const handleStaffApprovalModalClose = useCallback(() => { refreshApprovals(); refetchData?.(); staffApprovalModal.closeModal(); }, [refreshApprovals, refetchData]); const handleEditModalClose = useCallback(() => { refreshApprovals(); refetchData?.(); editModal.closeModal(); }, [refreshApprovals, refetchData]); // ===== DELETE HANDLER ===== const deleteItemsHandler = useCallback(async () => { const purchaseRequestId = searchParams.get('purchaseId') ? parseInt(searchParams.get('purchaseId')!) : initialValues?.id || 1; if (!purchaseRequestId) { toast.error('Purchase Request ID is required'); return; } const itemIdsToDelete = selectedItem ? [selectedItem.id] : selectedRowIds; if (itemIdsToDelete.length === 0) { toast.error('Pilih minimal 1 item untuk dihapus'); return; } setIsDeleteLoading(true); try { const res = await PurchaseApi.items.delete(purchaseRequestId, { item_ids: itemIdsToDelete, }); if (isResponseError(res)) { toast.error(res.message || 'Gagal menghapus item pembelian'); return; } const successMessage = selectedItem ? 'Berhasil menghapus item pembelian' : `Berhasil menghapus ${itemIdsToDelete.length} item pembelian`; toast.success(successMessage); refreshApprovals(); refetchData?.(); deleteModal.closeModal(); setSelectedItem(null); setRowSelection({}); } catch (error) { toast.error('Terjadi kesalahan saat menghapus item pembelian'); } finally { setIsDeleteLoading(false); } }, [ initialValues?.id, searchParams, selectedItem, selectedRowIds, refetchData, ]); if (!initialValues) { return null; } const purchaseData = initialValues; const purchaseOrderColumns: ColumnDef[] = [ ...(canDeleteItems ? [ { id: 'select', header: ({ table }: { table: TableType }) => (
), cell: ({ row }: { row: Row }) => { return (
); }, }, ] : []), { header: 'No', cell: (props) => props.row.index + 1, }, { accessorKey: 'product.name', header: 'Produk', cell: (props) => props.row.original.product?.name || '-', }, { accessorKey: 'product.product_category', header: 'Jenis Produk', cell: (props) => { const category = props.row.original.product?.product_category; if (typeof category === 'string') { return category; } return category?.name || '-'; }, }, { accessorKey: 'sub_qty', header: 'Jumlah', cell: (props) => formatNumber(props.getValue() as number), }, { accessorKey: 'product.uom.name', header: 'Satuan', cell: (props) => { const uom = props.row.original.product?.uom; if (uom && typeof uom === 'object' && uom.name) { return uom.name; } return uom || '-'; }, }, { accessorKey: 'price', header: 'Harga Satuan', cell: (props) => formatCurrency(props.getValue() as number), }, { accessorKey: 'total_price', header: 'Total (Rp.)', cell: (props) => formatCurrency(props.getValue() as number), }, ...(canDeleteItems ? [ { header: 'Aksi', cell: (props: { row: Row }) => { const deleteClickHandler = () => { setSelectedItem(props.row.original); setRowSelection({}); deleteModal.openModal(); }; return ( ); }, }, ] : []), ]; const goodsReceiptColumns: ColumnDef[] = [ { header: 'Tanggal Penerimaan', accessorKey: 'received_date', cell: (props) => props.row.original.received_date ? formatDate(props.row.original.received_date, 'DD MMM YYYY') : '-', }, { header: 'Gudang Tujuan', accessorKey: 'warehouse.name', cell: (props) => { const warehouse = props.row.original.warehouse; return warehouse?.name || '-'; }, }, { header: 'No. Surat Jalan', accessorKey: 'travel_number', cell: (props) => props.row.original.travel_number || '-', }, { header: 'Dokumen Surat Jalan', accessorKey: 'travel_document_path', cell: (props) => { const documentPath = props.row.original.travel_document_path; return documentPath ? ( ) : ( '-' ); }, }, { header: 'No. Armada Pengangkut', accessorKey: 'vehicle_number', cell: (props) => props.row.original.vehicle_number || '-', }, { header: 'Jumlah Total', accessorKey: 'sub_qty', cell: (props) => formatNumber(props.getValue() as number), }, { header: 'Jumlah Diterima', accessorKey: 'total_qty', cell: (props) => formatNumber(props.getValue() as number), }, { header: 'Ekspedisi', accessorKey: 'expedition_name', cell: (props) => '-', }, { header: 'Transport /Item', accessorKey: 'transport_per_item', cell: (props) => formatCurrency(props.getValue() as number), }, { header: 'Transport Total', accessorKey: 'transport_total', cell: (props) => formatCurrency(props.getValue() as number), }, ]; const summaryData = [ { label: 'Total Sebelum Pajak', value: totalBeforeTax, }, { label: 'Total Pembayaran', value: totalBeforeTax, }, ]; const summaryColumns: ColumnDef<(typeof summaryData)[0]>[] = [ { accessorKey: 'label', header: '', cell: (props) => ( {props.getValue() as string} ), }, { accessorKey: 'value', header: '', cell: (props) => ( {formatCurrency(props.getValue() as number)} ), }, ]; return (
{/* Approval and Action Buttons */}
{showApprovalButton && (
)}
{/* Steps */} {approvals && !approvalsLoading && (
)} {/* Detail Purchase Order */} {/* Order Information */}

Informasi Pesanan

{/* Kolom 1 */}
Area : {purchaseData.items?.[0]?.warehouse?.area?.name || '-'}
Lokasi :{' '} {purchaseData.items?.[0]?.warehouse?.type === 'LOKASI' && purchaseData.items?.[0]?.warehouse?.location?.name ? purchaseData.items[0].warehouse.location.name : '-'}
Gudang : {purchaseData.items?.[0]?.warehouse?.name || '-'}
{/* Kolom 2 */}
Nama Vendor : {purchaseData.supplier?.name || '-'} ( {purchaseData.supplier?.alias || ''})
Kategori Vendor : {purchaseData.supplier?.category || '-'}
Tgl. Jatuh Tempo : {formatDate(purchaseData.due_date, 'D MMM YYYY')} ( {purchaseData.credit_term} hari)
Nomor : {purchaseData.pr_number}
Nomor PO
{canShowPurchaseOrderInvoice && purchaseData.po_number && purchaseData.po_number !== 'Belum dibuat' ? ( ) : ( <> : Belum dibuat )}
{/* Item Pembelian Section */}

Item Pembelian

{canShowPurchaseOrderInvoice && ( )}
{/* Product Table */}
data={purchaseOrderItems} columns={purchaseOrderColumns} isLoading={false} sorting={sorting} setSorting={setSorting} rowSelection={rowSelection} setRowSelection={setRowSelection} enableRowSelection={() => canDeleteItems} className={{ containerClassName: 'm-0', tableWrapperClassName: 'overflow-x-auto', tableClassName: 'w-full table-auto', headerRowClassName: 'bg-gray-50 border-b border-gray-200', headerColumnClassName: 'px-6 py-4 text-sm font-semibold text-gray-700 text-left', bodyRowClassName: 'border-b border-gray-100 hover:bg-gray-50 transition-colors', bodyColumnClassName: 'px-6 py-4 text-sm text-gray-900', paginationClassName: 'hidden', }} />
{/* Bulk Action Buttons */} {selectedRowIds.length > 0 && canDeleteItems && (
)} {/* Bottom Section - Catatan dan Total */}
{/* Catatan Section */}

Catatan

{purchaseData.notes || 'Tidak ada catatan'}
{/* Summary Section */}
{/* Penerimaan Barang */} {/* Detail Penerimaan Barang Section */}

Informasi Penerimaan Barang

{canShowPenerimaanBarang && ( )}
{groupedGoodsReceiptItems.length > 0 ? (
{groupedGoodsReceiptItems.map((productData, index) => (
{/* Product Header */}
{productData.productIndex}.{' '} {productData.productName.toUpperCase()}
{/* Product Table */} data={productData.productGroupItems} columns={goodsReceiptColumns} isLoading={false} className={{ containerClassName: 'm-0', tableWrapperClassName: 'overflow-x-auto', tableClassName: 'w-full table-auto', headerRowClassName: 'bg-gray-50 border-b border-gray-200', headerColumnClassName: 'px-4 py-3 text-sm font-semibold text-gray-700 text-left whitespace-nowrap', bodyRowClassName: 'border-b border-gray-100 hover:bg-gray-50 transition-colors', bodyColumnClassName: 'px-4 py-3 text-sm text-gray-900 whitespace-nowrap', paginationClassName: 'hidden', }} />
{/* Add divider after table except for last item */} {index < groupedGoodsReceiptItems.length - 1 && (
)}
))}
) : (
Tidak ada data penerimaan barang
)}
{/* Confirmation Modal with Notes */} { const payload: CreateManagerApprovalRequestPayload = { action: 'APPROVED', notes: notes || null, }; await createManagerApprovalHandler(payload); await refreshApprovals(); await refetchData?.(); confirmationModalWithNotes.closeModal(); }, }} secondaryButton={{ text: 'Batal', }} /> {/* Staff Approval Modal */} {/* Accept Approval Modal */} {/* Edit Modal */} {/* Penerimaan Barang Modal */} {/* Staff Rejection Modal */} { const payload: CreateStaffApprovalRequestPayload = { action: 'REJECTED', notes: notes || null, items: [], }; await createStaffApprovalHandler(payload); await refetchData?.(); staffRejectionModal.closeModal(); }, }} secondaryButton={{ text: 'Batal', }} /> {/* Delete Confirmation Modal */} { await deleteItemsHandler(); }, }} secondaryButton={{ text: 'Batal', }} /> ); }; export default PurchaseOrderDetail;