mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
feat: add Excel export to BalanceMonitoringTab
Add exportBalanceMonitoringToExcel to FinanceApiService (server-side blob download hitting reports/balance-monitoring?export=excel). Wire it into BalanceMonitoringTab via a Dropdown export button in the tab actions. Wrap the handler in useCallback to prevent an infinite setTabActions loop caused by a new function reference on every render. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user