mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): Refactor ExpensesTable to use ExpensesFilterModal
This commit is contained in:
@@ -16,10 +16,6 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||
import Button from '@/components/Button';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
@@ -27,17 +23,15 @@ import RealizationStatusBadge from '@/components/pages/expense/RealizationStatus
|
||||
import ExpenseStatusBadge from '@/components/pages/expense/ExpenseStatusBadge';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||
import ExpensesFilterModal from '@/components/pages/expense/filter/ExpensesFilterModal';
|
||||
|
||||
import { Expense } from '@/types/api/expense';
|
||||
import { ExpenseApi } from '@/services/api/expense';
|
||||
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import { LocationApi, SupplierApi } from '@/services/api/master-data';
|
||||
import { Location } from '@/types/api/master-data/location';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
import { BaseApiResponse } from '@/types/api/api-general';
|
||||
|
||||
const RowOptionsMenu = ({
|
||||
@@ -179,6 +173,9 @@ const ExpensesTable = () => {
|
||||
const approveModal = useModal();
|
||||
const rejectModal = useModal();
|
||||
|
||||
// ===== FILTER MODAL STATE =====
|
||||
const filterModal = useModal();
|
||||
|
||||
const [selectedExpense, setSelectedExpense] = useState<Expense | undefined>(
|
||||
undefined
|
||||
);
|
||||
@@ -535,51 +532,32 @@ const ExpensesTable = () => {
|
||||
setIsRejectLoading(false);
|
||||
};
|
||||
|
||||
const {
|
||||
setInputValue: setLocationInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocationOptions,
|
||||
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||
|
||||
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
setSelectedLocation(val as OptionType);
|
||||
updateFilter(
|
||||
'locationId',
|
||||
val ? ((val as OptionType).value as string) : ''
|
||||
);
|
||||
};
|
||||
|
||||
const {
|
||||
setInputValue: setVendorInputValue,
|
||||
options: vendorOptions,
|
||||
isLoadingOptions: isLoadingVendorOptions,
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||
|
||||
const [selectedVendor, setSelectedVendor] = useState<OptionType | null>(null);
|
||||
|
||||
const vendorChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
setSelectedVendor(val as OptionType);
|
||||
updateFilter('vendorId', val ? ((val as OptionType).value as string) : '');
|
||||
};
|
||||
|
||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
updateFilter('search', e.target.value);
|
||||
};
|
||||
|
||||
const transactionDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (
|
||||
e
|
||||
) => {
|
||||
updateFilter('transactionDate', e.target.value);
|
||||
// ===== FILTER MODAL HANDLERS =====
|
||||
const handleFilterModalOpen = () => {
|
||||
filterModal.openModal();
|
||||
};
|
||||
|
||||
const realizationDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (
|
||||
e
|
||||
) => {
|
||||
updateFilter('realizationDate', e.target.value);
|
||||
const handleFilterSubmit = (values: {
|
||||
transaction_date?: string | null;
|
||||
realization_date?: string | null;
|
||||
location_id?: string | null;
|
||||
vendor_id?: string | null;
|
||||
}) => {
|
||||
updateFilter('transactionDate', values.transaction_date || '');
|
||||
updateFilter('realizationDate', values.realization_date || '');
|
||||
updateFilter('locationId', values.location_id || '');
|
||||
updateFilter('vendorId', values.vendor_id || '');
|
||||
};
|
||||
|
||||
const handleFilterReset = () => {
|
||||
updateFilter('transactionDate', '');
|
||||
updateFilter('realizationDate', '');
|
||||
updateFilter('locationId', '');
|
||||
updateFilter('vendorId', '');
|
||||
};
|
||||
|
||||
// track sorting
|
||||
@@ -595,16 +573,15 @@ const ExpensesTable = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-4'>
|
||||
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
||||
<div className='w-full'>
|
||||
<div className='w-full p-3 flex flex-row justify-between gap-3 flex-wrap border-b border-base-content/10'>
|
||||
{/* Action Buttons */}
|
||||
<div className='w-fit flex flex-row gap-3 flex-wrap'>
|
||||
<RequirePermission permissions='lti.expense.create'>
|
||||
<Button
|
||||
href='/expense/add'
|
||||
color='primary'
|
||||
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-lg shadow-sm'
|
||||
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<Icon icon='heroicons:plus' width={20} height={20} />
|
||||
Add Expense
|
||||
@@ -613,15 +590,22 @@ const ExpensesTable = () => {
|
||||
|
||||
{selectedRowIds.length > 0 && (
|
||||
<>
|
||||
<hr className='w-px h-full border-none bg-base-content/10 sm:block hidden' />
|
||||
|
||||
<RequirePermission permissions='lti.expense.approve.head_area'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='info'
|
||||
color='none'
|
||||
onClick={bulkApproveClickHandler}
|
||||
disabled={!isAllSelectedRowLatestApprovalOnHeadArea}
|
||||
className='w-full sm:w-fit'
|
||||
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<Icon icon='lucide-lab:farm' width={24} height={24} />
|
||||
<Icon
|
||||
icon='lucide-lab:farm'
|
||||
width={20}
|
||||
height={20}
|
||||
className='text-success'
|
||||
/>
|
||||
Approve Head Area
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
@@ -629,14 +613,19 @@ const ExpensesTable = () => {
|
||||
<RequirePermission permissions='lti.expense.approve.unit_vice_president'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='success'
|
||||
color='none'
|
||||
onClick={bulkApproveClickHandler}
|
||||
disabled={
|
||||
!isAllSelectedRowLatestApprovalOnUnitVicePresident
|
||||
}
|
||||
className='w-full sm:w-fit'
|
||||
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<Icon icon='tdesign:money' width={24} height={24} />
|
||||
<Icon
|
||||
icon='tdesign:money'
|
||||
width={20}
|
||||
height={20}
|
||||
className='text-success'
|
||||
/>
|
||||
Approve Unit Vice President
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
@@ -644,12 +633,17 @@ const ExpensesTable = () => {
|
||||
<RequirePermission permissions='lti.expense.approve.finance'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='success'
|
||||
color='none'
|
||||
onClick={bulkApproveClickHandler}
|
||||
disabled={!isAllSelectedRowLatestApprovalOnFinance}
|
||||
className='w-full sm:w-fit'
|
||||
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<Icon icon='tdesign:money' width={24} height={24} />
|
||||
<Icon
|
||||
icon='tdesign:money'
|
||||
width={20}
|
||||
height={20}
|
||||
className='text-success'
|
||||
/>
|
||||
Approve Finance
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
@@ -663,19 +657,20 @@ const ExpensesTable = () => {
|
||||
>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='error'
|
||||
color='none'
|
||||
onClick={bulkRejectClickHandler}
|
||||
disabled={
|
||||
!isAllSelectedRowLatestApprovalOnHeadArea &&
|
||||
!isAllSelectedRowLatestApprovalOnUnitVicePresident &&
|
||||
!isAllSelectedRowLatestApprovalOnFinance
|
||||
}
|
||||
className='w-full sm:w-fit'
|
||||
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:close'
|
||||
width={24}
|
||||
height={24}
|
||||
width={20}
|
||||
height={20}
|
||||
className='text-error'
|
||||
/>
|
||||
Reject
|
||||
</Button>
|
||||
@@ -683,70 +678,46 @@ const ExpensesTable = () => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-12 justify-end gap-2'>
|
||||
<DateInput
|
||||
required
|
||||
label='Tanggal Transaksi'
|
||||
name='transaction_date'
|
||||
placeholder='Masukkan tanggal transaksi'
|
||||
value={tableFilterState.transactionDate}
|
||||
onChange={transactionDateChangeHandler}
|
||||
className={{
|
||||
wrapper: 'col-span-12 sm:col-span-3',
|
||||
}}
|
||||
/>
|
||||
|
||||
<DateInput
|
||||
required
|
||||
label='Tanggal Realisasi'
|
||||
name='realization_date'
|
||||
placeholder='Masukkan tanggal realisasi'
|
||||
value={tableFilterState.realizationDate}
|
||||
onChange={realizationDateChangeHandler}
|
||||
className={{
|
||||
wrapper: 'col-span-12 sm:col-span-3',
|
||||
}}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
label='Lokasi'
|
||||
options={locationOptions}
|
||||
isLoading={isLoadingLocationOptions}
|
||||
value={selectedLocation}
|
||||
onChange={locationChangeHandler}
|
||||
onInputChange={setLocationInputValue}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'col-span-12 sm:col-span-3',
|
||||
}}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
label='Vendor'
|
||||
options={vendorOptions}
|
||||
isLoading={isLoadingVendorOptions}
|
||||
value={selectedVendor}
|
||||
onChange={vendorChangeHandler}
|
||||
onInputChange={setVendorInputValue}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'col-span-12 sm:col-span-3',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Search and Filter */}
|
||||
<div className='flex flex-1 flex-row justify-start sm:justify-end items-center gap-3 flex-wrap'>
|
||||
<DebouncedTextInput
|
||||
name='search'
|
||||
placeholder='Cari Biaya Operasional'
|
||||
value={tableFilterState.search}
|
||||
placeholder='Search'
|
||||
value={tableFilterState.search ?? ''}
|
||||
onChange={searchChangeHandler}
|
||||
className={{ wrapper: 'col-span-12 max-w-52 justify-self-end' }}
|
||||
startAdornment={
|
||||
<Icon
|
||||
icon='heroicons:magnifying-glass'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-24 max-w-3xs',
|
||||
inputWrapper: 'rounded-xl! shadow-button-soft',
|
||||
input:
|
||||
'placeholder:font-semibold placeholder:text-base-content/50',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ButtonFilter
|
||||
values={tableFilterState}
|
||||
excludeFields={[
|
||||
'page',
|
||||
'pageSize',
|
||||
'search',
|
||||
'nameSort',
|
||||
'userId',
|
||||
]}
|
||||
onClick={handleFilterModalOpen}
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table Section */}
|
||||
<div className='flex flex-col mb-4 -mx-4 px-4'>
|
||||
<Table<Expense>
|
||||
data={isResponseSuccess(expenses) ? expenses?.data : []}
|
||||
columns={expensesColumns}
|
||||
@@ -756,6 +727,7 @@ const ExpensesTable = () => {
|
||||
isResponseSuccess(expenses) ? expenses?.meta?.total_results : 0
|
||||
}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
isLoading={isLoading}
|
||||
sorting={sorting}
|
||||
setSorting={setSorting}
|
||||
@@ -763,21 +735,15 @@ const ExpensesTable = () => {
|
||||
setRowSelection={setRowSelection}
|
||||
enableRowSelection={tableEnableRowSelectionHandler}
|
||||
className={{
|
||||
containerClassName: cn({
|
||||
'mb-20':
|
||||
containerClassName: cn('p-3 mb-0', {
|
||||
'w-full':
|
||||
isResponseSuccess(expenses) && expenses?.data?.length === 0,
|
||||
}),
|
||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||
headerRowClassName: 'border-b border-b-gray-200',
|
||||
headerColumnClassName:
|
||||
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
||||
bodyRowClassName: 'border-b border-b-gray-200',
|
||||
bodyColumnClassName:
|
||||
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
||||
headerColumnClassName: 'text-nowrap',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={deleteModal.ref}
|
||||
@@ -831,6 +797,12 @@ const ExpensesTable = () => {
|
||||
onClick: confirmationModalRejectClickHandler,
|
||||
}}
|
||||
/>
|
||||
|
||||
<ExpensesFilterModal
|
||||
ref={filterModal.ref}
|
||||
onSubmit={handleFilterSubmit}
|
||||
onReset={handleFilterReset}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as yup from 'yup';
|
||||
|
||||
export type ExpensesFilterType = {
|
||||
transaction_date: string | null;
|
||||
realization_date: string | null;
|
||||
location_id: string | null;
|
||||
vendor_id: string | null;
|
||||
};
|
||||
|
||||
export const ExpensesFilterSchema = yup.object({
|
||||
transaction_date: yup.string().nullable(),
|
||||
realization_date: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.test(
|
||||
'is-greater-or-equal-transaction',
|
||||
'Tanggal realisasi tidak boleh sebelum tanggal transaksi',
|
||||
function (value) {
|
||||
const { transaction_date } = this.parent;
|
||||
if (!transaction_date || !value) return true;
|
||||
return new Date(value) >= new Date(transaction_date);
|
||||
}
|
||||
),
|
||||
location_id: yup.string().nullable(),
|
||||
vendor_id: yup.string().nullable(),
|
||||
});
|
||||
|
||||
export type ExpensesFilterValues = yup.InferType<typeof ExpensesFilterSchema>;
|
||||
@@ -0,0 +1,206 @@
|
||||
'use client';
|
||||
|
||||
import { RefObject } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
|
||||
import { Icon } from '@iconify/react';
|
||||
import Modal from '@/components/Modal';
|
||||
import Button from '@/components/Button';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
import SelectInput from '@/components/input/SelectInput';
|
||||
|
||||
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||
import { LocationApi, SupplierApi } from '@/services/api/master-data';
|
||||
import { Location } from '@/types/api/master-data/location';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
import {
|
||||
ExpensesFilterSchema,
|
||||
ExpensesFilterValues,
|
||||
} from '@/components/pages/expense/filter/ExpensesFilter';
|
||||
|
||||
interface ExpensesFilterModalProps {
|
||||
ref: RefObject<HTMLDialogElement | null>;
|
||||
initialValues?: ExpensesFilterValues;
|
||||
onSubmit?: (values: Partial<ExpensesFilterValues>) => void;
|
||||
onReset?: () => void;
|
||||
}
|
||||
|
||||
const ExpensesFilterModal = ({
|
||||
ref,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
onReset,
|
||||
}: ExpensesFilterModalProps) => {
|
||||
const closeModalHandler = () => {
|
||||
ref.current?.close();
|
||||
};
|
||||
|
||||
const {
|
||||
setInputValue: setLocationInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocationOptions,
|
||||
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||
|
||||
const {
|
||||
setInputValue: setVendorInputValue,
|
||||
options: vendorOptions,
|
||||
isLoadingOptions: isLoadingVendorOptions,
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||
|
||||
const formik = useFormik<ExpensesFilterValues>({
|
||||
initialValues: initialValues || {
|
||||
transaction_date: null,
|
||||
realization_date: null,
|
||||
location_id: null,
|
||||
vendor_id: null,
|
||||
},
|
||||
validationSchema: ExpensesFilterSchema,
|
||||
onSubmit: async (values) => {
|
||||
onSubmit?.(values);
|
||||
closeModalHandler();
|
||||
},
|
||||
onReset: () => {
|
||||
onReset?.();
|
||||
closeModalHandler();
|
||||
},
|
||||
});
|
||||
|
||||
const locationValue = formik.values.location_id
|
||||
? locationOptions.find(
|
||||
(opt) => String(opt.value) === formik.values.location_id
|
||||
) || null
|
||||
: null;
|
||||
|
||||
const vendorValue = formik.values.vendor_id
|
||||
? vendorOptions.find(
|
||||
(opt) => String(opt.value) === formik.values.vendor_id
|
||||
) || null
|
||||
: null;
|
||||
|
||||
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
const locationId =
|
||||
val && !Array.isArray(val) ? (String(val.value) as string) : null;
|
||||
formik.setFieldValue('location_id', locationId);
|
||||
};
|
||||
|
||||
const vendorChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
const vendorId =
|
||||
val && !Array.isArray(val) ? (String(val.value) as string) : null;
|
||||
formik.setFieldValue('vendor_id', vendorId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
ref={ref}
|
||||
className={{
|
||||
modalBox: 'p-0 rounded-xl xl:max-w-4/12 max-w-sm',
|
||||
}}
|
||||
>
|
||||
<form
|
||||
onSubmit={formik.handleSubmit}
|
||||
onReset={formik.handleReset}
|
||||
className='w-full flex flex-col'
|
||||
>
|
||||
{/* Modal Header */}
|
||||
<div className='p-4 flex items-center justify-between gap-2 border-b border-base-content/10'>
|
||||
<div className='flex items-center gap-2 text-primary'>
|
||||
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||||
<h3 className='text-sm font-medium'>Filter Data</h3>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={closeModalHandler}
|
||||
className='p-0 text-base-content/50 hover:text-base-content'
|
||||
>
|
||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Modal Body */}
|
||||
<div className='p-4 flex flex-col gap-1.5'>
|
||||
<DateInput
|
||||
label='Tanggal Transaksi'
|
||||
name='transaction_date'
|
||||
placeholder='Masukkan tanggal transaksi'
|
||||
value={formik.values.transaction_date || ''}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={
|
||||
formik.touched.transaction_date &&
|
||||
!!formik.errors.transaction_date
|
||||
}
|
||||
/>
|
||||
|
||||
<DateInput
|
||||
label='Tanggal Realisasi'
|
||||
name='realization_date'
|
||||
placeholder='Masukkan tanggal realisasi'
|
||||
value={formik.values.realization_date || ''}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={
|
||||
formik.touched.realization_date &&
|
||||
!!formik.errors.realization_date
|
||||
}
|
||||
/>
|
||||
{formik.touched.realization_date &&
|
||||
formik.errors.realization_date && (
|
||||
<span className='text-xs text-error'>
|
||||
{formik.errors.realization_date}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<SelectInput
|
||||
label='Lokasi'
|
||||
placeholder='Pilih Lokasi'
|
||||
options={locationOptions}
|
||||
value={locationValue}
|
||||
onChange={locationChangeHandler}
|
||||
onInputChange={setLocationInputValue}
|
||||
isLoading={isLoadingLocationOptions}
|
||||
isClearable
|
||||
isSearchable={true}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
label='Vendor'
|
||||
placeholder='Pilih Vendor'
|
||||
options={vendorOptions}
|
||||
value={vendorValue}
|
||||
onChange={vendorChangeHandler}
|
||||
onInputChange={setVendorInputValue}
|
||||
isLoading={isLoadingVendorOptions}
|
||||
isClearable
|
||||
isSearchable={true}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className='p-4 flex justify-between gap-4 border-t border-base-content/10 bg-gray-50'>
|
||||
<Button
|
||||
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
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type='submit'
|
||||
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
|
||||
disabled={!formik.isValid || formik.isSubmitting}
|
||||
>
|
||||
Apply Filter
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExpensesFilterModal;
|
||||
Reference in New Issue
Block a user