mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
262 lines
8.0 KiB
TypeScript
262 lines
8.0 KiB
TypeScript
'use client';
|
|
|
|
import Button from '@/components/Button';
|
|
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
|
import Table from '@/components/Table';
|
|
import { ROWS_OPTIONS } from '@/config/constant';
|
|
import { isResponseSuccess } from '@/lib/api-helper';
|
|
import { cn } from '@/lib/helper';
|
|
import { InventoryAdjustmentApi } from '@/services/api/inventory';
|
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
|
import { InventoryAdjustment } from '@/types/api/inventory/adjustment';
|
|
import { Icon } from '@iconify/react';
|
|
import { ColumnDef, ColumnSort, SortingState } from '@tanstack/react-table';
|
|
import { useCallback, useEffect, useState } from 'react';
|
|
import useSWR from 'swr';
|
|
|
|
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',
|
|
},
|
|
});
|
|
|
|
// Fetch Data
|
|
const { data: inventoryAdjustments, isLoading } = useSWR(
|
|
`${InventoryAdjustmentApi.basePath}${getTableFilterQueryString()}`,
|
|
InventoryAdjustmentApi.getAllFetcher
|
|
);
|
|
|
|
// State
|
|
const [sorting, setSorting] = useState<SortingState>([]);
|
|
|
|
// Columns
|
|
const inventoryAdjustmentsColumns: ColumnDef<InventoryAdjustment>[] = [
|
|
{
|
|
header: '#',
|
|
cell: (props) =>
|
|
tableFilterState.pageSize * (tableFilterState.page - 1) +
|
|
props.row.index +
|
|
1,
|
|
},
|
|
{
|
|
id: 'product_name',
|
|
header: 'Nama Produk',
|
|
accessorFn: (row) => row.product_warehouse?.product?.name ?? '-',
|
|
},
|
|
{
|
|
id: 'warehouse_name',
|
|
header: 'Gudang',
|
|
accessorFn: (row) => row.product_warehouse?.warehouse?.name ?? '-',
|
|
},
|
|
{
|
|
id: 'created_at',
|
|
header: 'Tanggal',
|
|
accessorFn: (row) =>
|
|
new Date(row.created_at).toLocaleDateString('id-ID', {
|
|
day: '2-digit',
|
|
month: 'short',
|
|
year: 'numeric',
|
|
}),
|
|
},
|
|
{
|
|
id: 'before_quantity',
|
|
header: 'Stok Sebelum',
|
|
accessorFn: (row) => formatNumber(String(row.before_quantity)),
|
|
},
|
|
{
|
|
id: 'after_quantity',
|
|
header: 'Stok Sesudah',
|
|
accessorFn: (row) => formatNumber(String(row.after_quantity)),
|
|
},
|
|
{
|
|
id: 'quantity',
|
|
header: 'Kuantitas',
|
|
accessorFn: (row) => formatNumber(String(row.quantity)),
|
|
},
|
|
{
|
|
id: 'transaction_type',
|
|
header: 'Tipe Transaksi',
|
|
accessorFn: (row) => {
|
|
if (row.transaction_type === 'INCREASE') return 'Peningkatan';
|
|
if (row.transaction_type === 'DECREASE') return 'Penurunan';
|
|
return '-';
|
|
},
|
|
cell: (props) => {
|
|
const type = props.row.original.transaction_type;
|
|
const label =
|
|
type === 'INCREASE'
|
|
? 'Peningkatan'
|
|
: type === 'DECREASE'
|
|
? 'Penurunan'
|
|
: '-';
|
|
|
|
return (
|
|
<div
|
|
className={`small mx-auto badge badge-soft ${
|
|
type === 'INCREASE' ? 'badge-success' : 'badge-error'
|
|
}`}
|
|
>
|
|
{label}
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: 'created_by',
|
|
header: 'Oleh',
|
|
accessorFn: (row) => row.created_user?.name ?? '-',
|
|
},
|
|
];
|
|
|
|
// Handler
|
|
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
|
const newVal = val as OptionType;
|
|
setPageSize(newVal.value as number);
|
|
};
|
|
|
|
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]
|
|
);
|
|
|
|
// Effect
|
|
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]);
|
|
|
|
// Utils Function
|
|
const formatNumber = (value: string) => {
|
|
const numericValue = value.replace(/[^0-9.]/g, '');
|
|
const [integer, decimal] = numericValue.split('.');
|
|
const formattedInteger = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
return decimal ? `${formattedInteger}.${decimal}` : formattedInteger;
|
|
};
|
|
|
|
// Render
|
|
return (
|
|
<>
|
|
<div className='w-full p-0 sm:p-4'>
|
|
<div className='flex flex-col gap-2 mb-4'>
|
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
|
<div className='w-full flex flex-row'>
|
|
<Button
|
|
href='/inventory/adjustment/add'
|
|
variant='outline'
|
|
color='primary'
|
|
className='w-full sm:w-fit'
|
|
>
|
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
|
Tambah
|
|
</Button>
|
|
|
|
{/* <DebouncedTextInput
|
|
name='search'
|
|
placeholder='Cari Stock Adjustment'
|
|
value={tableFilterState.search}
|
|
onChange={searchChangeHandler}
|
|
className={{ wrapper: 'sm:max-w-3xs' }}
|
|
/> */}
|
|
</div>
|
|
|
|
<div className='flex flex-row justify-end'>
|
|
<SelectInput
|
|
label='Baris'
|
|
options={ROWS_OPTIONS}
|
|
value={{
|
|
label: String(tableFilterState.pageSize),
|
|
value: tableFilterState.pageSize,
|
|
}}
|
|
onChange={pageSizeChangeHandler}
|
|
className={{ wrapper: 'min-w-28' }}
|
|
/>
|
|
</div>
|
|
</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}
|
|
isLoading={isLoading}
|
|
sorting={sorting}
|
|
setSorting={setSorting}
|
|
className={{
|
|
containerClassName: cn({
|
|
'mb-20':
|
|
isResponseSuccess(inventoryAdjustments) &&
|
|
inventoryAdjustments?.data?.length === 0,
|
|
}),
|
|
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
|
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
|
headerRowClassName: 'border-b border-b-gray-200',
|
|
headerColumnClassName:
|
|
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
|
bodyRowClassName: 'border-b border-b-gray-200',
|
|
bodyColumnClassName:
|
|
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default InventoryAdjustmentTable;
|