Merge branch 'fix/adjustment-param-state-keuangan' into 'development'

[FIX/FE] Adjustment Param State on Fetch Keuangan

See merge request mbugroup/lti-web-client!311
This commit is contained in:
Rivaldi A N S
2026-02-05 04:23:21 +00:00
2 changed files with 223 additions and 113 deletions
+185 -113
View File
@@ -1,13 +1,8 @@
import { import { useEffect, useMemo, useRef, useState } from 'react';
ChangeEventHandler,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { CellContext } from '@tanstack/react-table'; import { CellContext } from '@tanstack/react-table';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { useFormik } from 'formik';
import Button from '@/components/Button'; import Button from '@/components/Button';
import Card from '@/components/Card'; import Card from '@/components/Card';
@@ -40,6 +35,10 @@ import { Icon } from '@iconify/react';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import { useUiStore } from '@/stores/ui/ui.store'; import { useUiStore } from '@/stores/ui/ui.store';
import {
FinanceTableFilterSchema,
FinanceTableFilterValues,
} from './FinanceTableFilter.schema';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
type = 'dropdown', type = 'dropdown',
@@ -152,10 +151,10 @@ const FinanceTable = () => {
} = useTableFilter({ } = useTableFilter({
initial: { initial: {
search: searchValue, search: searchValue,
transactionType: '', transactionTypes: '',
bankId: '', bankIds: '',
customerId: '', customerIds: '',
supplierId: '', supplierIds: '',
sortBy: '', sortBy: '',
startDate: '', startDate: '',
endDate: '', endDate: '',
@@ -163,10 +162,10 @@ const FinanceTable = () => {
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
transactionType: 'transaction_type', transactionTypes: 'transaction_types',
bankId: 'bank_id', bankIds: 'bank_ids',
customerId: 'customer_id', customerIds: 'customer_ids',
supplierId: 'supplier_id', supplierIds: 'supplier_ids',
sortBy: 'sort_date', sortBy: 'sort_date',
startDate: 'start_date', startDate: 'start_date',
endDate: 'end_date', endDate: 'end_date',
@@ -174,18 +173,7 @@ const FinanceTable = () => {
}); });
// ===== State ===== // ===== State =====
const [searchParams, setSearchParams] = useSearchParams();
const deleteModal = useModal(); const deleteModal = useModal();
const [pendingFilters, setPendingFilters] = useState({
search: searchValue,
transactionType: '',
bankId: '',
customerId: '',
supplierId: '',
sortBy: '',
startDate: '',
endDate: '',
});
const [selectedTransactionType, setSelectedTransactionType] = useState< const [selectedTransactionType, setSelectedTransactionType] = useState<
OptionType | OptionType[] | null OptionType | OptionType[] | null
>(null); >(null);
@@ -201,6 +189,34 @@ const FinanceTable = () => {
const [selectedSortBy, setSelectedSortBy] = useState<OptionType | null>(null); const [selectedSortBy, setSelectedSortBy] = useState<OptionType | null>(null);
const [selectedFinance, setSelectedFinance] = useState<Finance | null>(null); const [selectedFinance, setSelectedFinance] = useState<Finance | null>(null);
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [dateErrorShown, setDateErrorShown] = 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 ===== // ===== Fetch Data =====
const { const {
@@ -237,84 +253,141 @@ const FinanceTable = () => {
loadMore: bankLoadMore, loadMore: bankLoadMore,
} = useSelect<Bank>(BankApi.basePath, 'id', 'alias'); } = useSelect<Bank>(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 ===== // ===== Handler =====
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setPendingFilters((prev) => ({ ...prev, search: e.target.value })); filterFormik.setFieldValue('search', e.target.value);
}; };
const transactionTypeChangeHandler = ( const transactionTypeChangeHandler = (
val: OptionType | OptionType[] | null val: OptionType | OptionType[] | null
) => { ) => {
setSelectedTransactionType(val); setSelectedTransactionType(val);
setPendingFilters((prev) => ({ filterFormik.setFieldValue(
...prev, 'transaction_types',
transactionType: val val
? Array.isArray(val) ? Array.isArray(val)
? val.map((item) => item.value).join(',') ? val.map((item) => item.value).join(',')
: (val.value as string) : (val.value as string)
: '', : ''
})); );
}; };
const bankChangeHandler = (val: OptionType | OptionType[] | null) => { const bankChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedBank(val); setSelectedBank(val);
setPendingFilters((prev) => ({ filterFormik.setFieldValue(
...prev, 'bank_ids',
bankId: val val
? Array.isArray(val) ? Array.isArray(val)
? val.map((item) => item.value).join(',') ? val.map((item) => item.value).join(',')
: (val.value as string) : (val.value as string)
: '', : ''
})); );
}; };
const customerIdChangeHandler = (val: OptionType | OptionType[] | null) => { const customerIdChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedCustomerId(val); setSelectedCustomerId(val);
setPendingFilters((prev) => ({ filterFormik.setFieldValue(
...prev, 'customer_ids',
customerId: val val
? Array.isArray(val) ? Array.isArray(val)
? val.map((item) => item.value).join(',') ? val.map((item) => item.value).join(',')
: (val.value as string) : (val.value as string)
: '', : ''
})); );
}; };
const supplierIdChangeHandler = (val: OptionType | OptionType[] | null) => { const supplierIdChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedSupplierId(val); setSelectedSupplierId(val);
setPendingFilters((prev) => ({ filterFormik.setFieldValue(
...prev, 'supplier_ids',
supplierId: val val
? Array.isArray(val) ? Array.isArray(val)
? val.map((item) => item.value).join(',') ? val.map((item) => item.value).join(',')
: (val.value as string) : (val.value as string)
: '', : ''
})); );
}; };
const sortByChangeHandler = (val: OptionType | OptionType[] | null) => { const sortByChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedSortBy(val as OptionType); setSelectedSortBy(val as OptionType);
setPendingFilters((prev) => ({ filterFormik.setFieldValue(
...prev, 'sort_by',
sortBy: val ? ((val as OptionType).value as string) : '', val ? ((val as OptionType).value as string) : ''
})); );
}; };
const startDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setPendingFilters((prev) => ({ ...prev, startDate: e.target.value })); const startDateChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
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: ChangeEventHandler<HTMLInputElement> = (e) => {
setPendingFilters((prev) => ({ ...prev, endDate: e.target.value })); const endDateChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
}; const value = e.target.value;
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { const startDate = filterFormik.values.start_date;
const newVal = val as OptionType;
setPageSize(newVal.value as number); filterFormik.setFieldValue('end_date', value);
};
const submitFilterHandler = () => { if (value && startDate) {
updateFilter('search', pendingFilters.search); const startDateObj = new Date(startDate);
setSearchValue(pendingFilters.search); const endDate = new Date(value);
updateFilter('transactionType', pendingFilters.transactionType);
updateFilter('bankId', pendingFilters.bankId); if (endDate < startDateObj) {
updateFilter('customerId', pendingFilters.customerId); filterFormik.setFieldError(
updateFilter('supplierId', pendingFilters.supplierId); 'end_date',
updateFilter('sortBy', pendingFilters.sortBy); 'Tanggal akhir tidak boleh masa lampau'
updateFilter('startDate', pendingFilters.startDate); );
updateFilter('endDate', pendingFilters.endDate); 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 = () => { const resetFilterHandler = () => {
setSelectedTransactionType(null); setSelectedTransactionType(null);
setSelectedBank(null); setSelectedBank(null);
@@ -322,24 +395,14 @@ const FinanceTable = () => {
setSelectedSupplierId(null); setSelectedSupplierId(null);
setSelectedSortBy(null); setSelectedSortBy(null);
const emptyFilters = { filterFormik.resetForm();
search: '',
transactionType: '',
bankId: '',
customerId: '',
supplierId: '',
sortBy: '',
startDate: '',
endDate: '',
};
setPendingFilters(emptyFilters);
updateFilter('search', ''); updateFilter('search', '');
resetSearchValue(); resetSearchValue();
updateFilter('transactionType', ''); updateFilter('transactionTypes', '');
updateFilter('bankId', ''); updateFilter('bankIds', '');
updateFilter('customerId', ''); updateFilter('customerIds', '');
updateFilter('supplierId', ''); updateFilter('supplierIds', '');
updateFilter('sortBy', ''); updateFilter('sortBy', '');
updateFilter('startDate', ''); updateFilter('startDate', '');
updateFilter('endDate', ''); updateFilter('endDate', '');
@@ -464,26 +527,36 @@ const FinanceTable = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
// Store current path on mount return () => {
if (dateErrorShown) {
toast.dismiss();
}
};
}, [dateErrorShown]);
useEffect(() => {
previousPathRef.current = window.location.pathname; previousPathRef.current = window.location.pathname;
return () => { return () => {
const currentPath = window.location.pathname; const currentPath = window.location.pathname;
// if both paths are within /finance module
const isCurrentPathFinance = currentPath.includes('/finance'); const isCurrentPathFinance = currentPath.includes('/finance');
const isPreviousPathFinance = const isPreviousPathFinance =
previousPathRef.current?.includes('/finance'); previousPathRef.current?.includes('/finance');
// reset if we outside finance module entirely
if (isPreviousPathFinance && !isCurrentPathFinance) { if (isPreviousPathFinance && !isCurrentPathFinance) {
resetSearchValue(); resetSearchValue();
} }
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}; };
}, [resetSearchValue]); }, [resetSearchValue, dateErrorShown]);
return ( return (
<section className='size-full p-6 flex flex-col gap-6'> <section className='size-full flex flex-col gap-6'>
<div className='flex justify-end gap-2'> <div className='flex justify-end gap-2'>
<RequirePermission permissions='lti.finance.injections.create'> <RequirePermission permissions='lti.finance.injections.create'>
<Button <Button
@@ -526,7 +599,7 @@ const FinanceTable = () => {
<Button <Button
color='primary' color='primary'
className='min-w-24' className='min-w-24'
onClick={submitFilterHandler} onClick={() => filterFormik.handleSubmit()}
> >
Cari Cari
</Button> </Button>
@@ -539,6 +612,7 @@ const FinanceTable = () => {
label='Jenis Transaksi' label='Jenis Transaksi'
value={selectedTransactionType} value={selectedTransactionType}
onChange={transactionTypeChangeHandler} onChange={transactionTypeChangeHandler}
closeMenuOnSelect={false}
isClearable isClearable
isMulti isMulti
/> />
@@ -550,6 +624,7 @@ const FinanceTable = () => {
onInputChange={customerInputValue} onInputChange={customerInputValue}
onMenuScrollToBottom={customerLoadMore} onMenuScrollToBottom={customerLoadMore}
isLoading={customerIsLoadingOptions} isLoading={customerIsLoadingOptions}
closeMenuOnSelect={false}
isClearable isClearable
isMulti isMulti
/> />
@@ -561,31 +636,18 @@ const FinanceTable = () => {
onInputChange={supplierInputValue} onInputChange={supplierInputValue}
onMenuScrollToBottom={supplierLoadMore} onMenuScrollToBottom={supplierLoadMore}
isLoading={supplierIsLoadingOptions} isLoading={supplierIsLoadingOptions}
closeMenuOnSelect={false}
isClearable isClearable
isMulti isMulti
/> />
<SelectInput <SelectInput
options={ options={bankSelectOptions}
isResponseSuccess(bankRawData)
? bankOptions.map((bank) => ({
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,
}))
: []
}
label='Bank' label='Bank'
value={selectedBank} value={selectedBank}
onChange={bankChangeHandler} onChange={bankChangeHandler}
onInputChange={bankInputValue} onInputChange={bankInputValue}
onMenuScrollToBottom={bankLoadMore} onMenuScrollToBottom={bankLoadMore}
closeMenuOnSelect={false}
isClearable isClearable
isMulti isMulti
/> />
@@ -597,22 +659,32 @@ const FinanceTable = () => {
isClearable isClearable
/> />
<DateInput <DateInput
name='startDate' name='start_date'
label='Periode Tanggal (Mulai)' label='Periode Tanggal (Mulai)'
value={pendingFilters.startDate} value={filterFormik.values.start_date}
onChange={startDateChangeHandler} onChange={startDateChangeHandler}
errorMessage={
filterFormik.errors.end_date
? filterFormik.errors.end_date
: undefined
}
/> />
<DateInput <DateInput
name='endDate' name='end_date'
label='Periode Tanggal (Akhir)' label='Periode Tanggal (Akhir)'
value={pendingFilters.endDate} value={filterFormik.values.end_date}
onChange={endDateChangeHandler} onChange={endDateChangeHandler}
errorMessage={
filterFormik.errors.end_date
? filterFormik.errors.end_date
: undefined
}
/> />
<DebouncedTextInput <DebouncedTextInput
name='search' name='search'
label='Cari' label='Cari'
placeholder='Cari' placeholder='Cari'
value={pendingFilters.search} value={filterFormik.values.search}
onChange={searchChangeHandler} onChange={searchChangeHandler}
/> />
</div> </div>
@@ -0,0 +1,38 @@
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
>;