refactor: optimize CustomerPaymentTab with useTableFilter persistence pattern

Replace filterParams/currentPage/pageSize state with useTableFilter (persist:true),
switch SWR to httpClientFetcher with explicit type, store OptionType[] directly for
customers/filterBy, add formikResetHandler using resetFilter(), remove enableReinitialize
and handleFilterModalOpenRef, pass filterModal.openModal directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ValdiANS
2026-05-20 16:10:10 +07:00
parent c98a51326f
commit b3b60018bb
@@ -1,14 +1,17 @@
import { useState, useMemo, useCallback, useEffect, useRef } from 'react'; import { useState, useCallback, useEffect } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { AxiosError } from 'axios';
import Card from '@/components/Card'; import Card from '@/components/Card';
import StatusBadge from '@/components/helper/StatusBadge'; import StatusBadge from '@/components/helper/StatusBadge';
import { useSelect } from '@/components/input/SelectInput'; import { useSelect, OptionType } from '@/components/input/SelectInput';
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
import SelectInputRadio from '@/components/input/SelectInputRadio'; import SelectInputRadio from '@/components/input/SelectInputRadio';
import DateInput from '@/components/input/DateInput'; import DateInput from '@/components/input/DateInput';
import { CustomerApi } from '@/services/api/master-data'; import { CustomerApi } from '@/services/api/master-data';
import { FinanceApi } from '@/services/api/report/finance-report'; import { FinanceApi } from '@/services/api/report/finance-report';
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general';
import Table from '@/components/Table'; import Table from '@/components/Table';
import { ColumnDef } from '@tanstack/react-table'; import { ColumnDef } from '@tanstack/react-table';
import { import {
@@ -27,28 +30,22 @@ import Dropdown from '@/components/Dropdown';
import Modal, { useModal } from '@/components/Modal'; import Modal, { useModal } from '@/components/Modal';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import {
CustomerPaymentFilterSchema,
CustomerPaymentFilterType,
} from '@/components/pages/report/finance/filter/CustomerPaymentFilter';
import { generateCustomerPaymentPDF } from '@/components/pages/report/finance/export/CustomerPaymentExportPDF'; import { generateCustomerPaymentPDF } from '@/components/pages/report/finance/export/CustomerPaymentExportPDF';
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store'; import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton'; import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton';
import { OptionType } from '@/components/table/TableRowSizeSelector';
import { Color } from '@/types/theme'; import { Color } from '@/types/theme';
import ButtonFilter from '@/components/helper/ButtonFilter'; import ButtonFilter from '@/components/helper/ButtonFilter';
import Pagination from '@/components/Pagination'; import Pagination from '@/components/Pagination';
import { useTableFilter } from '@/services/hooks/useTableFilter';
interface CustomerPaymentTabProps { interface CustomerPaymentTabProps {
tabId: string; tabId: string;
} }
interface FilterParams { const dataTypeOptions: OptionType<string>[] = [
customer_ids?: string; { value: 'trans_date', label: 'Tanggal Jual/Bayar' },
start_date?: string; { value: 'realization_date', label: 'Tanggal Realisasi' },
end_date?: string; ];
filter_by?: string;
}
const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => { const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
// ===== STATE MANAGEMENT ===== // ===== STATE MANAGEMENT =====
@@ -59,26 +56,44 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
const isAnyExportLoading = const isAnyExportLoading =
isPdfExportLoading || isExcelExportLoading || isExcelGeneralExportLoading; isPdfExportLoading || isExcelExportLoading || isExcelGeneralExportLoading;
// ===== PAGINATION STATE =====
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
// ===== SUBMISSION STATE =====
const [filterParams, setFilterParams] = useState<FilterParams>({});
const [dateErrorShown, setDateErrorShown] = useState(false); const [dateErrorShown, setDateErrorShown] = useState(false);
const [hasDateError, setHasDateError] = useState(false); const [hasDateError, setHasDateError] = useState(false);
const handleFilterModalOpenRef = useRef(() => {});
const filterModal = useModal(); const filterModal = useModal();
const dataTypeOptions = useMemo( const setTabActions = useTabActionsStore((state) => state.setTabActions);
() => [ const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
{ value: 'trans_date', label: 'Tanggal Jual/Bayar' },
{ value: 'realization_date', label: 'Tanggal Realisasi' }, const {
], state: tableFilterState,
[] updateFilter,
); setPage,
setPageSize,
toQueryString: getTableFilterQueryString,
reset: resetFilter,
} = useTableFilter<{
start_date: string;
end_date: string;
customers: OptionType<number>[];
filterBy?: OptionType<string>;
}>({
initial: {
start_date: '',
end_date: '',
customers: [],
filterBy: undefined,
},
paramMap: {
page: 'page',
pageSize: 'limit',
start_date: 'start_date',
end_date: 'end_date',
customers: 'customer_ids',
filterBy: 'filter_by',
},
persist: true,
storeName: 'customer-payment-report-table',
});
const { const {
options: customerOptions, options: customerOptions,
@@ -88,222 +103,159 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
} = useSelect(CustomerApi.basePath, 'id', 'name', 'search'); } = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
// ===== FORMIK SETUP ===== // ===== FORMIK SETUP =====
const formik = useFormik<CustomerPaymentFilterType>({ const formik = useFormik({
initialValues: { initialValues: {
start_date: null, start_date: tableFilterState.start_date,
end_date: null, end_date: tableFilterState.end_date,
customer_ids: null, customers: tableFilterState.customers,
filter_by: null, filterBy: tableFilterState.filterBy,
}, },
validationSchema: CustomerPaymentFilterSchema, onSubmit: (values) => {
onSubmit: (values, { setSubmitting }) => { updateFilter('start_date', values.start_date, true);
setFilterParams({ updateFilter('end_date', values.end_date, true);
start_date: values.start_date || undefined, updateFilter('customers', values.customers, true);
end_date: values.end_date || undefined, updateFilter('filterBy', values.filterBy, true);
customer_ids: values.customer_ids || undefined,
filter_by: values.filter_by || undefined,
});
filterModal.closeModal();
setCurrentPage(1);
setSubmitting(false);
},
onReset: () => {
setFilterParams({});
setCurrentPage(1);
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
filterModal.closeModal(); filterModal.closeModal();
}, },
}); });
handleFilterModalOpenRef.current = () => { const formikResetHandler = () => {
formik.setValues({ resetFilter();
start_date: filterParams.start_date || null,
end_date: filterParams.end_date || null, setHasDateError(false);
customer_ids: filterParams.customer_ids || null, if (dateErrorShown) {
filter_by: filterParams.filter_by || null, toast.dismiss();
setDateErrorShown(false);
}
formik.resetForm({
values: {
start_date: '',
end_date: '',
customers: [],
filterBy: undefined,
},
}); });
filterModal.openModal();
filterModal.closeModal();
}; };
const getPaymentStatusBadgeColor = (notes: string): Color => { const getPaymentStatusBadgeColor = (notes: string): Color => {
const normalizedValue = notes.toLowerCase(); const normalizedValue = notes.toLowerCase();
if (normalizedValue === 'lunas') return 'primary';
if (normalizedValue === 'lunas') { if (normalizedValue.includes('belum')) return 'warning';
return 'primary';
}
if (normalizedValue.includes('belum')) {
return 'warning';
}
return 'neutral'; return 'neutral';
}; };
// ===== DATE CHANGE HANDLERS ===== // ===== DATE CHANGE HANDLERS =====
const handleStartDateChange = useCallback( const handleStartDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
(e: React.ChangeEvent<HTMLInputElement>) => { const value = e.target.value;
const value = e.target.value; formik.setFieldValue('start_date', value);
formik.setFieldValue('start_date', value || null);
if (value && formik.values.end_date) { if (value && formik.values.end_date) {
const startDate = new Date(value); if (new Date(formik.values.end_date) < new Date(value)) {
const endDateObj = new Date(formik.values.end_date); setHasDateError(true);
if (!dateErrorShown) {
if (endDateObj < startDate) { toast.error('Tanggal akhir tidak boleh masa lampau', {
setHasDateError(true); duration: Infinity,
if (!dateErrorShown) { });
toast.error('Tanggal akhir tidak boleh masa lampau', { setDateErrorShown(true);
duration: Infinity,
});
setDateErrorShown(true);
}
} else {
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
} }
} else { } else {
setHasDateError(false); setHasDateError(false);
} if (dateErrorShown) {
}, toast.dismiss();
[formik, dateErrorShown] setDateErrorShown(false);
);
const handleEndDateChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
formik.setFieldValue('end_date', value || null);
if (value && formik.values.start_date) {
const startDateObj = new Date(formik.values.start_date);
const endDate = new Date(value);
if (endDate < startDateObj) {
setHasDateError(true);
if (!dateErrorShown) {
toast.error('Tanggal akhir tidak boleh masa lampau', {
duration: Infinity,
});
setDateErrorShown(true);
}
return;
} }
} }
} else {
setHasDateError(false); setHasDateError(false);
if (dateErrorShown) { }
toast.dismiss(); };
setDateErrorShown(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;
} }
}, }
[formik, dateErrorShown]
);
// ===== FILTER HELPERS ===== setHasDateError(false);
const customerIdsValue = useMemo(() => { if (dateErrorShown) {
if (!formik.values.customer_ids) return []; toast.dismiss();
return customerOptions.filter((opt) => setDateErrorShown(false);
formik.values.customer_ids?.split(',').includes(String(opt.value)) }
); };
}, [formik.values.customer_ids, customerOptions]);
const filterByValue = useMemo(() => {
if (!formik.values.filter_by) return null;
return (
dataTypeOptions.find((opt) => opt.value === formik.values.filter_by) ||
null
);
}, [formik.values.filter_by, dataTypeOptions]);
// ===== DATA FETCHING ===== // ===== DATA FETCHING =====
const { data: customerPayment, isLoading } = useSWR( const { data: customerPayment, isLoading } = useSWR<
() => { BaseApiResponse<CustomerPaymentReport>,
const params = { AxiosError<BaseApiResponse>,
customer_ids: filterParams.customer_ids, SWRHttpKey
filter_by: filterParams.filter_by as >(
| 'trans_date' `${FinanceApi.basePath}/customer-payment${getTableFilterQueryString()}`,
| 'realization_date' httpClientFetcher
| undefined,
start_date: filterParams.start_date,
end_date: filterParams.end_date,
page: currentPage,
limit: pageSize,
};
return ['customer-payment-report', params];
},
([, params]) =>
FinanceApi.getCustomerPaymentReport(
params.customer_ids,
params.filter_by,
params.start_date,
params.end_date,
params.page,
params.limit
)
); );
const data: CustomerPaymentReport[] = useMemo( const data: CustomerPaymentReport[] = isResponseSuccess(customerPayment)
() => ? (customerPayment?.data as unknown as CustomerPaymentReport[]) || []
isResponseSuccess(customerPayment) : [];
? (customerPayment?.data as unknown as CustomerPaymentReport[]) || []
: [],
[customerPayment]
);
const meta = useMemo( const meta =
() => isResponseSuccess(customerPayment) && customerPayment.meta
isResponseSuccess(customerPayment) && customerPayment.meta ? customerPayment.meta
? customerPayment.meta : null;
: null,
[customerPayment]
);
// ===== EXPORT DATA FETCHER ===== // ===== EXPORT DATA FETCHER =====
const customerPaymentExport = useCallback(async (): Promise< const customerPaymentExport = useCallback(async (): Promise<
CustomerPaymentReport[] | null CustomerPaymentReport[] | null
> => { > => {
const params = { const customer_ids =
customer_ids: filterParams.customer_ids, tableFilterState.customers.length > 0
filter_by: filterParams.filter_by as ? tableFilterState.customers.map((o) => String(o.value)).join(',')
| 'trans_date' : undefined;
| 'realization_date' const filter_by = tableFilterState.filterBy?.value as
| undefined, | 'trans_date'
start_date: filterParams.start_date, | 'realization_date'
end_date: filterParams.end_date, | undefined;
limit: 100,
page: 1,
};
const response = await FinanceApi.getCustomerPaymentReport( const response = await FinanceApi.getCustomerPaymentReport(
params.customer_ids, customer_ids,
params.filter_by, filter_by,
params.start_date, tableFilterState.start_date || undefined,
params.end_date, tableFilterState.end_date || undefined,
params.page, 1,
params.limit 100
); );
return isResponseSuccess(response) return isResponseSuccess(response)
? (response.data as unknown as CustomerPaymentReport[]) ? (response.data as unknown as CustomerPaymentReport[])
: null; : null;
}, [filterParams]); }, [tableFilterState]);
// ===== EXPORT HANDLERS ===== // ===== EXPORT HANDLERS =====
const handleExportExcelGeneral = useCallback(async () => { const handleExportExcelGeneral = useCallback(async () => {
setIsExcelGeneralExportLoading(true); setIsExcelGeneralExportLoading(true);
try { try {
const customer_ids =
tableFilterState.customers.length > 0
? tableFilterState.customers.map((o) => String(o.value)).join(',')
: undefined;
await FinanceApi.exportCustomerPaymentToExcelGeneral( await FinanceApi.exportCustomerPaymentToExcelGeneral(
filterParams.customer_ids, customer_ids,
filterParams.filter_by, tableFilterState.filterBy?.value,
filterParams.start_date, tableFilterState.start_date || undefined,
filterParams.end_date tableFilterState.end_date || undefined
); );
toast.success('Excel General berhasil dibuat dan diunduh.'); toast.success('Excel General berhasil dibuat dan diunduh.');
} catch { } catch {
@@ -311,16 +263,20 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
} finally { } finally {
setIsExcelGeneralExportLoading(false); setIsExcelGeneralExportLoading(false);
} }
}, [filterParams]); }, [tableFilterState]);
const handleExportExcel = useCallback(async () => { const handleExportExcel = useCallback(async () => {
setIsExcelExportLoading(true); setIsExcelExportLoading(true);
try { try {
const customer_ids =
tableFilterState.customers.length > 0
? tableFilterState.customers.map((o) => String(o.value)).join(',')
: undefined;
await FinanceApi.exportCustomerPaymentToExcelCustomerPerSheet( await FinanceApi.exportCustomerPaymentToExcelCustomerPerSheet(
filterParams.customer_ids, customer_ids,
filterParams.filter_by, tableFilterState.filterBy?.value,
filterParams.start_date, tableFilterState.start_date || undefined,
filterParams.end_date tableFilterState.end_date || undefined
); );
toast.success('Excel berhasil dibuat dan diunduh.'); toast.success('Excel berhasil dibuat dan diunduh.');
} catch { } catch {
@@ -328,7 +284,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
} finally { } finally {
setIsExcelExportLoading(false); setIsExcelExportLoading(false);
} }
}, [filterParams]); }, [tableFilterState]);
const handleExportPdf = useCallback(async () => { const handleExportPdf = useCallback(async () => {
setIsPdfExportLoading(true); setIsPdfExportLoading(true);
@@ -344,22 +300,18 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
return; return;
} }
const customerName = filterParams.customer_ids const customerName =
? customerOptions tableFilterState.customers.length > 0
.filter((opt) => ? tableFilterState.customers.map((o) => o.label).join(', ')
filterParams.customer_ids?.split(',').includes(String(opt.value)) : 'Semua Customer';
)
.map((opt) => opt.label)
.join(', ') || 'Semua Customer'
: 'Semua Customer';
await generateCustomerPaymentPDF({ await generateCustomerPaymentPDF({
data: allDataForExport, data: allDataForExport,
params: { params: {
customer_name: customerName, customer_name: customerName,
start_date: filterParams.start_date, start_date: tableFilterState.start_date || undefined,
end_date: filterParams.end_date, end_date: tableFilterState.end_date || undefined,
filter_by: filterParams.filter_by as filter_by: tableFilterState.filterBy?.value as
| 'trans_date' | 'trans_date'
| 'realization_date' | 'realization_date'
| undefined, | undefined,
@@ -371,119 +323,103 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
} finally { } finally {
setIsPdfExportLoading(false); setIsPdfExportLoading(false);
} }
}, [customerPaymentExport, filterParams, customerOptions]); }, [customerPaymentExport, tableFilterState]);
// ===== TAB ACTIONS COMPONENT ===== // ===== TAB ACTIONS =====
const TabActions = useMemo(() => { useEffect(() => {
return function TabActionsComponent() { setTabActions(
const setTabActions = useTabActionsStore((state) => state.setTabActions); tabId,
const clearTabActions = useTabActionsStore( <div className='flex flex-row gap-3'>
(state) => state.clearTabActions <ButtonFilter
); values={{
start_date: tableFilterState.start_date,
end_date: tableFilterState.end_date,
customers: tableFilterState.customers,
filterBy: tableFilterState.filterBy,
}}
fieldGroups={[['start_date', 'end_date']]}
onClick={filterModal.openModal}
variant='outline'
className='px-3 py-2.5'
/>
useEffect(() => { <Dropdown
setTabActions( align='end'
tabId, direction='bottom'
<div className='flex flex-row gap-3'> className={{
<ButtonFilter content:
values={filterParams} 'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
fieldGroups={[['start_date', 'end_date']]} }}
onClick={() => handleFilterModalOpenRef.current()} trigger={
<Button
variant='outline' variant='outline'
className='px-3 py-2.5' color='none'
/> isLoading={isAnyExportLoading}
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
<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={isAnyExportLoading}
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 <div className='flex flex-row items-center gap-1.5'>
variant='ghost' <Icon
color='none' icon='heroicons:cloud-arrow-down'
onClick={handleExportExcel} width={20}
isLoading={isExcelExportLoading} height={20}
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap' />
> <span>Export</span>
<Icon icon='heroicons:table-cells' width={20} height={20} /> <div className='w-px self-stretch bg-base-content/10' />
Export to Excel - Customer Per Sheet <Icon icon='heroicons:chevron-down' width={14} height={14} />
</Button> </div>
<Button </Button>
variant='ghost' }
color='none' >
onClick={handleExportExcelGeneral} <Button
isLoading={isExcelGeneralExportLoading} variant='ghost'
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap' color='none'
> onClick={handleExportExcel}
<Icon icon='heroicons:table-cells' width={20} height={20} /> isLoading={isExcelExportLoading}
Export to Excel - General className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
</Button> >
<Icon icon='heroicons:table-cells' width={20} height={20} />
<Button Export to Excel - Customer Per Sheet
variant='ghost' </Button>
color='none' <Button
onClick={handleExportPdf} variant='ghost'
isLoading={isPdfExportLoading} color='none'
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap' onClick={handleExportExcelGeneral}
> isLoading={isExcelGeneralExportLoading}
<Icon icon='heroicons:document' width={20} height={20} /> className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
Export to PDF >
</Button> <Icon icon='heroicons:table-cells' width={20} height={20} />
</Dropdown> Export to Excel - General
</div> </Button>
); <Button
}, [setTabActions]); variant='ghost'
color='none'
useEffect(() => { onClick={handleExportPdf}
return () => { isLoading={isPdfExportLoading}
clearTabActions(tabId); className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
}; >
}, [clearTabActions]); <Icon icon='heroicons:document' width={20} height={20} />
Export to PDF
return null; </Button>
}; </Dropdown>
</div>
);
}, [ }, [
tabId, tabId,
setTabActions,
tableFilterState,
filterModal.openModal,
isAnyExportLoading, isAnyExportLoading,
handleExportExcelGeneral,
handleExportExcel, handleExportExcel,
handleExportExcelGeneral,
handleExportPdf, handleExportPdf,
isExcelGeneralExportLoading,
isExcelExportLoading, isExcelExportLoading,
isExcelGeneralExportLoading,
isPdfExportLoading, isPdfExportLoading,
filterParams,
]); ]);
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]); useEffect(() => {
return () => clearTabActions(tabId);
}, [tabId, clearTabActions]);
const getTableColumns = ( const getTableColumns = (
summary: CustomerPaymentSummary summary: CustomerPaymentSummary
@@ -690,11 +626,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
enableSorting: false, enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.status; const value = props.row.original.status;
if (!value) return '-';
if (!value) {
return '-';
}
return ( return (
<StatusBadge <StatusBadge
color={getPaymentStatusBadgeColor(value)} color={getPaymentStatusBadgeColor(value)}
@@ -733,7 +665,6 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
return ( return (
<> <>
{TabActionsElement}
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'> <div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
{isLoading && ( {isLoading && (
<div className='w-full flex flex-row justify-center items-center p-4'> <div className='w-full flex flex-row justify-center items-center p-4'>
@@ -762,16 +693,16 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
<Pagination <Pagination
totalItems={meta.total_results || 0} totalItems={meta.total_results || 0}
itemsPerPage={meta.limit || 0} itemsPerPage={meta.limit || 0}
currentPage={meta.page || 0} currentPage={tableFilterState.page}
onPrevPage={() => onPrevPage={() => setPage(Math.max(1, tableFilterState.page - 1))}
setCurrentPage((curr) => (curr > 1 ? curr - 1 : curr))
}
onNextPage={() => onNextPage={() =>
setCurrentPage((curr) => setPage(
meta.total_pages && curr < meta.total_pages ? curr + 1 : curr meta.total_pages && tableFilterState.page < meta.total_pages
? tableFilterState.page + 1
: tableFilterState.page
) )
} }
onPageChange={(pageNumber) => setCurrentPage(pageNumber)} onPageChange={setPage}
rowOptions={[10, 20, 50, 100]} rowOptions={[10, 20, 50, 100]}
onRowChange={setPageSize} onRowChange={setPageSize}
/> />
@@ -878,16 +809,16 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
<Pagination <Pagination
totalItems={meta.total_results || 0} totalItems={meta.total_results || 0}
itemsPerPage={meta.limit || 0} itemsPerPage={meta.limit || 0}
currentPage={meta.page || 0} currentPage={tableFilterState.page}
onPrevPage={() => onPrevPage={() => setPage(Math.max(1, tableFilterState.page - 1))}
setCurrentPage((curr) => (curr > 1 ? curr - 1 : curr))
}
onNextPage={() => onNextPage={() =>
setCurrentPage((curr) => setPage(
meta.total_pages && curr < meta.total_pages ? curr + 1 : curr meta.total_pages && tableFilterState.page < meta.total_pages
? tableFilterState.page + 1
: tableFilterState.page
) )
} }
onPageChange={(pageNumber) => setCurrentPage(pageNumber)} onPageChange={setPage}
rowOptions={[10, 20, 50, 100]} rowOptions={[10, 20, 50, 100]}
onRowChange={setPageSize} onRowChange={setPageSize}
/> />
@@ -917,7 +848,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
<Icon icon='heroicons:x-mark' width={20} height={20} /> <Icon icon='heroicons:x-mark' width={20} height={20} />
</Button> </Button>
</div> </div>
<form onSubmit={formik.handleSubmit} onReset={formik.handleReset}> <form onSubmit={formik.handleSubmit} onReset={formikResetHandler}>
<div className='p-4 flex flex-col gap-1.5'> <div className='p-4 flex flex-col gap-1.5'>
<div> <div>
<label className='block text-xs font-semibold text-base-content py-2'> <label className='block text-xs font-semibold text-base-content py-2'>
@@ -927,29 +858,18 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
<DateInput <DateInput
name='start_date' name='start_date'
value={formik.values.start_date || ''} value={formik.values.start_date || ''}
errorMessage={formik.errors.start_date}
onChange={handleStartDateChange} onChange={handleStartDateChange}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
isNestedModal isNestedModal
isError={
formik.touched.start_date &&
Boolean(formik.errors.start_date)
}
/> />
<hr className='w-full max-w-3 h-px border-base-content/10' /> <hr className='w-full max-w-3 h-px border-base-content/10' />
<DateInput <DateInput
name='end_date' name='end_date'
value={formik.values.end_date || ''} value={formik.values.end_date || ''}
errorMessage={formik.errors.end_date}
onChange={handleEndDateChange} onChange={handleEndDateChange}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
isNestedModal isNestedModal
isError={ isError={hasDateError}
(formik.touched.end_date &&
Boolean(formik.errors.end_date)) ||
hasDateError
}
/> />
</div> </div>
</div> </div>
@@ -958,15 +878,10 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
label='Customer' label='Customer'
placeholder='Pilih Customer' placeholder='Pilih Customer'
options={customerOptions} options={customerOptions}
value={customerIdsValue} value={formik.values.customers}
onChange={(val) => { onChange={(val) =>
formik.setFieldValue( formik.setFieldValue('customers', Array.isArray(val) ? val : [])
'customer_ids', }
Array.isArray(val) && val.length > 0
? val.map((v: OptionType) => String(v.value)).join(',')
: null
);
}}
onInputChange={setCustomerInputValue} onInputChange={setCustomerInputValue}
isLoading={isLoadingCustomers} isLoading={isLoadingCustomers}
isClearable isClearable
@@ -978,14 +893,15 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
label='Filter Berdasarkan' label='Filter Berdasarkan'
placeholder='Pilih Filter Berdasarkan' placeholder='Pilih Filter Berdasarkan'
options={dataTypeOptions} options={dataTypeOptions}
value={filterByValue} value={formik.values.filterBy ?? null}
onChange={(val) => { onChange={(val) =>
if (!Array.isArray(val)) { formik.setFieldValue(
formik.setFieldValue('filter_by', val?.value || null); 'filterBy',
} !Array.isArray(val) ? (val ?? undefined) : undefined
}} )
}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
isClearable={true} isClearable
/> />
</div> </div>
@@ -1001,7 +917,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
<Button <Button
type='submit' type='submit'
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold' className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
disabled={hasDateError || !formik.isValid || formik.isSubmitting} disabled={hasDateError}
> >
Apply Filter Apply Filter
</Button> </Button>