Files
lti-web-client/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx
T

251 lines
7.9 KiB
TypeScript

'use client';
import { useCallback, useEffect, useMemo, useState } from 'react';
import useSWR from 'swr';
import { Icon } from '@iconify/react';
import { ColumnDef, ColumnSort, SortingState } from '@tanstack/react-table';
import Button from '@/components/Button';
import Table from '@/components/Table';
import RequirePermission from '@/components/helper/RequirePermission';
import { isResponseSuccess } from '@/lib/api-helper';
import { cn, formatNumber, formatDate, formatCurrency } from '@/lib/helper';
import { InventoryAdjustmentApi } from '@/services/api/inventory';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { InventoryAdjustment } from '@/types/api/inventory/adjustment';
import StatusBadge from '@/components/helper/StatusBadge';
import InventoryAdjustmentTableSkeleton from '@/components/pages/inventory/adjustment/skeleton/InventoryAdjustmentTableSkeleton';
const InventoryAdjustmentTable = () => {
const {
state: tableFilterState,
updateFilter,
setPage,
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
initial: {
search: '',
productCategorySort: '',
productSort: '',
warehouseSort: '',
stockSort: '',
},
paramMap: {
page: 'page',
pageSize: 'limit',
productCategorySort: 'sort_product_category',
productSort: 'sort_product',
warehouseSort: 'sort_warehouse',
stockSort: 'sort_stock',
},
});
const { data: inventoryAdjustments, isLoading } = useSWR(
`${InventoryAdjustmentApi.basePath}${getTableFilterQueryString()}`,
InventoryAdjustmentApi.getAllFetcher
);
const [sorting, setSorting] = useState<SortingState>([]);
const inventoryAdjustmentsColumns: ColumnDef<InventoryAdjustment>[] = 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 <div className='text-center'>{formatNumber(value)}</div>;
},
},
{
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 subtypeLabelMap: Record<string, string> = {
PURCHASE_IN: 'Pembelian',
MARKETING_OUT: 'Penjualan',
RECORDING_STOCK_OUT: 'Recording Stock',
RECORDING_DEPLETION_OUT: 'Recording Depletion',
RECORDING_EGG_IN: 'Recording Egg',
ADJUSTMENT_OUT: 'Penyesuaian',
};
const label = subtypeLabelMap[subtype] || subtype || '-';
return (
<StatusBadge
color={
increase > 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 ?? '-',
},
],
[tableFilterState.pageSize, tableFilterState.page]
);
const updateSortingFilter = useCallback(
(
sortName: Exclude<keyof typeof tableFilterState, 'page' | 'pageSize'>,
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 (
<div className='w-full'>
{/* Header Section */}
<div className='w-full p-3 flex flex-row justify-between gap-3 flex-wrap border-b border-base-content/10'>
<div className='w-fit flex flex-row gap-3 flex-wrap'>
<RequirePermission permissions='lti.inventory.create'>
<Button
href='/inventory/adjustment/add'
color='primary'
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-lg shadow-sm'
>
<Icon icon='heroicons:plus' width={20} height={20} />
Add Adjustment
</Button>
</RequirePermission>
</div>
</div>
{/* Table Section */}
<div className='flex flex-col mb-4'>
{isLoading ? (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
) : !isResponseSuccess(inventoryAdjustments) ||
inventoryAdjustments.data?.length === 0 ? (
<div className='p-3'>
<InventoryAdjustmentTableSkeleton
columns={inventoryAdjustmentsColumns}
icon={
<Icon
icon='heroicons:document-text'
className='text-white'
width={20}
height={20}
/>
}
/>
</div>
) : (
<Table<InventoryAdjustment>
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',
}}
/>
)}
</div>
</div>
);
};
export default InventoryAdjustmentTable;