refactor(FE): Use Formik for finance table filters

This commit is contained in:
rstubryan
2026-02-05 10:58:59 +07:00
parent c164977bb9
commit b45c7c8ea6
2 changed files with 120 additions and 79 deletions
+81 -79
View File
@@ -1,13 +1,9 @@
import {
ChangeEventHandler,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
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';
@@ -40,6 +36,10 @@ import { Icon } from '@iconify/react';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import { useUiStore } from '@/stores/ui/ui.store';
import {
FinanceTableFilterSchema,
FinanceTableFilterValues,
} from './FinanceTableFilter.schema';
const RowOptionsMenu = ({
type = 'dropdown',
@@ -176,16 +176,6 @@ const FinanceTable = () => {
// ===== State =====
const [searchParams, setSearchParams] = useSearchParams();
const deleteModal = useModal();
const [pendingFilters, setPendingFilters] = useState({
search: searchValue,
transactionTypes: '',
bankIds: '',
customerIds: '',
supplierIds: '',
sortBy: '',
startDate: '',
endDate: '',
});
const [selectedTransactionType, setSelectedTransactionType] = useState<
OptionType | OptionType[] | null
>(null);
@@ -202,6 +192,33 @@ const FinanceTable = () => {
const [selectedFinance, setSelectedFinance] = useState<Finance | null>(null);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
// ===== Formik for Filter =====
const filterFormik = useFormik<FinanceTableFilterValues>({
initialValues: {
search: searchValue,
transaction_types: '',
bank_ids: '',
customer_ids: '',
supplier_ids: '',
sort_by: '',
start_date: '',
end_date: '',
},
validationSchema: FinanceTableFilterSchema,
enableReinitialize: true,
onSubmit: (values) => {
updateFilter('search', values.search);
setSearchValue(values.search);
updateFilter('transactionTypes', values.transaction_types);
updateFilter('bankIds', values.bank_ids);
updateFilter('customerIds', values.customer_ids);
updateFilter('supplierIds', values.supplier_ids);
updateFilter('sortBy', values.sort_by);
updateFilter('startDate', values.start_date);
updateFilter('endDate', values.end_date);
},
});
// ===== Fetch Data =====
const {
data: finances,
@@ -238,83 +255,66 @@ const FinanceTable = () => {
} = useSelect<Bank>(BankApi.basePath, 'id', 'alias');
// ===== Handler =====
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setPendingFilters((prev) => ({ ...prev, search: e.target.value }));
const searchChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
filterFormik.setFieldValue('search', e.target.value);
};
const transactionTypeChangeHandler = (
val: OptionType | OptionType[] | null
) => {
setSelectedTransactionType(val);
setPendingFilters((prev) => ({
...prev,
transactionTypes: val
filterFormik.setFieldValue(
'transaction_types',
val
? Array.isArray(val)
? val.map((item) => item.value).join(',')
: (val.value as string)
: '',
}));
: ''
);
};
const bankChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedBank(val);
setPendingFilters((prev) => ({
...prev,
bankIds: val
filterFormik.setFieldValue(
'bank_ids',
val
? Array.isArray(val)
? val.map((item) => item.value).join(',')
: (val.value as string)
: '',
}));
: ''
);
};
const customerIdChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedCustomerId(val);
setPendingFilters((prev) => ({
...prev,
customerIds: val
filterFormik.setFieldValue(
'customer_ids',
val
? Array.isArray(val)
? val.map((item) => item.value).join(',')
: (val.value as string)
: '',
}));
: ''
);
};
const supplierIdChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedSupplierId(val);
setPendingFilters((prev) => ({
...prev,
supplierIds: val
filterFormik.setFieldValue(
'supplier_ids',
val
? Array.isArray(val)
? val.map((item) => item.value).join(',')
: (val.value as string)
: '',
}));
: ''
);
};
const sortByChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedSortBy(val as OptionType);
setPendingFilters((prev) => ({
...prev,
sortBy: val ? ((val as OptionType).value as string) : '',
}));
};
const startDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setPendingFilters((prev) => ({ ...prev, startDate: e.target.value }));
};
const endDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setPendingFilters((prev) => ({ ...prev, endDate: e.target.value }));
filterFormik.setFieldValue(
'sort_by',
val ? ((val as OptionType).value as string) : ''
);
};
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
const newVal = val as OptionType;
setPageSize(newVal.value as number);
};
const submitFilterHandler = () => {
updateFilter('search', pendingFilters.search);
setSearchValue(pendingFilters.search);
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);
};
const resetFilterHandler = () => {
setSelectedTransactionType(null);
setSelectedBank(null);
@@ -322,17 +322,7 @@ const FinanceTable = () => {
setSelectedSupplierId(null);
setSelectedSortBy(null);
const emptyFilters = {
search: '',
transactionTypes: '',
bankIds: '',
customerIds: '',
supplierIds: '',
sortBy: '',
startDate: '',
endDate: '',
};
setPendingFilters(emptyFilters);
filterFormik.resetForm();
updateFilter('search', '');
resetSearchValue();
@@ -526,7 +516,7 @@ const FinanceTable = () => {
<Button
color='primary'
className='min-w-24'
onClick={submitFilterHandler}
onClick={() => filterFormik.handleSubmit()}
>
Cari
</Button>
@@ -601,22 +591,34 @@ const FinanceTable = () => {
isClearable
/>
<DateInput
name='startDate'
name='start_date'
label='Periode Tanggal (Mulai)'
value={pendingFilters.startDate}
onChange={startDateChangeHandler}
value={filterFormik.values.start_date}
onChange={filterFormik.handleChange}
errorMessage={
filterFormik.touched.start_date && filterFormik.errors.start_date
? filterFormik.errors.start_date
: undefined
}
onBlur={filterFormik.handleBlur}
/>
<DateInput
name='endDate'
name='end_date'
label='Periode Tanggal (Akhir)'
value={pendingFilters.endDate}
onChange={endDateChangeHandler}
value={filterFormik.values.end_date}
onChange={filterFormik.handleChange}
errorMessage={
filterFormik.touched.end_date && filterFormik.errors.end_date
? filterFormik.errors.end_date
: undefined
}
onBlur={filterFormik.handleBlur}
/>
<DebouncedTextInput
name='search'
label='Cari'
placeholder='Cari'
value={pendingFilters.search}
value={filterFormik.values.search}
onChange={searchChangeHandler}
/>
</div>
@@ -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<FinanceTableFilterType>;
export type FinanceTableFilterValues = yup.InferType<
typeof FinanceTableFilterSchema
>;