mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat: create PurchaseFilterModal component
This commit is contained in:
@@ -0,0 +1,201 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { RefObject, useState, useEffect } from 'react';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import Modal from '@/components/Modal';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||||
|
|
||||||
|
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||||
|
import { PurchaseFilter } from '@/types/api/purchase/purchase';
|
||||||
|
import { ProductCategory } from '@/types/api/master-data/product-category';
|
||||||
|
import { ProductCategoryApi } from '@/services/api/master-data';
|
||||||
|
import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line';
|
||||||
|
|
||||||
|
interface PurchaseFilterModalProps {
|
||||||
|
ref: RefObject<HTMLDialogElement | null>;
|
||||||
|
onSubmit?: (values: PurchaseFilter) => void;
|
||||||
|
onReset?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PurchaseFilterModal = ({
|
||||||
|
ref,
|
||||||
|
onSubmit,
|
||||||
|
onReset,
|
||||||
|
}: PurchaseFilterModalProps) => {
|
||||||
|
const closeModalHandler = () => {
|
||||||
|
ref.current?.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== DATE ERROR STATE =====
|
||||||
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
|
const [hasDateError, setHasDateError] = useState(false);
|
||||||
|
|
||||||
|
// ===== CLEANUP TOAST ON UNMOUNT =====
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [dateErrorShown]);
|
||||||
|
|
||||||
|
// ===== CLEANUP TOAST WHEN MODAL CLOSES =====
|
||||||
|
useEffect(() => {
|
||||||
|
const dialogElement = ref.current;
|
||||||
|
const handleModalClose = () => {
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dialogElement?.addEventListener('close', handleModalClose);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dialogElement?.removeEventListener('close', handleModalClose);
|
||||||
|
};
|
||||||
|
}, [ref, dateErrorShown]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
setInputValue: setProductCategoryInputValue,
|
||||||
|
options: productCategoryOptions,
|
||||||
|
isLoadingOptions: isLoadingProductCategoryOptions,
|
||||||
|
loadMore: loadMoreProductCategory,
|
||||||
|
} = useSelect<ProductCategory>(
|
||||||
|
ProductCategoryApi.basePath,
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'search'
|
||||||
|
);
|
||||||
|
|
||||||
|
const formik = useFormik<{
|
||||||
|
poDate: string;
|
||||||
|
category: { label: string; value: number }[];
|
||||||
|
status: { label: string; value: string }[];
|
||||||
|
}>({
|
||||||
|
initialValues: {
|
||||||
|
poDate: '',
|
||||||
|
category: [],
|
||||||
|
status: [],
|
||||||
|
},
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
const formattedValues = {
|
||||||
|
...values,
|
||||||
|
category: values.category.map((item) => String(item.value)),
|
||||||
|
status: values.status.map((item) => String(item.value)),
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit?.(formattedValues);
|
||||||
|
closeModalHandler();
|
||||||
|
},
|
||||||
|
onReset: () => {
|
||||||
|
onReset?.();
|
||||||
|
closeModalHandler();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const productCategoryChangeHandler = (
|
||||||
|
val: OptionType | OptionType[] | null
|
||||||
|
) => {
|
||||||
|
formik.setFieldValue('category', val);
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
|
formik.setFieldValue('status', val);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
ref={ref}
|
||||||
|
className={{
|
||||||
|
modalBox: 'p-0 rounded-xl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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-gray-300'>
|
||||||
|
<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'>
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<DateInput
|
||||||
|
label='PO Date'
|
||||||
|
name='poDate'
|
||||||
|
placeholder='Pilih Tanggal'
|
||||||
|
value={formik.values.poDate}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
isNestedModal
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInputCheckbox
|
||||||
|
label='Kategori'
|
||||||
|
placeholder='Pilih Kategori'
|
||||||
|
value={formik.values.category}
|
||||||
|
onChange={productCategoryChangeHandler}
|
||||||
|
options={productCategoryOptions}
|
||||||
|
isLoading={isLoadingProductCategoryOptions}
|
||||||
|
onInputChange={setProductCategoryInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreProductCategory}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInputCheckbox
|
||||||
|
label='Status'
|
||||||
|
placeholder='Status'
|
||||||
|
value={formik.values.status}
|
||||||
|
onChange={statusChangeHandler}
|
||||||
|
options={PURCHASE_ORDER_APPROVAL_LINE.map((item) => ({
|
||||||
|
label: item.step_name,
|
||||||
|
value: item.step_name,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modal Footer */}
|
||||||
|
<div className='p-4 flex justify-between gap-4 border-t border-gray-300 bg-gray-100'>
|
||||||
|
<Button
|
||||||
|
type='reset'
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
className='p-3 rounded-lg text-base-content/65'
|
||||||
|
>
|
||||||
|
Reset Filter
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
className='p-3 rounded-lg w-fit sm:w-full max-w-40 text-base-100 text-sm'
|
||||||
|
>
|
||||||
|
Apply Filter
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PurchaseFilterModal;
|
||||||
Reference in New Issue
Block a user