mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
Merge branch 'development' into 'production'
feat: add server-side Excel export to PurchasesPerSupplierTab See merge request mbugroup/lti-web-client!496
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useMemo, useEffect } from 'react';
|
import { useState, useMemo, useEffect, useCallback } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
@@ -24,6 +24,7 @@ import { BalanceMonitoringRow } from '@/types/api/report/balance-monitoring';
|
|||||||
import { CustomerPaymentRow } from '@/types/api/report/customer-payment';
|
import { CustomerPaymentRow } from '@/types/api/report/customer-payment';
|
||||||
import Modal, { useModal } from '@/components/Modal';
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import Dropdown from '@/components/Dropdown';
|
||||||
import DateInput from '@/components/input/DateInput';
|
import DateInput from '@/components/input/DateInput';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton';
|
import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton';
|
||||||
@@ -40,6 +41,7 @@ const filterByOptions: OptionType<string>[] = [
|
|||||||
const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
||||||
const [hasDateError, setHasDateError] = useState(false);
|
const [hasDateError, setHasDateError] = useState(false);
|
||||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
|
const [isExcelExportLoading, setIsExcelExportLoading] = useState(false);
|
||||||
|
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
@@ -230,6 +232,33 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
? balanceMonitoringsResponse.meta
|
? balanceMonitoringsResponse.meta
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const handleExportExcel = useCallback(async () => {
|
||||||
|
setIsExcelExportLoading(true);
|
||||||
|
try {
|
||||||
|
const customer_ids =
|
||||||
|
tableFilterState.customers.length > 0
|
||||||
|
? tableFilterState.customers.map((o) => String(o.value)).join(',')
|
||||||
|
: undefined;
|
||||||
|
const sales_ids =
|
||||||
|
tableFilterState.salesPersons.length > 0
|
||||||
|
? tableFilterState.salesPersons.map((o) => String(o.value)).join(',')
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
await FinanceApi.exportBalanceMonitoringToExcel(
|
||||||
|
customer_ids,
|
||||||
|
sales_ids,
|
||||||
|
tableFilterState.filterBy?.value,
|
||||||
|
tableFilterState.start_date || undefined,
|
||||||
|
tableFilterState.end_date || undefined
|
||||||
|
);
|
||||||
|
toast.success('Excel berhasil dibuat dan diunduh.');
|
||||||
|
} catch {
|
||||||
|
toast.error('Gagal membuat Excel. Silakan coba lagi.');
|
||||||
|
} finally {
|
||||||
|
setIsExcelExportLoading(false);
|
||||||
|
}
|
||||||
|
}, [tableFilterState]);
|
||||||
|
|
||||||
// Inject tab actions directly — no nested component, no remount cycle
|
// Inject tab actions directly — no nested component, no remount cycle
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabActions(
|
setTabActions(
|
||||||
@@ -248,9 +277,55 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
variant='outline'
|
variant='outline'
|
||||||
className='px-3 py-2.5'
|
className='px-3 py-2.5'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
align='end'
|
||||||
|
direction='bottom'
|
||||||
|
className={{
|
||||||
|
content:
|
||||||
|
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||||
|
}}
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='none'
|
||||||
|
isLoading={isExcelExportLoading}
|
||||||
|
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||||
|
>
|
||||||
|
<div className='flex flex-row items-center gap-1.5'>
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:cloud-arrow-down'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
<span>Export</span>
|
||||||
|
<div className='w-px self-stretch bg-base-content/10' />
|
||||||
|
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
onClick={handleExportExcel}
|
||||||
|
isLoading={isExcelExportLoading}
|
||||||
|
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||||
|
Ekspor ke Excel
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [tabId, setTabActions, tableFilterState, filterModal.openModal]);
|
}, [
|
||||||
|
tabId,
|
||||||
|
setTabActions,
|
||||||
|
tableFilterState,
|
||||||
|
filterModal.openModal,
|
||||||
|
isExcelExportLoading,
|
||||||
|
handleExportExcel,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => clearTabActions(tabId);
|
return () => clearTabActions(tabId);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
LogisticPurchasePerSupplierReport,
|
LogisticPurchasePerSupplierReport,
|
||||||
LogisticPurchasePerSupplierSummary,
|
LogisticPurchasePerSupplierSummary,
|
||||||
} from '@/types/api/report/logistic-stock';
|
} from '@/types/api/report/logistic-stock';
|
||||||
import { generatePurchasesPerSupplierExcel } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExportXLSX';
|
|
||||||
import { generatePurchasesPerSupplierPDF } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExportPDF';
|
import { generatePurchasesPerSupplierPDF } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExportPDF';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
@@ -53,7 +52,10 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
// ===== STATE MANAGEMENT =====
|
// ===== STATE MANAGEMENT =====
|
||||||
const [isPdfExportLoading, setIsPdfExportLoading] = useState(false);
|
const [isPdfExportLoading, setIsPdfExportLoading] = useState(false);
|
||||||
const [isExcelExportLoading, setIsExcelExportLoading] = useState(false);
|
const [isExcelExportLoading, setIsExcelExportLoading] = useState(false);
|
||||||
const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading;
|
const [isExcelGeneralExportLoading, setIsExcelGeneralExportLoading] =
|
||||||
|
useState(false);
|
||||||
|
const isAnyExportLoading =
|
||||||
|
isPdfExportLoading || isExcelExportLoading || isExcelGeneralExportLoading;
|
||||||
|
|
||||||
// ===== PAGINATION STATE =====
|
// ===== PAGINATION STATE =====
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
@@ -360,25 +362,44 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
const handleExportExcel = useCallback(async () => {
|
const handleExportExcel = useCallback(async () => {
|
||||||
setIsExcelExportLoading(true);
|
setIsExcelExportLoading(true);
|
||||||
try {
|
try {
|
||||||
const allDataForExport = await logisticPurchasePerSupplierExport();
|
await LogisticApi.exportToExcelSupplierPerSheet(
|
||||||
|
filterParams.area_id,
|
||||||
if (
|
filterParams.supplier_id,
|
||||||
!allDataForExport ||
|
filterParams.product_id,
|
||||||
!Array.isArray(allDataForExport) ||
|
filterParams.product_category_id,
|
||||||
allDataForExport.length === 0
|
filterParams.start_date,
|
||||||
) {
|
filterParams.end_date,
|
||||||
toast.error('Tidak ada data untuk diekspor.');
|
filterParams.sort_by,
|
||||||
return;
|
filterParams.filter_by
|
||||||
}
|
);
|
||||||
|
|
||||||
await generatePurchasesPerSupplierExcel({ data: allDataForExport });
|
|
||||||
toast.success('Excel berhasil dibuat dan diunduh.');
|
toast.success('Excel berhasil dibuat dan diunduh.');
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Gagal membuat Excel. Silakan coba lagi.');
|
toast.error('Gagal membuat Excel. Silakan coba lagi.');
|
||||||
} finally {
|
} finally {
|
||||||
setIsExcelExportLoading(false);
|
setIsExcelExportLoading(false);
|
||||||
}
|
}
|
||||||
}, [logisticPurchasePerSupplierExport]);
|
}, [filterParams]);
|
||||||
|
|
||||||
|
const handleExportExcelGeneral = useCallback(async () => {
|
||||||
|
setIsExcelGeneralExportLoading(true);
|
||||||
|
try {
|
||||||
|
await LogisticApi.exportToExcelGeneral(
|
||||||
|
filterParams.area_id,
|
||||||
|
filterParams.supplier_id,
|
||||||
|
filterParams.product_id,
|
||||||
|
filterParams.product_category_id,
|
||||||
|
filterParams.start_date,
|
||||||
|
filterParams.end_date,
|
||||||
|
filterParams.sort_by,
|
||||||
|
filterParams.filter_by
|
||||||
|
);
|
||||||
|
toast.success('Excel General berhasil dibuat dan diunduh.');
|
||||||
|
} catch {
|
||||||
|
toast.error('Gagal membuat Excel General. Silakan coba lagi.');
|
||||||
|
} finally {
|
||||||
|
setIsExcelGeneralExportLoading(false);
|
||||||
|
}
|
||||||
|
}, [filterParams]);
|
||||||
|
|
||||||
const handleExportPdf = useCallback(async () => {
|
const handleExportPdf = useCallback(async () => {
|
||||||
setIsPdfExportLoading(true);
|
setIsPdfExportLoading(true);
|
||||||
@@ -523,7 +544,17 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||||
>
|
>
|
||||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||||
Export to Excel
|
Export to Excel - Supplier Per Sheet
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
onClick={handleExportExcelGeneral}
|
||||||
|
isLoading={isExcelGeneralExportLoading}
|
||||||
|
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||||
|
Export to Excel - General
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
@@ -553,8 +584,10 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
filterParams,
|
filterParams,
|
||||||
isAnyExportLoading,
|
isAnyExportLoading,
|
||||||
handleExportExcel,
|
handleExportExcel,
|
||||||
|
handleExportExcelGeneral,
|
||||||
handleExportPdf,
|
handleExportPdf,
|
||||||
isExcelExportLoading,
|
isExcelExportLoading,
|
||||||
|
isExcelGeneralExportLoading,
|
||||||
isPdfExportLoading,
|
isPdfExportLoading,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,40 @@ export class FinanceApiService extends BaseApiService<
|
|||||||
link.remove();
|
link.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exportBalanceMonitoringToExcel(
|
||||||
|
customer_ids?: string,
|
||||||
|
sales_ids?: string,
|
||||||
|
filter_by?: string,
|
||||||
|
start_date?: string,
|
||||||
|
end_date?: string
|
||||||
|
) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (customer_ids) params.set('customer_ids', customer_ids);
|
||||||
|
if (sales_ids) params.set('sales_ids', sales_ids);
|
||||||
|
if (filter_by) params.set('filter_by', filter_by);
|
||||||
|
if (start_date) params.set('start_date', start_date);
|
||||||
|
if (end_date) params.set('end_date', end_date);
|
||||||
|
params.set('export', 'excel');
|
||||||
|
params.set('page', '1');
|
||||||
|
params.set('limit', '9999999999');
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(
|
||||||
|
`${this.basePath}/balance-monitoring?${params.toString()}`,
|
||||||
|
{ method: 'GET', responseType: 'blob' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute(
|
||||||
|
'download',
|
||||||
|
`laporan-balance-monitoring-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`
|
||||||
|
);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
|
|
||||||
async getBalanceMonitoringReport(params: {
|
async getBalanceMonitoringReport(params: {
|
||||||
start_date?: string;
|
start_date?: string;
|
||||||
end_date?: string;
|
end_date?: string;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { BaseApiService } from '@/services/api/base';
|
import { BaseApiService } from '@/services/api/base';
|
||||||
|
import { httpClient } from '@/services/http/client';
|
||||||
|
import { formatDate } from '@/lib/helper';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import { LogisticPurchasePerSupplierReport } from '@/types/api/report/logistic-stock';
|
import { LogisticPurchasePerSupplierReport } from '@/types/api/report/logistic-stock';
|
||||||
|
|
||||||
@@ -11,6 +13,115 @@ export class LogisticApiService extends BaseApiService<
|
|||||||
super(basePath);
|
super(basePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildPurchaseSupplierParams(
|
||||||
|
area_id?: string,
|
||||||
|
supplier_id?: string,
|
||||||
|
product_id?: string,
|
||||||
|
product_category_id?: string,
|
||||||
|
start_date?: string,
|
||||||
|
end_date?: string,
|
||||||
|
sort_by?: string,
|
||||||
|
filter_by?: string
|
||||||
|
): URLSearchParams {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (area_id) params.set('area_id', area_id);
|
||||||
|
if (supplier_id) params.set('supplier_id', supplier_id);
|
||||||
|
if (product_id) params.set('product_id', product_id);
|
||||||
|
if (product_category_id)
|
||||||
|
params.set('product_category_id', product_category_id);
|
||||||
|
if (filter_by === 'received_date' && start_date)
|
||||||
|
params.set('received_date', start_date);
|
||||||
|
if (filter_by === 'po_date' && start_date)
|
||||||
|
params.set('po_date', start_date);
|
||||||
|
if (start_date) params.set('start_date', start_date);
|
||||||
|
if (end_date) params.set('end_date', end_date);
|
||||||
|
if (sort_by) params.set('sort_by', sort_by);
|
||||||
|
if (filter_by) params.set('filter_by', filter_by);
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportToExcelSupplierPerSheet(
|
||||||
|
area_id?: string,
|
||||||
|
supplier_id?: string,
|
||||||
|
product_id?: string,
|
||||||
|
product_category_id?: string,
|
||||||
|
start_date?: string,
|
||||||
|
end_date?: string,
|
||||||
|
sort_by?: string,
|
||||||
|
filter_by?: string
|
||||||
|
) {
|
||||||
|
const params = this.buildPurchaseSupplierParams(
|
||||||
|
area_id,
|
||||||
|
supplier_id,
|
||||||
|
product_id,
|
||||||
|
product_category_id,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
sort_by,
|
||||||
|
filter_by
|
||||||
|
);
|
||||||
|
params.set('export', 'excel');
|
||||||
|
params.set('page', '1');
|
||||||
|
params.set('limit', '99999999999');
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(
|
||||||
|
`${this.basePath.replace(/\/$/, '')}/purchase-supplier?${params.toString()}`,
|
||||||
|
{ method: 'GET', responseType: 'blob' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute(
|
||||||
|
'download',
|
||||||
|
`laporan-pembelian-per-supplier-per-sheet-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`
|
||||||
|
);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportToExcelGeneral(
|
||||||
|
area_id?: string,
|
||||||
|
supplier_id?: string,
|
||||||
|
product_id?: string,
|
||||||
|
product_category_id?: string,
|
||||||
|
start_date?: string,
|
||||||
|
end_date?: string,
|
||||||
|
sort_by?: string,
|
||||||
|
filter_by?: string
|
||||||
|
) {
|
||||||
|
const params = this.buildPurchaseSupplierParams(
|
||||||
|
area_id,
|
||||||
|
supplier_id,
|
||||||
|
product_id,
|
||||||
|
product_category_id,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
sort_by,
|
||||||
|
filter_by
|
||||||
|
);
|
||||||
|
params.set('export', 'excel-all');
|
||||||
|
params.set('page', '1');
|
||||||
|
params.set('limit', '99999999999');
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(
|
||||||
|
`${this.basePath.replace(/\/$/, '')}/purchase-supplier?${params.toString()}`,
|
||||||
|
{ method: 'GET', responseType: 'blob' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute(
|
||||||
|
'download',
|
||||||
|
`laporan-pembelian-per-supplier-general-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`
|
||||||
|
);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
|
|
||||||
async getLogisticPurchasePerSupplierReport(
|
async getLogisticPurchasePerSupplierReport(
|
||||||
area_id?: string,
|
area_id?: string,
|
||||||
supplier_id?: string,
|
supplier_id?: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user