mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
feat: implement export product stock log
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import { FormHeader } from '@/components/helper/form/FormHeader';
|
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||||
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import StockLogTable from '@/components/pages/inventory/product/detail/StockLogTable';
|
import StockLogTable from '@/components/pages/inventory/product/detail/StockLogTable';
|
||||||
import StockProductWarehouseTable from '@/components/pages/inventory/product/detail/StockProductWarehouseTable';
|
import StockProductWarehouseTable from '@/components/pages/inventory/product/detail/StockProductWarehouseTable';
|
||||||
import { formatCurrency, formatNumber } from '@/lib/helper';
|
import { formatCurrency, formatNumber } from '@/lib/helper';
|
||||||
@@ -11,18 +12,6 @@ const InventoryProductDetail = ({
|
|||||||
}: {
|
}: {
|
||||||
inventoryProduct?: InventoryProduct;
|
inventoryProduct?: InventoryProduct;
|
||||||
}) => {
|
}) => {
|
||||||
const stockLogs = useMemo(() => {
|
|
||||||
return (
|
|
||||||
inventoryProduct?.product_warehouses?.flatMap((warehouse) =>
|
|
||||||
warehouse.stock_logs.map((log) => ({
|
|
||||||
...log,
|
|
||||||
warehouse_name: warehouse.warehouse_name,
|
|
||||||
warehouse_id: warehouse.warehouse_id,
|
|
||||||
}))
|
|
||||||
) || []
|
|
||||||
);
|
|
||||||
}, [inventoryProduct]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-4 p-4'>
|
<div className='flex flex-col gap-4 p-4'>
|
||||||
<FormHeader
|
<FormHeader
|
||||||
@@ -114,7 +103,14 @@ const InventoryProductDetail = ({
|
|||||||
productWarehouseStock={inventoryProduct?.product_warehouses ?? []}
|
productWarehouseStock={inventoryProduct?.product_warehouses ?? []}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StockLogTable stockLogs={stockLogs} />
|
<RequirePermission permissions={'lti.inventory.stock_log.list'}>
|
||||||
|
{inventoryProduct?.product_warehouses?.map((productWarehouse) => (
|
||||||
|
<StockLogTable
|
||||||
|
key={productWarehouse.id}
|
||||||
|
productWarehouse={productWarehouse}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
|
import Button from '@/components/Button';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { formatDate, formatNumber, formatTitleCase } from '@/lib/helper';
|
import { formatDate, formatNumber, formatTitleCase } from '@/lib/helper';
|
||||||
|
import { StockLogApi } from '@/services/api/inventory';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { StockLog } from '@/types/api/inventory/product';
|
import { ProductWarehouseStock, StockLog } from '@/types/api/inventory/product';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import { FileDown } from 'lucide-react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
const stockLogTableColumns: ColumnDef<StockLog>[] = [
|
const stockLogTableColumns: (warehouseName: string) => ColumnDef<StockLog>[] = (
|
||||||
|
warehouseName
|
||||||
|
) => [
|
||||||
{
|
{
|
||||||
header: 'ID',
|
header: 'ID',
|
||||||
accessorKey: 'id',
|
accessorKey: 'id',
|
||||||
@@ -20,6 +29,7 @@ const stockLogTableColumns: ColumnDef<StockLog>[] = [
|
|||||||
{
|
{
|
||||||
header: 'Gudang',
|
header: 'Gudang',
|
||||||
accessorKey: 'warehouse_name',
|
accessorKey: 'warehouse_name',
|
||||||
|
cell: warehouseName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Stock Akhir',
|
header: 'Stock Akhir',
|
||||||
@@ -65,31 +75,77 @@ const stockLogTableColumns: ColumnDef<StockLog>[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const StockLogTable = ({
|
const StockLogTable = ({
|
||||||
stockLogs,
|
productWarehouse,
|
||||||
}: {
|
}: {
|
||||||
stockLogs: (StockLog & { warehouse_name: string; warehouse_id: number })[];
|
productWarehouse: ProductWarehouseStock;
|
||||||
}) => {
|
}) => {
|
||||||
const { state: tableFilterState, setPage, setPageSize } = useTableFilter();
|
const [isExportLoading, setIsExportLoading] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
state: tableFilterState,
|
||||||
|
setPage,
|
||||||
|
setPageSize,
|
||||||
|
toQueryString: getTableFilterQueryString,
|
||||||
|
} = useTableFilter({
|
||||||
|
initial: {
|
||||||
|
product_warehouse_id: productWarehouse.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleExportExcel = async () => {
|
||||||
|
setIsExportLoading(true);
|
||||||
|
try {
|
||||||
|
await StockLogApi.exportToExcel(
|
||||||
|
productWarehouse.warehouse_name,
|
||||||
|
getTableFilterQueryString()
|
||||||
|
);
|
||||||
|
toast.success('Excel berhasil dibuat dan diunduh.');
|
||||||
|
} catch {
|
||||||
|
toast.error('Gagal membuat Excel. Silakan coba lagi.');
|
||||||
|
} finally {
|
||||||
|
setIsExportLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data: stockLogsResponse, isLoading: isLoadingStockLogs } = useSWR(
|
||||||
|
`${StockLogApi.basePath}${getTableFilterQueryString()}`,
|
||||||
|
StockLogApi.getAllFetcher
|
||||||
|
);
|
||||||
|
|
||||||
|
const stockLogs = isResponseSuccess(stockLogsResponse)
|
||||||
|
? stockLogsResponse.data
|
||||||
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title='Informasi Stock Produk'
|
title={`Informasi Stock Produk - ${productWarehouse.warehouse_name}`}
|
||||||
collapsible
|
collapsible
|
||||||
variant='bordered'
|
variant='bordered'
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'w-full',
|
wrapper: 'w-full',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div className='flex justify-end px-6 pt-4'>
|
||||||
|
<Button onClick={handleExportExcel} isLoading={isExportLoading}>
|
||||||
|
<FileDown size={16} />
|
||||||
|
Export Excel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<Table<StockLog>
|
<Table<StockLog>
|
||||||
data={stockLogs}
|
data={stockLogs}
|
||||||
columns={stockLogTableColumns}
|
columns={stockLogTableColumns(productWarehouse.warehouse_name)}
|
||||||
page={tableFilterState.page ?? 0}
|
page={tableFilterState.page ?? 0}
|
||||||
pageSize={tableFilterState.pageSize}
|
pageSize={tableFilterState.pageSize}
|
||||||
onPageChange={setPage}
|
onPageChange={setPage}
|
||||||
onPageSizeChange={setPageSize}
|
onPageSizeChange={setPageSize}
|
||||||
totalItems={stockLogs?.length ?? 0}
|
isLoading={isLoadingStockLogs}
|
||||||
|
totalItems={
|
||||||
|
isResponseSuccess(stockLogsResponse)
|
||||||
|
? stockLogsResponse.meta?.total_results
|
||||||
|
: 0
|
||||||
|
}
|
||||||
className={{
|
className={{
|
||||||
containerClassName: 'mt-6',
|
containerClassName: 'mt-4 mb-0',
|
||||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||||
headerRowClassName: 'border-b border-b-gray-200',
|
headerRowClassName: 'border-b border-b-gray-200',
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import {
|
|||||||
CreateInventoryAdjustmentPayload,
|
CreateInventoryAdjustmentPayload,
|
||||||
InventoryAdjustment,
|
InventoryAdjustment,
|
||||||
} from '@/types/api/inventory/adjustment';
|
} from '@/types/api/inventory/adjustment';
|
||||||
import { InventoryProduct } from '@/types/api/inventory/product';
|
import { InventoryProduct, StockLog } from '@/types/api/inventory/product';
|
||||||
|
import { httpClient } from '../http/client';
|
||||||
|
import { formatDate } from '@/lib/helper';
|
||||||
|
|
||||||
export const ProductWarehouseApi = new BaseApiService<
|
export const ProductWarehouseApi = new BaseApiService<
|
||||||
ProductWarehouse,
|
ProductWarehouse,
|
||||||
@@ -65,3 +67,41 @@ export const InventoryProductApi = new BaseApiService<
|
|||||||
unknown,
|
unknown,
|
||||||
unknown
|
unknown
|
||||||
>('/inventory/product-stocks');
|
>('/inventory/product-stocks');
|
||||||
|
|
||||||
|
export class StockLogService extends BaseApiService<
|
||||||
|
StockLog,
|
||||||
|
unknown,
|
||||||
|
unknown
|
||||||
|
> {
|
||||||
|
constructor(basePath: string = '/inventory/stock-logs') {
|
||||||
|
super(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportToExcel(warehouseName: string, initialQueryString: string) {
|
||||||
|
const params = new URLSearchParams(initialQueryString);
|
||||||
|
|
||||||
|
params.set('export', 'excel');
|
||||||
|
params.set('page', '1');
|
||||||
|
params.set('limit', '99999999999');
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
|
||||||
|
method: 'GET',
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
|
||||||
|
const fileName = `informasi-stok-produk-${warehouseName.toLowerCase().replaceAll(' ', '-')}-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`;
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StockLogApi = new StockLogService('/inventory/stock-logs');
|
||||||
|
|||||||
Reference in New Issue
Block a user