From ac3fbedccd91580035f120265c8d9adfe64ea38e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 5 Feb 2026 10:46:37 +0700 Subject: [PATCH 1/6] refactor(FE): Rename filter keys to plural forms --- src/components/pages/finance/FinanceTable.tsx | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx index c1c7f079..ba2101b8 100644 --- a/src/components/pages/finance/FinanceTable.tsx +++ b/src/components/pages/finance/FinanceTable.tsx @@ -152,10 +152,10 @@ const FinanceTable = () => { } = useTableFilter({ initial: { search: searchValue, - transactionType: '', - bankId: '', - customerId: '', - supplierId: '', + transactionTypes: '', + bankIds: '', + customerIds: '', + supplierIds: '', sortBy: '', startDate: '', endDate: '', @@ -163,10 +163,10 @@ const FinanceTable = () => { paramMap: { page: 'page', pageSize: 'limit', - transactionType: 'transaction_type', - bankId: 'bank_id', - customerId: 'customer_id', - supplierId: 'supplier_id', + transactionTypes: 'transaction_types', + bankIds: 'bank_ids', + customerIds: 'customer_ids', + supplierIds: 'supplier_ids', sortBy: 'sort_date', startDate: 'start_date', endDate: 'end_date', @@ -178,10 +178,10 @@ const FinanceTable = () => { const deleteModal = useModal(); const [pendingFilters, setPendingFilters] = useState({ search: searchValue, - transactionType: '', - bankId: '', - customerId: '', - supplierId: '', + transactionTypes: '', + bankIds: '', + customerIds: '', + supplierIds: '', sortBy: '', startDate: '', endDate: '', @@ -247,7 +247,7 @@ const FinanceTable = () => { setSelectedTransactionType(val); setPendingFilters((prev) => ({ ...prev, - transactionType: val + transactionTypes: val ? Array.isArray(val) ? val.map((item) => item.value).join(',') : (val.value as string) @@ -258,7 +258,7 @@ const FinanceTable = () => { setSelectedBank(val); setPendingFilters((prev) => ({ ...prev, - bankId: val + bankIds: val ? Array.isArray(val) ? val.map((item) => item.value).join(',') : (val.value as string) @@ -269,7 +269,7 @@ const FinanceTable = () => { setSelectedCustomerId(val); setPendingFilters((prev) => ({ ...prev, - customerId: val + customerIds: val ? Array.isArray(val) ? val.map((item) => item.value).join(',') : (val.value as string) @@ -280,7 +280,7 @@ const FinanceTable = () => { setSelectedSupplierId(val); setPendingFilters((prev) => ({ ...prev, - supplierId: val + supplierIds: val ? Array.isArray(val) ? val.map((item) => item.value).join(',') : (val.value as string) @@ -307,10 +307,10 @@ const FinanceTable = () => { const submitFilterHandler = () => { updateFilter('search', pendingFilters.search); setSearchValue(pendingFilters.search); - updateFilter('transactionType', pendingFilters.transactionType); - updateFilter('bankId', pendingFilters.bankId); - updateFilter('customerId', pendingFilters.customerId); - updateFilter('supplierId', pendingFilters.supplierId); + updateFilter('transactionTypes', pendingFilters.transactionTypes); + updateFilter('bankIds', pendingFilters.bankIds); + updateFilter('customerIds', pendingFilters.customerIds); + updateFilter('supplierIds', pendingFilters.supplierIds); updateFilter('sortBy', pendingFilters.sortBy); updateFilter('startDate', pendingFilters.startDate); updateFilter('endDate', pendingFilters.endDate); @@ -324,10 +324,10 @@ const FinanceTable = () => { const emptyFilters = { search: '', - transactionType: '', - bankId: '', - customerId: '', - supplierId: '', + transactionTypes: '', + bankIds: '', + customerIds: '', + supplierIds: '', sortBy: '', startDate: '', endDate: '', @@ -336,10 +336,10 @@ const FinanceTable = () => { updateFilter('search', ''); resetSearchValue(); - updateFilter('transactionType', ''); - updateFilter('bankId', ''); - updateFilter('customerId', ''); - updateFilter('supplierId', ''); + updateFilter('transactionTypes', ''); + updateFilter('bankIds', ''); + updateFilter('customerIds', ''); + updateFilter('supplierIds', ''); updateFilter('sortBy', ''); updateFilter('startDate', ''); updateFilter('endDate', ''); From 3153423f14ec6d638b00d955f57593a16268f309 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 5 Feb 2026 10:48:26 +0700 Subject: [PATCH 2/6] refactor(FE): Replace size-full with w-full in FinanceTable --- src/components/pages/finance/FinanceTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx index ba2101b8..670ad5a4 100644 --- a/src/components/pages/finance/FinanceTable.tsx +++ b/src/components/pages/finance/FinanceTable.tsx @@ -483,7 +483,7 @@ const FinanceTable = () => { }, [resetSearchValue]); return ( -
+
@@ -601,22 +591,34 @@ const FinanceTable = () => { isClearable />
diff --git a/src/components/pages/finance/FinanceTableFilter.schema.ts b/src/components/pages/finance/FinanceTableFilter.schema.ts new file mode 100644 index 00000000..bc1053d3 --- /dev/null +++ b/src/components/pages/finance/FinanceTableFilter.schema.ts @@ -0,0 +1,39 @@ +import { OptionType } from '@/components/input/SelectInput'; +import * as yup from 'yup'; + +export type FinanceTableFilterType = { + search: string; + transaction_types: string; + bank_ids: string; + customer_ids: string; + supplier_ids: string; + sort_by: string; + start_date: string; + end_date: string; +}; + +export const FinanceTableFilterSchema = yup.object({ + search: yup.string().optional(), + transaction_types: yup.string().optional(), + bank_ids: yup.string().optional(), + customer_ids: yup.string().optional(), + supplier_ids: yup.string().optional(), + sort_by: yup.string().optional(), + start_date: yup.string().optional(), + end_date: yup + .string() + .optional() + .test( + 'is-greater-than-start', + 'Tanggal akhir tidak boleh masa lampau', + function (value) { + const { start_date } = this.parent; + if (!start_date || !value) return true; + return new Date(value) >= new Date(start_date); + } + ), +}) as yup.ObjectSchema; + +export type FinanceTableFilterValues = yup.InferType< + typeof FinanceTableFilterSchema +>; From 4aa9d54b1e6182a5668cc939556a68a9095afc96 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 5 Feb 2026 11:02:20 +0700 Subject: [PATCH 5/6] refactor(FE): Remove unused search params and yup import --- src/components/pages/finance/FinanceTable.tsx | 40 +++++++------------ .../finance/FinanceTableFilter.schema.ts | 1 - 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx index aba80e4e..40091237 100644 --- a/src/components/pages/finance/FinanceTable.tsx +++ b/src/components/pages/finance/FinanceTable.tsx @@ -3,7 +3,6 @@ import { CellContext } from '@tanstack/react-table'; import { useSearchParams } from 'next/navigation'; import useSWR from 'swr'; import { useFormik } from 'formik'; -import * as yup from 'yup'; import Button from '@/components/Button'; import Card from '@/components/Card'; @@ -174,7 +173,6 @@ const FinanceTable = () => { }); // ===== State ===== - const [searchParams, setSearchParams] = useSearchParams(); const deleteModal = useModal(); const [selectedTransactionType, setSelectedTransactionType] = useState< OptionType | OptionType[] | null @@ -254,6 +252,20 @@ const FinanceTable = () => { loadMore: bankLoadMore, } = useSelect(BankApi.basePath, 'id', 'alias'); + const bankSelectOptions = useMemo(() => { + if (!isResponseSuccess(bankRawData)) return []; + + return bankOptions.map((bank) => { + const bankData = bankRawData.data.find((data) => data.id === bank?.value); + return { + label: bankData + ? `${bankData.alias} - ${bankData.account_number} - ${bankData.owner}` + : '', + value: bank?.value, + }; + }); + }, [bankOptions, bankRawData]); + // ===== Handler ===== const searchChangeHandler = (e: React.ChangeEvent) => { filterFormik.setFieldValue('search', e.target.value); @@ -311,10 +323,6 @@ const FinanceTable = () => { val ? ((val as OptionType).value as string) : '' ); }; - const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { - const newVal = val as OptionType; - setPageSize(newVal.value as number); - }; const resetFilterHandler = () => { setSelectedTransactionType(null); setSelectedBank(null); @@ -454,18 +462,15 @@ const FinanceTable = () => { }, []); useEffect(() => { - // Store current path on mount previousPathRef.current = window.location.pathname; return () => { const currentPath = window.location.pathname; - // if both paths are within /finance module const isCurrentPathFinance = currentPath.includes('/finance'); const isPreviousPathFinance = previousPathRef.current?.includes('/finance'); - // reset if we outside finance module entirely if (isPreviousPathFinance && !isCurrentPathFinance) { resetSearchValue(); } @@ -558,22 +563,7 @@ const FinanceTable = () => { isMulti /> ({ - label: - bankRawData.data.find((data) => data.id === bank?.value) - ?.alias + - ' - ' + - bankRawData.data.find((data) => data.id === bank?.value) - ?.account_number + - ' - ' + - bankRawData.data.find((data) => data.id === bank?.value) - ?.owner, - value: bank?.value, - })) - : [] - } + options={bankSelectOptions} label='Bank' value={selectedBank} onChange={bankChangeHandler} diff --git a/src/components/pages/finance/FinanceTableFilter.schema.ts b/src/components/pages/finance/FinanceTableFilter.schema.ts index bc1053d3..fecfc35d 100644 --- a/src/components/pages/finance/FinanceTableFilter.schema.ts +++ b/src/components/pages/finance/FinanceTableFilter.schema.ts @@ -1,4 +1,3 @@ -import { OptionType } from '@/components/input/SelectInput'; import * as yup from 'yup'; export type FinanceTableFilterType = { From 372b439ff01cdc542f861da5c0ba52bc9a413d2d Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 5 Feb 2026 11:13:53 +0700 Subject: [PATCH 6/6] refactor(FE): Validate date range and show persistent toast --- src/components/pages/finance/FinanceTable.tsx | 92 +++++++++++++++++-- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx index 40091237..6f422753 100644 --- a/src/components/pages/finance/FinanceTable.tsx +++ b/src/components/pages/finance/FinanceTable.tsx @@ -189,6 +189,7 @@ const FinanceTable = () => { const [selectedSortBy, setSelectedSortBy] = useState(null); const [selectedFinance, setSelectedFinance] = useState(null); const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const [dateErrorShown, setDateErrorShown] = useState(false); // ===== Formik for Filter ===== const filterFormik = useFormik({ @@ -323,6 +324,70 @@ const FinanceTable = () => { val ? ((val as OptionType).value as string) : '' ); }; + + const startDateChangeHandler = (e: React.ChangeEvent) => { + const value = e.target.value; + const endDate = filterFormik.values.end_date; + + filterFormik.setFieldValue('start_date', value); + + if (value && endDate) { + const startDate = new Date(value); + const endDateObj = new Date(endDate); + + if (endDateObj < startDate) { + filterFormik.setFieldError( + 'end_date', + 'Tanggal akhir tidak boleh masa lampau' + ); + if (!dateErrorShown) { + toast.error('Tanggal akhir tidak boleh masa lampau', { + duration: Infinity, + }); + setDateErrorShown(true); + } + } else { + filterFormik.setFieldError('end_date', undefined); + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } + } + } + }; + + const endDateChangeHandler = (e: React.ChangeEvent) => { + const value = e.target.value; + const startDate = filterFormik.values.start_date; + + filterFormik.setFieldValue('end_date', value); + + if (value && startDate) { + const startDateObj = new Date(startDate); + const endDate = new Date(value); + + if (endDate < startDateObj) { + filterFormik.setFieldError( + 'end_date', + 'Tanggal akhir tidak boleh masa lampau' + ); + if (!dateErrorShown) { + toast.error('Tanggal akhir tidak boleh masa lampau', { + duration: Infinity, + }); + setDateErrorShown(true); + } + return; + } + } + + filterFormik.setFieldError('end_date', undefined); + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } + }; + const resetFilterHandler = () => { setSelectedTransactionType(null); setSelectedBank(null); @@ -461,6 +526,14 @@ const FinanceTable = () => { ]; }, []); + useEffect(() => { + return () => { + if (dateErrorShown) { + toast.dismiss(); + } + }; + }, [dateErrorShown]); + useEffect(() => { previousPathRef.current = window.location.pathname; @@ -474,8 +547,13 @@ const FinanceTable = () => { if (isPreviousPathFinance && !isCurrentPathFinance) { resetSearchValue(); } + + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } }; - }, [resetSearchValue]); + }, [resetSearchValue, dateErrorShown]); return (
@@ -584,25 +662,23 @@ const FinanceTable = () => { name='start_date' label='Periode Tanggal (Mulai)' value={filterFormik.values.start_date} - onChange={filterFormik.handleChange} + onChange={startDateChangeHandler} errorMessage={ - filterFormik.touched.start_date && filterFormik.errors.start_date - ? filterFormik.errors.start_date + filterFormik.errors.end_date + ? filterFormik.errors.end_date : undefined } - onBlur={filterFormik.handleBlur} />