feat: create ReportDepreciationFilterModal component

This commit is contained in:
ValdiANS
2026-04-20 00:28:41 +07:00
parent d47142153e
commit 8afc1a6381
@@ -0,0 +1,270 @@
'use client';
import { RefObject, useMemo, useState } from 'react';
import { useFormik } from 'formik';
import * as yup from 'yup';
import { Icon } from '@iconify/react';
import Modal from '@/components/Modal';
import Button from '@/components/Button';
import DateInput from '@/components/input/DateInput';
import SelectInput, {
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import { AreaApi, LocationApi } from '@/services/api/master-data';
import { ProjectFlockApi } from '@/services/api/production';
import { Area } from '@/types/api/master-data/area';
import { Location } from '@/types/api/master-data/location';
import { ProjectFlock } from '@/types/api/production/project-flock';
export type ReportDepreciationFilterValues = {
area_id: string | null;
location_id: string | null;
project_flock_id: string | null;
period: string | null;
};
export const ReportDepreciationFilterSchema = yup.object({
area_id: yup.string().nullable(),
location_id: yup.string().nullable(),
project_flock_id: yup.string().nullable(),
period: yup.string().nullable().required('Periode wajib dipilih'),
}) as yup.ObjectSchema<ReportDepreciationFilterValues>;
interface ReportDepreciationFilterModalProps {
ref: RefObject<HTMLDialogElement | null>;
initialValues?: ReportDepreciationFilterValues;
onSubmit?: (values: Partial<ReportDepreciationFilterValues>) => void;
onReset?: () => void;
}
const defaultInitialValues: ReportDepreciationFilterValues = {
area_id: null,
location_id: null,
project_flock_id: null,
period: null,
};
const ReportDepreciationFilterModal = ({
ref,
initialValues,
onSubmit,
onReset,
}: ReportDepreciationFilterModalProps) => {
const [selectedAreaId, setSelectedAreaId] = useState<string | undefined>(
initialValues?.area_id || undefined
);
const [selectedLocationId, setSelectedLocationId] = useState<
string | undefined
>(initialValues?.location_id || undefined);
const closeModalHandler = () => {
ref.current?.close();
};
const {
setInputValue: setAreaInputValue,
options: areaOptions,
isLoadingOptions: isLoadingAreaOptions,
loadMore: loadMoreAreas,
} = useSelect<Area>(AreaApi.basePath, 'id', 'name', 'search');
const {
setInputValue: setLocationInputValue,
options: locationOptions,
isLoadingOptions: isLoadingLocationOptions,
loadMore: loadMoreLocations,
} = useSelect<Location>(LocationApi.basePath, 'id', 'name', 'search', {
area_id: selectedAreaId || '',
});
const {
setInputValue: setProjectFlockInputValue,
options: projectFlockOptions,
isLoadingOptions: isLoadingProjectFlockOptions,
loadMore: loadMoreProjectFlocks,
} = useSelect<ProjectFlock>(
ProjectFlockApi.basePath,
'id',
'flock_name',
'search',
{
location_id: selectedLocationId || '',
}
);
const formik = useFormik<ReportDepreciationFilterValues>({
initialValues: initialValues || defaultInitialValues,
validationSchema: ReportDepreciationFilterSchema,
onSubmit: async (values) => {
onSubmit?.(values);
closeModalHandler();
},
onReset: (_) => {
onReset?.();
closeModalHandler();
},
});
const areaValue = useMemo(() => {
if (!formik.values.area_id) return null;
return (
areaOptions.find((opt) => String(opt.value) === formik.values.area_id) ||
null
);
}, [formik.values.area_id, areaOptions]);
const locationValue = useMemo(() => {
if (!formik.values.location_id) return null;
return (
locationOptions.find(
(opt) => String(opt.value) === formik.values.location_id
) || null
);
}, [formik.values.location_id, locationOptions]);
const projectFlockValue = useMemo(() => {
if (!formik.values.project_flock_id) return null;
return (
projectFlockOptions.find(
(opt) => String(opt.value) === formik.values.project_flock_id
) || null
);
}, [formik.values.project_flock_id, projectFlockOptions]);
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
const areaId = val && !Array.isArray(val) ? String(val.value) : null;
setSelectedAreaId(areaId || undefined);
formik.setFieldValue('area_id', areaId);
formik.setFieldValue('location_id', null);
formik.setFieldValue('project_flock_id', null);
setSelectedLocationId(undefined);
};
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
const locationId = val && !Array.isArray(val) ? String(val.value) : null;
setSelectedLocationId(locationId || undefined);
formik.setFieldValue('location_id', locationId);
formik.setFieldValue('project_flock_id', null);
};
const projectFlockChangeHandler = (val: OptionType | OptionType[] | null) => {
const projectFlockId =
val && !Array.isArray(val) ? String(val.value) : null;
formik.setFieldValue('project_flock_id', projectFlockId);
};
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'
>
<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>
<div className='p-4 flex flex-col gap-1.5'>
<SelectInput
label='Area'
placeholder='Pilih Area'
options={areaOptions}
value={areaValue}
onChange={areaChangeHandler}
onInputChange={setAreaInputValue}
onMenuScrollToBottom={loadMoreAreas}
isLoading={isLoadingAreaOptions}
isClearable
isSearchable={true}
className={{ wrapper: 'w-full' }}
/>
<SelectInput
label='Lokasi'
placeholder='Pilih Lokasi'
options={locationOptions}
value={locationValue}
onChange={locationChangeHandler}
onInputChange={setLocationInputValue}
onMenuScrollToBottom={loadMoreLocations}
isLoading={isLoadingLocationOptions}
isClearable
isSearchable={true}
className={{ wrapper: 'w-full' }}
/>
<SelectInput
label='Project Flock'
placeholder='Pilih Project Flock'
options={projectFlockOptions}
value={projectFlockValue}
onChange={projectFlockChangeHandler}
onInputChange={setProjectFlockInputValue}
onMenuScrollToBottom={loadMoreProjectFlocks}
isLoading={isLoadingProjectFlockOptions}
isClearable
isSearchable={true}
className={{ wrapper: 'w-full' }}
/>
<DateInput
label='Periode'
name='period'
placeholder='Pilih Periode'
value={formik.values.period || ''}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
isError={formik.touched.period && !!formik.errors.period}
errorMessage={formik.errors.period}
required
isNestedModal
/>
</div>
<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 ReportDepreciationFilterModal;