refactor: optimize DebtSupplierTab with useTableFilter persistence pattern

Replace filterParams/currentPage/pageSize state with useTableFilter (persist:true),
switch SWR to httpClientFetcher with explicit type, store OptionType[] directly for
suppliers/filterBy, add formikResetHandler using resetFilter(), remove TabActions
component anti-pattern 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:19 +07:00
parent b3b60018bb
commit d60877d391
@@ -9,23 +9,15 @@ import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper'; import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
import { SupplierApi } from '@/services/api/master-data'; import { SupplierApi } from '@/services/api/master-data';
import { import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier';
DebtRow,
DebtSupplier,
DebtSupplierFilter,
} from '@/types/api/report/debt-supplier';
import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF'; import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { ColumnDef } from '@tanstack/react-table'; import { ColumnDef } from '@tanstack/react-table';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import useSWR from 'swr'; import useSWR from 'swr';
import { DebtSupplierApi } from '@/services/api/report/debt-supplier'; import { DebtSupplierApi } from '@/services/api/report/debt-supplier';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import {
DebtSupplierFilterSchema,
DebtSupplierFilterType,
} from '@/components/pages/report/finance/filter/DebtSupplierFilter';
import ButtonFilter from '@/components/helper/ButtonFilter'; import ButtonFilter from '@/components/helper/ButtonFilter';
import { Color } from '@/types/theme'; import { Color } from '@/types/theme';
import { Supplier } from '@/types/api/master-data/supplier'; import { Supplier } from '@/types/api/master-data/supplier';
@@ -34,6 +26,10 @@ import SelectInputRadio from '@/components/input/SelectInputRadio';
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store'; import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
import StatusBadge from '@/components/helper/StatusBadge'; import StatusBadge from '@/components/helper/StatusBadge';
import DebtSupplierSkeleton from '@/components/pages/report/finance/skeleton/DebtSupplierSkeleton'; import DebtSupplierSkeleton from '@/components/pages/report/finance/skeleton/DebtSupplierSkeleton';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general';
import { AxiosError } from 'axios';
const dueStatus: Record<string, Color> = { const dueStatus: Record<string, Color> = {
'Sudah Jatuh Tempo': 'error', 'Sudah Jatuh Tempo': 'error',
@@ -51,7 +47,6 @@ const getPillBadge = (
statusText: string, statusText: string,
type: 'due' | 'payment' = 'payment' type: 'due' | 'payment' = 'payment'
) => { ) => {
// Get color based on type
const color = const color =
type === 'due' type === 'due'
? dueStatus[statusText] || 'neutral' ? dueStatus[statusText] || 'neutral'
@@ -68,6 +63,11 @@ const getPillBadge = (
); );
}; };
const dataTypeOptions: OptionType<string>[] = [
{ value: 'received_date', label: 'Tanggal Terima' },
{ value: 'po_date', label: 'Tanggal PO' },
];
interface DebtSupplierTabProps { interface DebtSupplierTabProps {
tabId: string; tabId: string;
} }
@@ -81,26 +81,45 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
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<DebtSupplierFilter>({
start_date: undefined,
end_date: undefined,
supplier_ids: undefined,
filter_by: undefined,
});
// ===== DATE ERROR STATE =====
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 setTabActions = useTabActionsStore((state) => state.setTabActions);
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
const {
state: tableFilterState,
updateFilter,
setPage,
setPageSize,
toQueryString: getTableFilterQueryString,
reset: resetFilter,
} = useTableFilter<{
start_date: string;
end_date: string;
suppliers: OptionType<number>[];
filterBy?: OptionType<string>;
}>({
initial: {
start_date: '',
end_date: '',
suppliers: [],
filterBy: undefined,
},
paramMap: {
page: 'page',
pageSize: 'limit',
start_date: 'start_date',
end_date: 'end_date',
suppliers: 'supplier_ids',
filterBy: 'filter_by',
},
persist: true,
storeName: 'debt-supplier-report-table',
});
const { const {
setInputValue: setSupplierInputValue, setInputValue: setSupplierInputValue,
options: supplierOptions, options: supplierOptions,
@@ -108,154 +127,149 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
loadMore: loadMoreSuppliers, loadMore: loadMoreSuppliers,
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name'); } = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
const dataTypeOptions = useMemo(
() => [
{ value: 'received_date', label: 'Tanggal Terima' },
{ value: 'po_date', label: 'Tanggal PO' },
],
[]
);
// ===== FORMIK SETUP ===== // ===== FORMIK SETUP =====
const formik = useFormik<DebtSupplierFilterType>({ const formik = useFormik({
initialValues: { initialValues: {
startDate: null, start_date: tableFilterState.start_date,
endDate: null, end_date: tableFilterState.end_date,
supplierIds: null, suppliers: tableFilterState.suppliers,
filterBy: null, filterBy: tableFilterState.filterBy,
}, },
validationSchema: DebtSupplierFilterSchema,
onSubmit: (values) => { onSubmit: (values) => {
setFilterParams({ updateFilter('start_date', values.start_date, true);
start_date: values.startDate?.toString() || undefined, updateFilter('end_date', values.end_date, true);
end_date: values.endDate?.toString() || undefined, updateFilter('suppliers', values.suppliers, true);
supplier_ids: updateFilter('filterBy', values.filterBy, true);
values.supplierIds?.map((v) => String(v.value)).join(',') ||
undefined,
filter_by: values.filterBy?.value?.toString() || undefined,
});
filterModal.closeModal();
setCurrentPage(1);
},
onReset: () => {
setFilterParams({
start_date: undefined,
end_date: undefined,
supplier_ids: undefined,
filter_by: undefined,
});
setCurrentPage(1);
filterModal.closeModal(); filterModal.closeModal();
}, },
}); });
handleFilterModalOpenRef.current = () => { const formikResetHandler = () => {
const restoredFilterBy = resetFilter();
dataTypeOptions.find((opt) => opt.value === filterParams.filter_by) ||
null;
const supplierIdList = filterParams.supplier_ids setHasDateError(false);
? filterParams.supplier_ids.split(',') if (dateErrorShown) {
: []; toast.dismiss();
const restoredSupplierIds = supplierOptions.filter((opt) => setDateErrorShown(false);
supplierIdList.includes(String(opt.value)) }
);
formik.setValues({ formik.resetForm({
startDate: filterParams.start_date || null, values: {
endDate: filterParams.end_date || null, start_date: '',
supplierIds: restoredSupplierIds.length > 0 ? restoredSupplierIds : null, end_date: '',
filterBy: restoredFilterBy, suppliers: [],
filterBy: undefined,
},
}); });
filterModal.openModal();
filterModal.closeModal();
};
// ===== DATE CHANGE HANDLERS =====
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);
}
}; };
// ===== DATA FETCHING ===== // ===== DATA FETCHING =====
const { data: debtSupplier, isLoading } = useSWR( const { data: debtSupplierResponse, isLoading } = useSWR<
() => { BaseApiResponse<DebtSupplier[]>,
const params = { AxiosError<BaseApiResponse>,
supplier_ids: filterParams.supplier_ids, SWRHttpKey
filter_by: filterParams.filter_by, >(
start_date: filterParams.start_date, `${DebtSupplierApi.basePath}/debt-supplier${getTableFilterQueryString()}`,
end_date: filterParams.end_date, httpClientFetcher
page: currentPage,
limit: pageSize,
};
return ['debt-supplier-report', params];
},
([, params]) =>
DebtSupplierApi.getDebtSupplierReport(
params.supplier_ids,
params.filter_by,
params.start_date,
params.end_date,
params.page,
params.limit
)
); );
const data: DebtSupplier[] = useMemo( const data: DebtSupplier[] = isResponseSuccess(debtSupplierResponse)
() => ? ((debtSupplierResponse?.data as unknown as DebtSupplier[]) ?? [])
isResponseSuccess(debtSupplier) : [];
? (debtSupplier?.data as unknown as DebtSupplier[]) || []
: [],
[debtSupplier]
);
const meta = useMemo( const meta =
() => isResponseSuccess(debtSupplierResponse) && debtSupplierResponse.meta
isResponseSuccess(debtSupplier) && debtSupplier.meta ? debtSupplierResponse.meta
? debtSupplier.meta : null;
: null,
[debtSupplier]
);
// ===== EXPORT DATA FETCHER ===== // ===== EXPORT DATA FETCHER =====
const debtSupplierExport = useCallback(async (): Promise< const debtSupplierExport = useCallback(async (): Promise<
DebtSupplier[] | null DebtSupplier[] | null
> => { > => {
const params = { const supplier_ids =
supplier_ids: tableFilterState.suppliers.length > 0
formik.values.supplierIds && formik.values.supplierIds.length > 0 ? tableFilterState.suppliers.map((o) => String(o.value)).join(',')
? formik.values.supplierIds.map((v) => String(v.value)).join(',') : undefined;
: undefined,
filter_by: formik.values.filterBy?.value?.toString() || undefined,
start_date: formik.values.startDate || undefined,
end_date: formik.values.endDate || undefined,
date_type: formik.values.filterBy
? formik.values.filterBy.value
: undefined,
limit: 100,
page: 1,
};
const response = await DebtSupplierApi.getDebtSupplierReport( const response = await DebtSupplierApi.getDebtSupplierReport(
params.supplier_ids, supplier_ids,
params.filter_by, tableFilterState.filterBy?.value,
params.start_date, tableFilterState.start_date || undefined,
params.end_date tableFilterState.end_date || undefined,
1,
100
); );
return isResponseSuccess(response) return isResponseSuccess(response)
? (response.data as unknown as DebtSupplier[]) ? (response.data as unknown as DebtSupplier[])
: null; : null;
}, [ }, [tableFilterState]);
formik.values.supplierIds,
formik.values.startDate,
formik.values.endDate,
formik.values.filterBy,
]);
// ===== EXPORT HANDLERS ===== // ===== EXPORT HANDLERS =====
const handleExportExcel = useCallback(async () => { const handleExportExcel = useCallback(async () => {
setIsExcelExportLoading(true); setIsExcelExportLoading(true);
try { try {
const supplier_ids =
tableFilterState.suppliers.length > 0
? tableFilterState.suppliers.map((o) => String(o.value)).join(',')
: undefined;
await DebtSupplierApi.exportToExcelSupplierPerSheet( await DebtSupplierApi.exportToExcelSupplierPerSheet(
filterParams.supplier_ids, supplier_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 {
@@ -263,7 +277,30 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
} finally { } finally {
setIsExcelExportLoading(false); setIsExcelExportLoading(false);
} }
}, [filterParams]); }, [tableFilterState]);
const handleExportExcelGeneral = useCallback(async () => {
setIsExcelGeneralExportLoading(true);
try {
const supplier_ids =
tableFilterState.suppliers.length > 0
? tableFilterState.suppliers.map((o) => String(o.value)).join(',')
: undefined;
await DebtSupplierApi.exportToExcelGeneral(
supplier_ids,
tableFilterState.filterBy?.value,
tableFilterState.start_date || undefined,
tableFilterState.end_date || undefined
);
toast.success('Excel General berhasil dibuat dan diunduh.');
} catch {
toast.error('Gagal membuat Excel General. Silakan coba lagi.');
} finally {
setIsExcelGeneralExportLoading(false);
}
}, [tableFilterState]);
const handleExportPdf = useCallback(async () => { const handleExportPdf = useCallback(async () => {
setIsPdfExportLoading(true); setIsPdfExportLoading(true);
@@ -279,15 +316,18 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
return; return;
} }
const supplierName =
tableFilterState.suppliers.length > 0
? tableFilterState.suppliers.map((o) => o.label).join(', ')
: undefined;
await generateDebtSupplierPDF({ await generateDebtSupplierPDF({
data: allDataForExport, data: allDataForExport,
params: { params: {
supplier_name: formik.values.supplierIds supplier_name: supplierName,
?.map((v) => v.label) filter_by: tableFilterState.filterBy?.label,
.join(', '), start_date: tableFilterState.start_date || undefined,
filter_by: formik.values.filterBy?.label, end_date: tableFilterState.end_date || undefined,
start_date: formik.values.startDate || undefined,
end_date: formik.values.endDate || undefined,
}, },
}); });
toast.success('PDF berhasil dibuat dan diunduh.'); toast.success('PDF berhasil dibuat dan diunduh.');
@@ -296,47 +336,22 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
} finally { } finally {
setIsPdfExportLoading(false); setIsPdfExportLoading(false);
} }
}, [ }, [debtSupplierExport, tableFilterState]);
debtSupplierExport,
formik.values.supplierIds,
formik.values.filterBy,
formik.values.startDate,
formik.values.endDate,
]);
const handleExportExcelGeneral = useCallback(async () => {
setIsExcelGeneralExportLoading(true);
try {
await DebtSupplierApi.exportToExcelGeneral(
filterParams.supplier_ids,
filterParams.filter_by,
filterParams.start_date,
filterParams.end_date
);
toast.success('Excel General berhasil dibuat dan diunduh.');
} catch {
toast.error('Gagal membuat Excel General. Silakan coba lagi.');
} finally {
setIsExcelGeneralExportLoading(false);
}
}, [filterParams]);
// ===== TAB ACTIONS COMPONENT =====
const TabActions = useMemo(() => {
return function TabActionsComponent() {
const setTabActions = useTabActionsStore((state) => state.setTabActions);
const clearTabActions = useTabActionsStore(
(state) => state.clearTabActions
);
// ===== TAB ACTIONS =====
useEffect(() => { useEffect(() => {
setTabActions( setTabActions(
tabId, tabId,
<div className='flex flex-row gap-3'> <div className='flex flex-row gap-3'>
<ButtonFilter <ButtonFilter
values={filterParams} values={{
start_date: tableFilterState.start_date,
end_date: tableFilterState.end_date,
suppliers: tableFilterState.suppliers,
filterBy: tableFilterState.filterBy,
}}
fieldGroups={[['start_date', 'end_date']]} fieldGroups={[['start_date', 'end_date']]}
onClick={() => handleFilterModalOpenRef.current()} onClick={filterModal.openModal}
variant='outline' variant='outline'
className='px-3 py-2.5' className='px-3 py-2.5'
/> />
@@ -361,16 +376,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
width={20} width={20}
height={20} height={20}
/> />
<span>Export</span> <span>Export</span>
<div className='w-px self-stretch bg-base-content/10' /> <div className='w-px self-stretch bg-base-content/10' />
<Icon icon='heroicons:chevron-down' width={14} height={14} />
<Icon
icon='heroicons:chevron-down'
width={14}
height={14}
/>
</div> </div>
</Button> </Button>
} }
@@ -408,19 +416,11 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
</Dropdown> </Dropdown>
</div> </div>
); );
}, [setTabActions]);
useEffect(() => {
return () => {
clearTabActions(tabId);
};
}, [clearTabActions]);
return null;
};
}, [ }, [
tabId, tabId,
filterParams, setTabActions,
tableFilterState,
filterModal.openModal,
isAnyExportLoading, isAnyExportLoading,
handleExportExcel, handleExportExcel,
handleExportExcelGeneral, handleExportExcelGeneral,
@@ -430,24 +430,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
isPdfExportLoading, isPdfExportLoading,
]); ]);
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
useEffect(() => { useEffect(() => {
return () => { return () => clearTabActions(tabId);
if (dateErrorShown) { }, [tabId, clearTabActions]);
toast.dismiss();
}
};
}, [dateErrorShown]);
useEffect(() => {
return () => {
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
};
}, [filterModal.open, dateErrorShown]);
const getTableColumns = (supplier?: DebtSupplier): ColumnDef<DebtRow>[] => [ const getTableColumns = (supplier?: DebtSupplier): ColumnDef<DebtRow>[] => [
{ {
@@ -662,9 +647,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
}, },
}, },
]; ];
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'>
@@ -693,16 +678,16 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
<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}
/> />
@@ -802,16 +787,16 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
<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}
/> />
@@ -827,7 +812,6 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
modalBox: 'p-0 rounded-[0.875rem] xl:max-w-4/12 max-w-sm', modalBox: 'p-0 rounded-[0.875rem] xl:max-w-4/12 max-w-sm',
}} }}
> >
<form onSubmit={formik.handleSubmit} onReset={formik.handleReset}>
{/* Modal Header */} {/* Modal Header */}
<div className='flex items-center justify-between gap-2 border-b border-base-content/10 p-4'> <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'> <div className='flex items-center gap-2 text-primary'>
@@ -844,6 +828,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
</Button> </Button>
</div> </div>
<form onSubmit={formik.handleSubmit} onReset={formikResetHandler}>
{/* Modal Body */} {/* Modal Body */}
<div className='p-4 flex flex-col gap-1.5'> <div className='p-4 flex flex-col gap-1.5'>
<div> <div>
@@ -852,153 +837,68 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
</label> </label>
<div className='flex flex-row gap-1.5 items-center justify-between'> <div className='flex flex-row gap-1.5 items-center justify-between'>
<DateInput <DateInput
name='startDate' name='start_date'
value={formik.values.startDate || ''} value={formik.values.start_date || ''}
onChange={(e) => { onChange={handleStartDateChange}
const value = e.target.value;
formik.setFieldValue('startDate', value || null);
if (value && formik.values.endDate) {
const startDate = new Date(value);
const endDateObj = new Date(formik.values.endDate);
if (endDateObj < startDate) {
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);
}
}}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
isError={
formik.touched.startDate && !!formik.errors.startDate
}
errorMessage={formik.errors.startDate}
isNestedModal isNestedModal
/> />
<hr className='w-full max-w-3 h-px border-base-content/10'></hr> <hr className='w-full max-w-3 h-px border-base-content/10' />
<DateInput <DateInput
name='endDate' name='end_date'
value={formik.values.endDate || ''} value={formik.values.end_date || ''}
onChange={(e) => { onChange={handleEndDateChange}
const value = e.target.value;
formik.setFieldValue('endDate', value || null);
if (value && formik.values.startDate) {
const startDateObj = new Date(formik.values.startDate);
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;
}
}
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
isError={
(formik.touched.endDate && !!formik.errors.endDate) ||
hasDateError
}
errorMessage={formik.errors.endDate}
isNestedModal isNestedModal
isError={hasDateError}
/> />
</div> </div>
</div> </div>
<div>
<SelectInputCheckbox <SelectInputCheckbox
label='Supplier' label='Supplier'
placeholder='Pilih Supplier' placeholder='Pilih Supplier'
isMulti
options={supplierOptions} options={supplierOptions}
value={ value={formik.values.suppliers}
(formik.values.supplierIds as onChange={(val) =>
| { value: number; label: string } formik.setFieldValue('suppliers', Array.isArray(val) ? val : [])
| { value: number; label: string }[]
| null
| undefined) || []
} }
onChange={(val) => {
formik.setFieldValue(
'supplierIds',
Array.isArray(val) ? val : val ? [val] : null
);
}}
onInputChange={setSupplierInputValue} onInputChange={setSupplierInputValue}
onMenuScrollToBottom={loadMoreSuppliers} onMenuScrollToBottom={loadMoreSuppliers}
isLoading={isLoadingSupplierOptions} isLoading={isLoadingSupplierOptions}
isClearable isClearable
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
isError={
formik.touched.supplierIds && !!formik.errors.supplierIds
}
errorMessage={formik.errors.supplierIds as string}
/> />
</div>
<div>
<SelectInputRadio <SelectInputRadio
label='Filter Berdasarkan' label='Filter Berdasarkan'
placeholder='Pilih Filter Berdasarkan' placeholder='Pilih Filter Berdasarkan'
options={dataTypeOptions} options={dataTypeOptions}
value={ value={formik.values.filterBy ?? null}
(formik.values.filterBy as onChange={(val) =>
| { value: string; label: string }
| { value: string; label: string }[]
| null
| undefined) || null
}
onChange={(val) => {
formik.setFieldValue( formik.setFieldValue(
'filterBy', 'filterBy',
val ? (val as OptionType) : null !Array.isArray(val) ? (val ?? undefined) : undefined
); )
}} }
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
isClearable isClearable
isError={formik.touched.filterBy && !!formik.errors.filterBy}
errorMessage={formik.errors.filterBy as string}
/> />
</div> </div>
</div>
{/* Action Buttons */} {/* Modal Footer */}
<div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'> <div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'>
<Button <Button
variant='soft'
color='none'
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'
type='reset' 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 Reset Filter
</Button> </Button>
<Button <Button
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
type='submit' type='submit'
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
disabled={hasDateError}
> >
Apply Filter Apply Filter
</Button> </Button>