'use client'; import { ChangeEventHandler, useCallback, useEffect, useMemo, useState, } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; import { ColumnDef, ColumnSort, SortingState } from '@tanstack/react-table'; import { useFormik } from 'formik'; import Button from '@/components/Button'; import Table from '@/components/Table'; import RequirePermission from '@/components/helper/RequirePermission'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import SelectInput, { useSelect } from '@/components/input/SelectInput'; import { OptionType } from '@/components/input/SelectInput'; import ButtonFilter from '@/components/helper/ButtonFilter'; import Modal, { useModal } from '@/components/Modal'; import { isResponseSuccess } from '@/lib/api-helper'; import { cn, formatNumber, formatDate, formatCurrency } from '@/lib/helper'; import { InventoryAdjustmentApi } from '@/services/api/inventory'; import { WarehouseApi, ProductApi } from '@/services/api/master-data'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import PopoverButton from '@/components/popover/PopoverButton'; import PopoverContent from '@/components/popover/PopoverContent'; import toast from 'react-hot-toast'; import { InventoryAdjustment } from '@/types/api/inventory/adjustment'; import { Warehouse } from '@/types/api/master-data/warehouse'; import { TRANSACTION_SUBTYPE_OPTIONS } from '@/config/constant'; import { Product } from '@/types/api/master-data/product'; import StatusBadge from '@/components/helper/StatusBadge'; import InventoryAdjustmentTableSkeleton from '@/components/pages/inventory/adjustment/skeleton/InventoryAdjustmentTableSkeleton'; import { AdjustmentFilterSchema, AdjustmentFilterType, } from '@/components/pages/inventory/adjustment/filter/AdjustmentFilter'; import SelectInputRadio from '@/components/input/SelectInputRadio'; import { CellContext } from '@tanstack/react-table'; const RowOptionsMenu = ({ popoverPosition = 'bottom', props, deleteClickHandler, }: { popoverPosition: 'bottom' | 'top'; props: CellContext; deleteClickHandler: () => void; }) => { const popoverId = `adjustment#${props.row.original.id}`; const popoverAnchorName = `--anchor-adjustment#${props.row.original.id}`; const closePopover = () => { document.getElementById(popoverId)?.hidePopover(); }; return (
); }; const InventoryAdjustmentTable = () => { const { state: tableFilterState, updateFilter, setPage, setPageSize, toQueryString: getTableFilterQueryString, } = useTableFilter<{ search: string; productCategorySort: string; productSort: string; warehouseSort: string; stockSort: string; productFilter?: OptionType; warehouseFilter?: OptionType; transactionTypeFilter?: OptionType; }>({ initial: { search: '', productCategorySort: '', productSort: '', warehouseSort: '', stockSort: '', productFilter: undefined, warehouseFilter: undefined, transactionTypeFilter: undefined, }, paramMap: { page: 'page', pageSize: 'limit', productCategorySort: 'sort_product_category', productSort: 'sort_product', warehouseSort: 'sort_warehouse', stockSort: 'sort_stock', productFilter: 'product_id', warehouseFilter: 'warehouse_id', transactionTypeFilter: 'transaction_type', }, persist: true, storeName: 'inventory-adjustment-table', }); // ===== FILTER MODAL STATE ===== const filterModal = useModal(); // ===== FORMIK SETUP ===== const formik = useFormik({ initialValues: { product: tableFilterState.productFilter, warehouse: tableFilterState.warehouseFilter, transaction_type: tableFilterState.transactionTypeFilter, }, validationSchema: AdjustmentFilterSchema, onSubmit: (values, { setSubmitting }) => { updateFilter('productFilter', values.product || undefined, true); updateFilter('warehouseFilter', values.warehouse || undefined, true); updateFilter( 'transactionTypeFilter', values.transaction_type || undefined, true ); filterModal.closeModal(); setSubmitting(false); }, onReset: () => { updateFilter('productFilter', undefined, true); updateFilter('warehouseFilter', undefined, true); updateFilter('transactionTypeFilter', undefined, true); filterModal.closeModal(); }, }); // ===== PRODUCT OPTIONS ===== const { setInputValue: setProductInputValue, options: productOptions, isLoadingOptions: isLoadingProductOptions, loadMore: loadMoreProducts, } = useSelect( filterModal.open ? ProductApi.basePath : null, 'id', 'name', 'search' ); // ===== WAREHOUSE OPTIONS ===== const { setInputValue: setWarehouseInputValue, options: warehouseOptions, isLoadingOptions: isLoadingWarehouseOptions, loadMore: loadMoreWarehouses, } = useSelect( filterModal.open ? WarehouseApi.basePath : null, 'id', 'name', 'search' ); // ===== TRANSACTION TYPE OPTIONS ===== const transactionTypeOptions = useMemo(() => { return [ { value: 'increase', label: 'Increase' }, { value: 'decrease', label: 'Decrease' }, ]; }, []); // ===== FILTER HANDLERS ===== const handleFilterProductChange = (val: OptionType | OptionType[] | null) => { formik.setFieldValue('product', val); }; const handleFilterWarehouseChange = ( val: OptionType | OptionType[] | null ) => { formik.setFieldValue('warehouse', val); }; const handleFilterTransactionTypeChange = ( val: OptionType | OptionType[] | null ) => { formik.setFieldValue('transaction_type', val); }; // ===== HANDLE FILTER MODAL OPEN ===== const handleFilterModalOpen = () => { formik.setValues({ product: tableFilterState.productFilter ?? undefined, warehouse: tableFilterState.warehouseFilter ?? undefined, transaction_type: tableFilterState.transactionTypeFilter ?? undefined, }); filterModal.openModal(); }; const { data: inventoryAdjustments, isLoading, mutate: refreshAdjustments, } = useSWR( `${InventoryAdjustmentApi.basePath}${getTableFilterQueryString()}`, InventoryAdjustmentApi.getAllFetcher ); const singleDeleteHandler = async () => { setIsDeleteLoading(true); const response = await InventoryAdjustmentApi.delete( selectedAdjustment?.id as number ); singleDeleteModal.closeModal(); setIsDeleteLoading(false); if (isResponseSuccess(response)) { toast.success(response?.message || 'Successfully delete Adjustment!'); refreshAdjustments(); } else { toast.error(response?.message || 'Failed to delete Adjustment'); } }; const [sorting, setSorting] = useState([]); const [selectedAdjustment, setSelectedAdjustment] = useState< InventoryAdjustment | undefined >(undefined); const [isDeleteLoading, setIsDeleteLoading] = useState(false); const singleDeleteModal = useModal(); const searchChangeHandler: ChangeEventHandler = (e) => { updateFilter('search', e.target.value, true); }; const inventoryAdjustmentsColumns: ColumnDef[] = useMemo( () => [ { id: 'adj_number', header: 'No. Referensi', accessorFn: (row) => row.adj_number ?? '-', }, { id: 'location', header: 'Lokasi', accessorFn: (row) => row.location?.name ?? '-', }, { id: 'project_flock', header: 'Flock', accessorFn: (row) => row.project_flock?.flock_name ?? '-', }, { id: 'warehouse_name', header: 'Gudang', accessorFn: (row) => row.product_warehouse?.warehouse?.name ?? '-', }, { id: 'product_name', header: 'Nama Produk', accessorFn: (row) => row.product_warehouse?.product?.name ?? '-', }, { id: 'quantity', header: 'Kuantitas', accessorFn: (row) => row.qty ?? '-', cell: (row) => { const value = row.row.original.increase + row.row.original.decrease; return
{formatNumber(value)}
; }, }, { id: 'price', header: 'Harga', accessorFn: (row) => (row.price ? formatCurrency(row.price) : '-'), }, { id: 'grand_total', header: 'Grand Total', accessorFn: (row) => row.grand_total ? formatCurrency(row.grand_total) : '-', }, { id: 'transaction_type', header: 'Tipe Transaksi', accessorFn: (row) => row.transaction_subtype ?? '-', cell: (row) => { const subtype = row.row.original.transaction_subtype; const increase = row.row.original.increase; const getSubtypeLabel = (subtypeValue: string): string => { if (subtypeValue === TRANSACTION_SUBTYPE_OPTIONS.PEMBELIAN.value) { return TRANSACTION_SUBTYPE_OPTIONS.PEMBELIAN.label; } if (subtypeValue === TRANSACTION_SUBTYPE_OPTIONS.PENJUALAN.value) { return TRANSACTION_SUBTYPE_OPTIONS.PENJUALAN.label; } const recordingOption = TRANSACTION_SUBTYPE_OPTIONS.RECORDING.find( (opt) => opt.value === subtypeValue ); if (recordingOption) { return recordingOption.label; } if (subtypeValue === 'RECORDING_DEPLETION_OUT') { return 'Recording Depletion'; } return subtypeValue || '-'; }; const label = getSubtypeLabel(subtype); return ( 0 ? 'success' : increase <= 0 ? 'error' : 'neutral' } text={label} className={{ badge: 'whitespace-nowrap', }} /> ); }, }, { id: 'created_at', header: 'Tanggal', accessorFn: (row) => row.created_at ? formatDate(row.created_at, 'DD MMM YYYY') : '-', }, { id: 'created_by', header: 'Oleh', accessorFn: (row) => row.created_user?.name ?? '-', }, { id: 'actions', header: 'Aksi', cell: (props: CellContext) => { const currentPageSize = props.table.getPaginationRowModel().rows.length; const currentPageRows = props.table.getPaginationRowModel().flatRows; const currentRowRelativeIndex = currentPageRows.findIndex((r) => r.id === props.row.id) + 1; const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2; const deleteClickHandler = () => { setSelectedAdjustment(props.row.original); singleDeleteModal.openModal(); }; return ( ); }, }, ], [ tableFilterState.pageSize, tableFilterState.page, singleDeleteModal, setSelectedAdjustment, ] ); const updateSortingFilter = useCallback( ( sortName: Exclude, sortFilter: ColumnSort | undefined ) => { if (!sortFilter) { updateFilter(sortName, ''); } else { updateFilter(sortName, sortFilter.desc ? 'desc' : 'asc'); } }, [updateFilter] ); useEffect(() => { const productCategorySortFilter = sorting.find( (sortItem) => sortItem.id === 'productCategory' ); const productSortFilter = sorting.find( (sortItem) => sortItem.id === 'product' ); const warehouseSortFilter = sorting.find( (sortItem) => sortItem.id === 'warehouse' ); const stockSortFilter = sorting.find((sortItem) => sortItem.id === 'stock'); updateSortingFilter('productCategorySort', productCategorySortFilter); updateSortingFilter('productSort', productSortFilter); updateSortingFilter('warehouseSort', warehouseSortFilter); updateSortingFilter('stockSort', stockSortFilter); }, [sorting, updateSortingFilter]); return ( <>
{/* Header Section */}
{/* Action Buttons */}
{/* Search and Filter */}
} className={{ wrapper: 'w-full min-w-24 max-w-3xs', inputWrapper: 'rounded-xl! shadow-button-soft', input: 'placeholder:font-semibold placeholder:text-base-content/50', }} />
{/* Table Section */}
{isLoading ? (
) : !isResponseSuccess(inventoryAdjustments) || inventoryAdjustments.data?.length === 0 ? (
} />
) : ( data={ isResponseSuccess(inventoryAdjustments) ? inventoryAdjustments?.data : [] } columns={inventoryAdjustmentsColumns} pageSize={tableFilterState.pageSize} page={ isResponseSuccess(inventoryAdjustments) ? inventoryAdjustments?.meta?.page : 0 } totalItems={ isResponseSuccess(inventoryAdjustments) ? inventoryAdjustments?.meta?.total_results : 0 } onPageChange={setPage} onPageSizeChange={setPageSize} isLoading={isLoading} sorting={sorting} setSorting={setSorting} className={{ containerClassName: cn('p-3 mb-0'), headerColumnClassName: 'text-nowrap', }} /> )}
{/* Filter Modal */} {/* Modal Header */}

Filter Data

{/* Modal Footer */}
); }; export default InventoryAdjustmentTable;