mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat: create BalanceMonitoringTab component
This commit is contained in:
@@ -0,0 +1,665 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { useFormik } from 'formik';
|
||||
import toast from 'react-hot-toast';
|
||||
import { ColumnDef, SortingState, Updater } from '@tanstack/react-table';
|
||||
import { FinanceApi } from '@/services/api/report/finance-report';
|
||||
import { CustomerApi } from '@/services/api/master-data';
|
||||
import { UserApi } from '@/services/api/user';
|
||||
import SelectInput, {
|
||||
useSelect,
|
||||
OptionType,
|
||||
} from '@/components/input/SelectInput';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||
import { formatCurrency, formatNumber } from '@/lib/helper';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { BalanceMonitoringRow } from '@/types/api/report/balance-monitoring';
|
||||
import { CustomerPaymentRow } from '@/types/api/report/customer-payment';
|
||||
import Modal, { useModal } from '@/components/Modal';
|
||||
import Button from '@/components/Button';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import Table from '@/components/Table';
|
||||
import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton';
|
||||
|
||||
interface BalanceMonitoringTabProps {
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
||||
const [isPdfExportLoading, setIsPdfExportLoading] = useState(false);
|
||||
const [hasDateError, setHasDateError] = useState(false);
|
||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||
|
||||
const handleFilterModalOpenRef = useRef(() => {});
|
||||
const filterModal = useModal();
|
||||
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
|
||||
|
||||
const {
|
||||
state: tableFilterState,
|
||||
updateFilter,
|
||||
setPage,
|
||||
setPageSize,
|
||||
toQueryString,
|
||||
} = useTableFilter<{
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
customerFilter?: OptionType<number>;
|
||||
salesFilter?: OptionType<number>;
|
||||
sort_by: string;
|
||||
order_by: string;
|
||||
}>({
|
||||
initial: {
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
customerFilter: undefined,
|
||||
salesFilter: undefined,
|
||||
sort_by: '',
|
||||
order_by: '',
|
||||
},
|
||||
paramMap: {
|
||||
page: 'page',
|
||||
pageSize: 'limit',
|
||||
start_date: 'start_date',
|
||||
end_date: 'end_date',
|
||||
customerFilter: 'customer_id',
|
||||
salesFilter: 'sales_id',
|
||||
sort_by: 'sort_by',
|
||||
order_by: 'sort_order',
|
||||
},
|
||||
persist: true,
|
||||
storeName: 'balance-monitoring-table',
|
||||
});
|
||||
|
||||
// Keep a stable ref so handleExportPDF doesn't need toQueryString as a dep
|
||||
const toQueryStringRef = useRef(toQueryString);
|
||||
useEffect(() => {
|
||||
toQueryStringRef.current = toQueryString;
|
||||
});
|
||||
|
||||
const sorting: SortingState = tableFilterState.sort_by
|
||||
? [
|
||||
{
|
||||
id: tableFilterState.sort_by,
|
||||
desc: tableFilterState.order_by === 'desc',
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const handleSortingChange = (updater: Updater<SortingState>) => {
|
||||
const next = typeof updater === 'function' ? updater(sorting) : updater;
|
||||
if (next.length > 0) {
|
||||
updateFilter('sort_by', next[0].id, true);
|
||||
updateFilter('order_by', next[0].desc ? 'desc' : 'asc', true);
|
||||
} else {
|
||||
updateFilter('sort_by', '', true);
|
||||
updateFilter('order_by', '', true);
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
options: customerOptions,
|
||||
setInputValue: setCustomerInput,
|
||||
isLoadingOptions: isLoadingCustomers,
|
||||
loadMore: loadMoreCustomers,
|
||||
} = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const {
|
||||
options: salesOptions,
|
||||
setInputValue: setSalesInput,
|
||||
isLoadingOptions: isLoadingSales,
|
||||
loadMore: loadMoreSales,
|
||||
} = useSelect(UserApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
start_date: tableFilterState.start_date,
|
||||
end_date: tableFilterState.end_date,
|
||||
customerFilter: tableFilterState.customerFilter,
|
||||
salesFilter: tableFilterState.salesFilter,
|
||||
},
|
||||
enableReinitialize: true,
|
||||
onSubmit: (values) => {
|
||||
updateFilter('start_date', values.start_date, true);
|
||||
updateFilter('end_date', values.end_date, true);
|
||||
updateFilter('customerFilter', values.customerFilter, true);
|
||||
updateFilter('salesFilter', values.salesFilter, true);
|
||||
filterModal.closeModal();
|
||||
},
|
||||
onReset: () => {
|
||||
updateFilter('start_date', '', true);
|
||||
updateFilter('end_date', '', true);
|
||||
updateFilter('customerFilter', undefined, true);
|
||||
updateFilter('salesFilter', undefined, true);
|
||||
setHasDateError(false);
|
||||
if (dateErrorShown) {
|
||||
toast.dismiss();
|
||||
setDateErrorShown(false);
|
||||
}
|
||||
filterModal.closeModal();
|
||||
},
|
||||
});
|
||||
|
||||
handleFilterModalOpenRef.current = () => {
|
||||
filterModal.openModal();
|
||||
};
|
||||
|
||||
const handleStartDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
formik.setFieldValue('start_date', value);
|
||||
|
||||
if (value && formik.values.end_date) {
|
||||
if (new Date(formik.values.end_date) < new Date(value)) {
|
||||
setHasDateError(true);
|
||||
if (!dateErrorShown) {
|
||||
toast.error('Tanggal akhir tidak boleh masa lampau', {
|
||||
duration: Infinity,
|
||||
});
|
||||
setDateErrorShown(true);
|
||||
}
|
||||
} else {
|
||||
setHasDateError(false);
|
||||
if (dateErrorShown) {
|
||||
toast.dismiss();
|
||||
setDateErrorShown(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setHasDateError(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEndDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
formik.setFieldValue('end_date', value);
|
||||
|
||||
if (value && formik.values.start_date) {
|
||||
if (new Date(value) < new Date(formik.values.start_date)) {
|
||||
setHasDateError(true);
|
||||
if (!dateErrorShown) {
|
||||
toast.error('Tanggal akhir tidak boleh masa lampau', {
|
||||
duration: Infinity,
|
||||
});
|
||||
setDateErrorShown(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setHasDateError(false);
|
||||
if (dateErrorShown) {
|
||||
toast.dismiss();
|
||||
setDateErrorShown(false);
|
||||
}
|
||||
};
|
||||
|
||||
const queryString = toQueryString();
|
||||
|
||||
const { data: response, isLoading } = useSWR(queryString, (qs) =>
|
||||
FinanceApi.getBalanceMonitoringReport(
|
||||
Object.fromEntries(new URLSearchParams(qs)) as Parameters<
|
||||
typeof FinanceApi.getBalanceMonitoringReport
|
||||
>[0]
|
||||
)
|
||||
);
|
||||
|
||||
const data: BalanceMonitoringRow[] = useMemo(
|
||||
() =>
|
||||
isResponseSuccess(response)
|
||||
? ((response.data as BalanceMonitoringRow[]) ?? [])
|
||||
: [],
|
||||
[response]
|
||||
);
|
||||
|
||||
const meta = useMemo(
|
||||
() => (isResponseSuccess(response) && response.meta ? response.meta : null),
|
||||
[response]
|
||||
);
|
||||
|
||||
// Stable — uses ref so toQueryString is always current without being a dep
|
||||
const handleExportPDF = useCallback(async () => {
|
||||
setIsPdfExportLoading(true);
|
||||
try {
|
||||
await FinanceApi.exportBalanceMonitoringToPDF(toQueryStringRef.current());
|
||||
toast.success('PDF berhasil dibuat dan diunduh.');
|
||||
} catch {
|
||||
toast.error('Gagal membuat PDF. Silakan coba lagi.');
|
||||
} finally {
|
||||
setIsPdfExportLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Inject tab actions directly — no nested component, no remount cycle
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={{
|
||||
start_date: tableFilterState.start_date,
|
||||
end_date: tableFilterState.end_date,
|
||||
customerFilter: tableFilterState.customerFilter,
|
||||
salesFilter: tableFilterState.salesFilter,
|
||||
}}
|
||||
fieldGroups={[['start_date', 'end_date']]}
|
||||
onClick={() => handleFilterModalOpenRef.current()}
|
||||
variant='outline'
|
||||
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={isPdfExportLoading}
|
||||
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={handleExportPDF}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
tabId,
|
||||
setTabActions,
|
||||
isPdfExportLoading,
|
||||
handleExportPDF,
|
||||
tableFilterState.start_date,
|
||||
tableFilterState.end_date,
|
||||
tableFilterState.customerFilter,
|
||||
tableFilterState.salesFilter,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => clearTabActions(tabId);
|
||||
}, [tabId, clearTabActions]);
|
||||
|
||||
const page = meta?.page || tableFilterState.page;
|
||||
const pageSize = meta?.limit || tableFilterState.pageSize;
|
||||
|
||||
const columns = useMemo(
|
||||
(): ColumnDef<BalanceMonitoringRow>[] => [
|
||||
{
|
||||
header: 'No',
|
||||
enableSorting: false,
|
||||
cell: (props) => (page - 1) * pageSize + props.row.index + 1,
|
||||
},
|
||||
{
|
||||
header: 'Customer',
|
||||
accessorKey: 'customer_name',
|
||||
enableSorting: true,
|
||||
id: 'customer_name',
|
||||
},
|
||||
{
|
||||
header: 'Saldo Awal',
|
||||
accessorKey: 'saldo_awal',
|
||||
id: 'saldo_awal',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-right'>
|
||||
{formatCurrency(row.original.saldo_awal)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Penjualan Ayam',
|
||||
columns: [
|
||||
{
|
||||
header: 'Ekor',
|
||||
accessorKey: 'penjualan_ayam_ekor',
|
||||
id: 'penjualan_ayam_ekor',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-right'>
|
||||
{formatNumber(row.original.penjualan_ayam_ekor)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Kg',
|
||||
accessorKey: 'penjualan_ayam_kg',
|
||||
id: 'penjualan_ayam_kg',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-right'>
|
||||
{formatNumber(row.original.penjualan_ayam_kg)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Nominal',
|
||||
accessorKey: 'penjualan_ayam_nominal',
|
||||
id: 'penjualan_ayam_nominal',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-right'>
|
||||
{formatCurrency(row.original.penjualan_ayam_nominal)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Penjualan Telur',
|
||||
columns: [
|
||||
{
|
||||
header: 'Kuantitas',
|
||||
accessorKey: 'penjualan_telur_kuantitas',
|
||||
id: 'penjualan_telur_kuantitas',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-right'>
|
||||
{formatNumber(row.original.penjualan_telur_kuantitas)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Kg',
|
||||
accessorKey: 'penjualan_telur_kg',
|
||||
id: 'penjualan_telur_kg',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-right'>
|
||||
{formatNumber(row.original.penjualan_telur_kg)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Nominal',
|
||||
accessorKey: 'penjualan_telur_nominal',
|
||||
id: 'penjualan_telur_nominal',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-right'>
|
||||
{formatCurrency(row.original.penjualan_telur_nominal)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Penjualan Trading',
|
||||
accessorKey: 'penjualan_trading',
|
||||
id: 'penjualan_trading',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-right'>
|
||||
{formatCurrency(row.original.penjualan_trading)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Pembayaran',
|
||||
accessorKey: 'pembayaran',
|
||||
id: 'pembayaran',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-right'>
|
||||
{formatCurrency(row.original.pembayaran)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Aging',
|
||||
accessorKey: 'aging',
|
||||
id: 'aging',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-center'>
|
||||
{formatNumber(row.original.aging)} hari
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Aging Rata-Rata',
|
||||
accessorKey: 'aging_rata_rata',
|
||||
id: 'aging_rata_rata',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div className='text-center'>
|
||||
{formatNumber(row.original.aging_rata_rata)} hari
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Saldo Akhir',
|
||||
accessorKey: 'saldo_akhir',
|
||||
id: 'saldo_akhir',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => (
|
||||
<div
|
||||
className={`text-right font-semibold ${row.original.saldo_akhir < 0 ? 'text-error' : ''}`}
|
||||
>
|
||||
{formatCurrency(row.original.saldo_akhir)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
[page, pageSize]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||
{isLoading && (
|
||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||
<span className='loading loading-spinner loading-xl' />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && data.length === 0 && (
|
||||
<CustomerSupplierSkeleton
|
||||
columns={columns as unknown as ColumnDef<CustomerPaymentRow>[]}
|
||||
icon={
|
||||
<Icon
|
||||
icon='heroicons:chart-bar'
|
||||
className='text-white'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
}
|
||||
title='Data Not Yet Available'
|
||||
subtitle='Please change your filters to get the data.'
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isLoading && data.length > 0 && (
|
||||
<>
|
||||
<div className='w-full overflow-x-auto'>
|
||||
<Table
|
||||
data={data}
|
||||
columns={columns}
|
||||
pageSize={pageSize}
|
||||
page={meta?.page || 1}
|
||||
totalItems={meta?.total_results || 0}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
sorting={sorting}
|
||||
setSorting={handleSortingChange}
|
||||
manualSorting
|
||||
className={{
|
||||
containerClassName: 'w-full mb-0!',
|
||||
tableWrapperClassName: 'overflow-x-auto',
|
||||
tableClassName: 'w-full table-auto text-sm',
|
||||
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
|
||||
headerColumnClassName:
|
||||
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200 text-nowrap',
|
||||
bodyRowClassName:
|
||||
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
|
||||
bodyColumnClassName:
|
||||
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||||
paginationClassName: 'hidden',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{meta && (
|
||||
<div className='mt-5 px-3'>
|
||||
<Pagination
|
||||
totalItems={meta.total_results || 0}
|
||||
itemsPerPage={meta.limit || 0}
|
||||
currentPage={meta.page || 0}
|
||||
onPrevPage={() => setPage(Math.max(1, (meta.page || 1) - 1))}
|
||||
onNextPage={() =>
|
||||
setPage(
|
||||
meta.total_pages && (meta.page || 1) < meta.total_pages
|
||||
? (meta.page || 1) + 1
|
||||
: meta.page || 1
|
||||
)
|
||||
}
|
||||
onPageChange={setPage}
|
||||
rowOptions={[10, 20, 50, 100]}
|
||||
onRowChange={setPageSize}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Filter Modal */}
|
||||
<Modal
|
||||
ref={filterModal.ref}
|
||||
className={{
|
||||
modal: 'p-0',
|
||||
modalBox: 'p-0 rounded-[0.875rem] xl:max-w-4/12 max-w-sm',
|
||||
}}
|
||||
>
|
||||
{/* Modal Header */}
|
||||
<div className='flex items-center justify-between gap-2 border-b border-base-content/10 p-4'>
|
||||
<div className='flex items-center gap-2 text-primary'>
|
||||
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||||
<h3 className='font-medium text-sm'>Filter Data</h3>
|
||||
</div>
|
||||
<Button
|
||||
variant='link'
|
||||
onClick={filterModal.closeModal}
|
||||
className='text-base-content/50 hover:text-base-content transition-colors cursor-pointer'
|
||||
>
|
||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={formik.handleSubmit} onReset={formik.handleReset}>
|
||||
<div className='p-4 flex flex-col gap-3'>
|
||||
<div>
|
||||
<label className='block text-xs font-semibold text-base-content py-2'>
|
||||
Tanggal
|
||||
</label>
|
||||
<div className='flex flex-row gap-1.5 items-center justify-between'>
|
||||
<DateInput
|
||||
name='start_date'
|
||||
value={formik.values.start_date || ''}
|
||||
onChange={handleStartDateChange}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
isNestedModal
|
||||
/>
|
||||
<hr className='w-full max-w-3 h-px border-base-content/10' />
|
||||
<DateInput
|
||||
name='end_date'
|
||||
value={formik.values.end_date || ''}
|
||||
onChange={handleEndDateChange}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
isNestedModal
|
||||
isError={hasDateError}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SelectInput
|
||||
label='Customer'
|
||||
placeholder='Pilih Customer'
|
||||
options={customerOptions}
|
||||
value={formik.values.customerFilter ?? null}
|
||||
onChange={(val) =>
|
||||
formik.setFieldValue(
|
||||
'customerFilter',
|
||||
val as OptionType<number> | null
|
||||
)
|
||||
}
|
||||
onInputChange={setCustomerInput}
|
||||
isLoading={isLoadingCustomers}
|
||||
isClearable
|
||||
onMenuScrollToBottom={loadMoreCustomers}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
label='Sales'
|
||||
placeholder='Pilih Sales'
|
||||
options={salesOptions}
|
||||
value={formik.values.salesFilter ?? null}
|
||||
onChange={(val) =>
|
||||
formik.setFieldValue(
|
||||
'salesFilter',
|
||||
val as OptionType<number> | null
|
||||
)
|
||||
}
|
||||
onInputChange={setSalesInput}
|
||||
isLoading={isLoadingSales}
|
||||
isClearable
|
||||
onMenuScrollToBottom={loadMoreSales}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'>
|
||||
<Button
|
||||
type='reset'
|
||||
variant='soft'
|
||||
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
|
||||
>
|
||||
Reset Filter
|
||||
</Button>
|
||||
<Button
|
||||
type='submit'
|
||||
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
|
||||
disabled={hasDateError}
|
||||
>
|
||||
Apply Filter
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BalanceMonitoringTab;
|
||||
Reference in New Issue
Block a user