diff --git a/src/components/pages/inventory/product/detail/InventoryProductDetail.tsx b/src/components/pages/inventory/product/detail/InventoryProductDetail.tsx
index 39609b06..715a7a43 100644
--- a/src/components/pages/inventory/product/detail/InventoryProductDetail.tsx
+++ b/src/components/pages/inventory/product/detail/InventoryProductDetail.tsx
@@ -1,5 +1,6 @@
import Card from '@/components/Card';
import { FormHeader } from '@/components/helper/form/FormHeader';
+import RequirePermission from '@/components/helper/RequirePermission';
import StockLogTable from '@/components/pages/inventory/product/detail/StockLogTable';
import StockProductWarehouseTable from '@/components/pages/inventory/product/detail/StockProductWarehouseTable';
import { formatCurrency, formatNumber } from '@/lib/helper';
@@ -11,18 +12,6 @@ const InventoryProductDetail = ({
}: {
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 (
-
+
+ {inventoryProduct?.product_warehouses?.map((productWarehouse) => (
+
+ ))}
+
);
};
diff --git a/src/components/pages/inventory/product/detail/StockLogTable.tsx b/src/components/pages/inventory/product/detail/StockLogTable.tsx
index a8240952..b92a4512 100644
--- a/src/components/pages/inventory/product/detail/StockLogTable.tsx
+++ b/src/components/pages/inventory/product/detail/StockLogTable.tsx
@@ -1,11 +1,20 @@
+import Button from '@/components/Button';
import Card from '@/components/Card';
import Table from '@/components/Table';
+import { isResponseSuccess } from '@/lib/api-helper';
import { formatDate, formatNumber, formatTitleCase } from '@/lib/helper';
+import { StockLogApi } from '@/services/api/inventory';
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 { FileDown } from 'lucide-react';
+import toast from 'react-hot-toast';
+import { useState } from 'react';
+import useSWR from 'swr';
-const stockLogTableColumns: ColumnDef[] = [
+const stockLogTableColumns: (warehouseName: string) => ColumnDef[] = (
+ warehouseName
+) => [
{
header: 'ID',
accessorKey: 'id',
@@ -20,6 +29,7 @@ const stockLogTableColumns: ColumnDef[] = [
{
header: 'Gudang',
accessorKey: 'warehouse_name',
+ cell: warehouseName,
},
{
header: 'Stock Akhir',
@@ -65,31 +75,77 @@ const stockLogTableColumns: ColumnDef[] = [
];
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 (
+
+
+
data={stockLogs}
- columns={stockLogTableColumns}
+ columns={stockLogTableColumns(productWarehouse.warehouse_name)}
page={tableFilterState.page ?? 0}
pageSize={tableFilterState.pageSize}
onPageChange={setPage}
onPageSizeChange={setPageSize}
- totalItems={stockLogs?.length ?? 0}
+ isLoading={isLoadingStockLogs}
+ totalItems={
+ isResponseSuccess(stockLogsResponse)
+ ? stockLogsResponse.meta?.total_results
+ : 0
+ }
className={{
- containerClassName: 'mt-6',
+ containerClassName: 'mt-4 mb-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',
diff --git a/src/components/pages/inventory/product/detail/StockProductWarehouseTable.tsx b/src/components/pages/inventory/product/detail/StockProductWarehouseTable.tsx
index 4d361c5c..8b36ee4b 100644
--- a/src/components/pages/inventory/product/detail/StockProductWarehouseTable.tsx
+++ b/src/components/pages/inventory/product/detail/StockProductWarehouseTable.tsx
@@ -55,7 +55,7 @@ const StockProductWarehouseTable = ({
onPageChange={setPage}
onPageSizeChange={setPageSize}
className={{
- containerClassName: 'mt-6',
+ containerClassName: 'mt-6 mb-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',
diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
index ad59cfb0..55cf08f3 100644
--- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
+++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
@@ -732,7 +732,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
)}
{!isLoading && data.length > 0 && meta && (
-
+
{
)}
{!isLoading && data.length > 0 && meta && (
-
+
('/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(`${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');
diff --git a/src/services/api/marketing/marketing.ts b/src/services/api/marketing/marketing.ts
index 923f9724..2cd225a5 100644
--- a/src/services/api/marketing/marketing.ts
+++ b/src/services/api/marketing/marketing.ts
@@ -1,6 +1,5 @@
-import { isResponseError } from '@/lib/api-helper';
import { BaseApiService } from '@/services/api/base';
-import { httpClient, httpClientFetcher } from '@/services/http/client';
+import { httpClient } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general';
import axios from 'axios';
import {
@@ -11,9 +10,8 @@ import {
CreateDeliveryOrderPayload,
UpdateDeliveryOrderPayload,
} from '@/types/api/marketing/marketing';
-import toast from 'react-hot-toast';
-import * as XLSX from 'xlsx';
-import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
+
+import { formatDate } from '@/lib/helper';
/**
* 💡 Helper untuk membuat respons dummy
diff --git a/src/services/hooks/useTableFilter.tsx b/src/services/hooks/useTableFilter.tsx
index ad9c6679..acc1dff7 100644
--- a/src/services/hooks/useTableFilter.tsx
+++ b/src/services/hooks/useTableFilter.tsx
@@ -249,6 +249,9 @@ export function useTableFilter<
const mapKey = useCallback(
(key: string) => {
const m = options?.paramMap as Record | undefined;
+
+ if (key === 'pageSize' && ((m && !m[key]) || !m)) return 'limit';
+
return (m && m[key]) || key;
},
[options?.paramMap]