Merge branch 'fix/purchase-form' into 'development'

[FIX/FE] Purchase Form & Expense Filter

See merge request mbugroup/lti-web-client!421
This commit is contained in:
Rivaldi A N S
2026-04-22 07:01:46 +00:00
4 changed files with 86 additions and 39 deletions
+46 -8
View File
@@ -51,7 +51,9 @@ type ExpenseTableFilters = {
transactionDate: string; transactionDate: string;
realizationDate: string; realizationDate: string;
locationId: string; locationId: string;
locationName: string;
vendorId: string; vendorId: string;
vendorName: string;
userId: string; userId: string;
}; };
@@ -235,6 +237,7 @@ const ExpensesTable = () => {
setPage, setPage,
setPageSize, setPageSize,
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
reset: resetFilter,
} = useTableFilter<ExpenseTableFilters>({ } = useTableFilter<ExpenseTableFilters>({
initial: { initial: {
page: 1, page: 1,
@@ -244,7 +247,9 @@ const ExpensesTable = () => {
transactionDate: '', transactionDate: '',
realizationDate: '', realizationDate: '',
locationId: '', locationId: '',
locationName: '',
vendorId: '', vendorId: '',
vendorName: '',
userId: '', userId: '',
}, },
paramMap: { paramMap: {
@@ -254,7 +259,9 @@ const ExpensesTable = () => {
transactionDate: 'transaction_date', transactionDate: 'transaction_date',
realizationDate: 'realization_date', realizationDate: 'realization_date',
locationId: 'location_id', locationId: 'location_id',
locationName: 'location_name',
vendorId: 'vendor_id', vendorId: 'vendor_id',
vendorName: 'vendor_name',
userId: 'user_id', userId: 'user_id',
}, },
@@ -740,20 +747,31 @@ const ExpensesTable = () => {
const handleFilterSubmit = (values: { const handleFilterSubmit = (values: {
transaction_date?: string | null; transaction_date?: string | null;
realization_date?: string | null; realization_date?: string | null;
location_id?: string | null; location?: { value: number; label: string } | null;
vendor_id?: string | null; vendor?: { value: number; label: string } | null;
}) => { }) => {
updateFilter('transactionDate', values.transaction_date || ''); updateFilter('transactionDate', values.transaction_date || '');
updateFilter('realizationDate', values.realization_date || ''); updateFilter('realizationDate', values.realization_date || '');
updateFilter('locationId', values.location_id || ''); updateFilter(
updateFilter('vendorId', values.vendor_id || ''); 'locationId',
values.location?.value ? String(values.location?.value) : ''
);
updateFilter(
'locationName',
values.location?.label ? String(values.location?.label) : ''
);
updateFilter(
'vendorId',
values.vendor?.value ? String(values.vendor?.value) : ''
);
updateFilter(
'vendorName',
values.vendor?.label ? String(values.vendor?.label) : ''
);
}; };
const handleFilterReset = () => { const handleFilterReset = () => {
updateFilter('transactionDate', ''); resetFilter();
updateFilter('realizationDate', '');
updateFilter('locationId', '');
updateFilter('vendorId', '');
}; };
// track sorting // track sorting
@@ -927,6 +945,8 @@ const ExpensesTable = () => {
'search', 'search',
'nameSort', 'nameSort',
'userId', 'userId',
'locationName',
'vendorName',
]} ]}
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
className='px-3 py-2.5' className='px-3 py-2.5'
@@ -1245,6 +1265,24 @@ const ExpensesTable = () => {
ref={filterModal.ref} ref={filterModal.ref}
onSubmit={handleFilterSubmit} onSubmit={handleFilterSubmit}
onReset={handleFilterReset} onReset={handleFilterReset}
initialValues={{
location:
tableFilterState.locationId && tableFilterState.locationName
? {
value: Number(tableFilterState.locationId),
label: tableFilterState.locationName,
}
: null,
vendor:
tableFilterState.vendorId && tableFilterState.vendorName
? {
value: Number(tableFilterState.vendorId),
label: tableFilterState.vendorName,
}
: null,
realization_date: tableFilterState.realizationDate,
transaction_date: tableFilterState.transactionDate,
}}
/> />
</> </>
); );
@@ -3,8 +3,8 @@ import * as yup from 'yup';
export type ExpensesFilterType = { export type ExpensesFilterType = {
transaction_date: string | null; transaction_date: string | null;
realization_date: string | null; realization_date: string | null;
location_id: string | null; location: { value: number; label: string } | null;
vendor_id: string | null; vendor: { value: number; label: string } | null;
}; };
export const ExpensesFilterSchema = yup.object({ export const ExpensesFilterSchema = yup.object({
@@ -21,8 +21,18 @@ export const ExpensesFilterSchema = yup.object({
return new Date(value) >= new Date(transaction_date); return new Date(value) >= new Date(transaction_date);
} }
), ),
location_id: yup.string().nullable(), location: yup
vendor_id: yup.string().nullable(), .object({
value: yup.number().required(),
label: yup.string().required(),
})
.nullable(),
vendor: yup
.object({
value: yup.number().required(),
label: yup.string().required(),
})
.nullable(),
}); });
export type ExpensesFilterValues = yup.InferType<typeof ExpensesFilterSchema>; export type ExpensesFilterValues = yup.InferType<typeof ExpensesFilterSchema>;
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { RefObject } from 'react'; import { RefObject, useCallback } from 'react';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
@@ -39,54 +39,51 @@ const ExpensesFilterModal = ({
setInputValue: setLocationInputValue, setInputValue: setLocationInputValue,
options: locationOptions, options: locationOptions,
isLoadingOptions: isLoadingLocationOptions, isLoadingOptions: isLoadingLocationOptions,
loadMore: loadMoreLocations,
} = useSelect<Location>(LocationApi.basePath, 'id', 'name'); } = useSelect<Location>(LocationApi.basePath, 'id', 'name');
const { const {
setInputValue: setVendorInputValue, setInputValue: setVendorInputValue,
options: vendorOptions, options: vendorOptions,
isLoadingOptions: isLoadingVendorOptions, isLoadingOptions: isLoadingVendorOptions,
loadMore: loadMoreVendors,
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name'); } = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
const formik = useFormik<ExpensesFilterValues>({ const formik = useFormik<ExpensesFilterValues>({
initialValues: initialValues || { initialValues: initialValues || {
transaction_date: null, transaction_date: null,
realization_date: null, realization_date: null,
location_id: null, location: null,
vendor_id: null, vendor: null,
}, },
validationSchema: ExpensesFilterSchema, validationSchema: ExpensesFilterSchema,
onSubmit: async (values) => { onSubmit: async (values) => {
onSubmit?.(values); onSubmit?.(values);
closeModalHandler(); closeModalHandler();
}, },
onReset: () => {
onReset?.();
closeModalHandler();
},
}); });
const locationValue = formik.values.location_id const { resetForm } = formik;
? locationOptions.find(
(opt) => String(opt.value) === formik.values.location_id
) || null
: null;
const vendorValue = formik.values.vendor_id const formikResetHandler = useCallback(() => {
? vendorOptions.find( resetForm({
(opt) => String(opt.value) === formik.values.vendor_id values: {
) || null transaction_date: null,
: null; realization_date: null,
location: null,
vendor: null,
},
});
onReset?.();
closeModalHandler();
}, [resetForm, onReset, closeModalHandler]);
const locationChangeHandler = (val: OptionType | OptionType[] | null) => { const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
const locationId = formik.setFieldValue('location', val as OptionType | null);
val && !Array.isArray(val) ? (String(val.value) as string) : null;
formik.setFieldValue('location_id', locationId);
}; };
const vendorChangeHandler = (val: OptionType | OptionType[] | null) => { const vendorChangeHandler = (val: OptionType | OptionType[] | null) => {
const vendorId = formik.setFieldValue('vendor', val as OptionType | null);
val && !Array.isArray(val) ? (String(val.value) as string) : null;
formik.setFieldValue('vendor_id', vendorId);
}; };
return ( return (
@@ -98,7 +95,7 @@ const ExpensesFilterModal = ({
> >
<form <form
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
onReset={formik.handleReset} onReset={formikResetHandler}
className='w-full flex flex-col' className='w-full flex flex-col'
> >
{/* Modal Header */} {/* Modal Header */}
@@ -160,10 +157,11 @@ const ExpensesFilterModal = ({
label='Lokasi' label='Lokasi'
placeholder='Pilih Lokasi' placeholder='Pilih Lokasi'
options={locationOptions} options={locationOptions}
value={locationValue} value={formik.values.location}
onChange={locationChangeHandler} onChange={locationChangeHandler}
onInputChange={setLocationInputValue} onInputChange={setLocationInputValue}
isLoading={isLoadingLocationOptions} isLoading={isLoadingLocationOptions}
onMenuScrollToBottom={loadMoreLocations}
isClearable isClearable
isSearchable={true} isSearchable={true}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
@@ -173,10 +171,11 @@ const ExpensesFilterModal = ({
label='Vendor' label='Vendor'
placeholder='Pilih Vendor' placeholder='Pilih Vendor'
options={vendorOptions} options={vendorOptions}
value={vendorValue} value={formik.values.vendor}
onChange={vendorChangeHandler} onChange={vendorChangeHandler}
onInputChange={setVendorInputValue} onInputChange={setVendorInputValue}
isLoading={isLoadingVendorOptions} isLoading={isLoadingVendorOptions}
onMenuScrollToBottom={loadMoreVendors}
isClearable isClearable
isSearchable={true} isSearchable={true}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
@@ -55,7 +55,6 @@ const PurchaseRequestForm = ({
const deleteModal = useModal(); const deleteModal = useModal();
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [, setLocationSelectInputValue] = useState('');
const [selectedPurchaseItems, setSelectedPurchaseItems] = useState<number[]>( const [selectedPurchaseItems, setSelectedPurchaseItems] = useState<number[]>(
[] []
); );
@@ -163,6 +162,7 @@ const PurchaseRequestForm = ({
options: locationOptions, options: locationOptions,
isLoadingOptions: isLoadingLocations, isLoadingOptions: isLoadingLocations,
loadMore: loadMoreLocations, loadMore: loadMoreLocations,
setInputValue: setLocationSelectInputValue,
} = useSelect(LocationApi.basePath, 'id', 'name', '', { } = useSelect(LocationApi.basePath, 'id', 'name', '', {
area_id: area_id:
selectedArea != '' selectedArea != ''