mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-22 14:25:47 +00:00
251 lines
7.9 KiB
TypeScript
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;
|