mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
refactor(FE): Add Formik-based filter with validation
This commit is contained in:
@@ -51,6 +51,13 @@ import { generateUniformityExcel } from '@/components/pages/production/uniformit
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import Menu from '@/components/menu/Menu';
|
||||
import MenuItem from '@/components/menu/MenuItem';
|
||||
import { useFormik } from 'formik';
|
||||
import {
|
||||
UniformityTableFilterSchema,
|
||||
type UniformityTableFilterValues,
|
||||
} from '@/components/pages/production/uniformity/UniformityTableFilter.schema';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
|
||||
const UniformityConfirmationPreview = ({
|
||||
uniformity,
|
||||
@@ -314,6 +321,37 @@ const UniformityTable = () => {
|
||||
}
|
||||
}, [projectFlockKandangLookup]);
|
||||
|
||||
// ===== FORMIK FILTER =====
|
||||
const filterFormik = useFormik<UniformityTableFilterValues>({
|
||||
initialValues: {
|
||||
start_date: filterStartDate,
|
||||
end_date: filterEndDate,
|
||||
location: filterLocation,
|
||||
location_id: Number(filterLocation?.value) || 0,
|
||||
project_flock: filterProjectFlock,
|
||||
project_flock_id: Number(filterProjectFlock?.value) || 0,
|
||||
project_flock_kandang_id: filterProjectFlockKandangId,
|
||||
kandang: filterKandang,
|
||||
kandang_id: Number(filterKandang?.value) || 0,
|
||||
},
|
||||
validationSchema: UniformityTableFilterSchema,
|
||||
enableReinitialize: true,
|
||||
onSubmit: async (values) => {
|
||||
setFilterStartDate(values.start_date);
|
||||
setFilterEndDate(values.end_date);
|
||||
setFilterLocation(values.location ?? null);
|
||||
setFilterProjectFlock(values.project_flock ?? null);
|
||||
setFilterKandang(values.kandang ?? null);
|
||||
|
||||
setIsSubmitted(true);
|
||||
filterModal.closeModal();
|
||||
},
|
||||
});
|
||||
|
||||
// ===== FORMIK ERROR LIST =====
|
||||
const { formErrorList, close, handleFormSubmit } =
|
||||
useFormikErrorList(filterFormik);
|
||||
|
||||
// ===== BUILD SWR KEY WITH FILTERS =====
|
||||
const uniformitySwrKey = useMemo(() => {
|
||||
const basePath = UniformityApi.basePath;
|
||||
@@ -370,29 +408,54 @@ const UniformityTable = () => {
|
||||
const handleFilterLocationChange = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
const location = val as OptionType | null;
|
||||
const locationId = Number(location?.value) || 0;
|
||||
|
||||
filterFormik.setFieldValue('location', location);
|
||||
filterFormik.setFieldValue('location_id', locationId);
|
||||
|
||||
setFilterLocation(location);
|
||||
setFilterProjectFlock(null);
|
||||
setFilterKandang(null);
|
||||
setFilterProjectFlockLocationId(
|
||||
location ? location.value.toString() : ''
|
||||
);
|
||||
|
||||
filterFormik.setFieldValue('project_flock', null);
|
||||
filterFormik.setFieldValue('project_flock_id', 0);
|
||||
filterFormik.setFieldValue('kandang', null);
|
||||
filterFormik.setFieldValue('kandang_id', 0);
|
||||
},
|
||||
[]
|
||||
[filterFormik]
|
||||
);
|
||||
|
||||
const handleFilterProjectFlockChange = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
setFilterProjectFlock(val as OptionType | null);
|
||||
const projectFlock = val as OptionType | null;
|
||||
const projectFlockId = Number(projectFlock?.value) || 0;
|
||||
|
||||
filterFormik.setFieldValue('project_flock', projectFlock);
|
||||
filterFormik.setFieldValue('project_flock_id', projectFlockId);
|
||||
|
||||
setFilterProjectFlock(projectFlock);
|
||||
setFilterKandang(null);
|
||||
|
||||
filterFormik.setFieldValue('kandang', null);
|
||||
filterFormik.setFieldValue('kandang_id', 0);
|
||||
},
|
||||
[]
|
||||
[filterFormik]
|
||||
);
|
||||
|
||||
const handleFilterKandangChange = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
setFilterKandang(val as OptionType | null);
|
||||
const kandang = val as OptionType | null;
|
||||
const kandangId = Number(kandang?.value) || 0;
|
||||
|
||||
filterFormik.setFieldValue('kandang', kandang);
|
||||
filterFormik.setFieldValue('kandang_id', kandangId);
|
||||
|
||||
setFilterKandang(kandang);
|
||||
},
|
||||
[]
|
||||
[filterFormik]
|
||||
);
|
||||
|
||||
const handleResetFilters = useCallback(() => {
|
||||
@@ -403,41 +466,34 @@ const UniformityTable = () => {
|
||||
setFilterProjectFlockKandangId(undefined);
|
||||
setFilterStartDate('');
|
||||
setFilterEndDate('');
|
||||
}, []);
|
||||
setFilterErrors({});
|
||||
|
||||
filterFormik.resetForm();
|
||||
}, [filterFormik]);
|
||||
|
||||
const handleFilterStartDateChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setFilterStartDate(value);
|
||||
filterFormik.setFieldValue('start_date', value);
|
||||
},
|
||||
[filterFormik]
|
||||
);
|
||||
|
||||
const handleFilterEndDateChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setFilterEndDate(value);
|
||||
filterFormik.setFieldValue('end_date', value);
|
||||
},
|
||||
[filterFormik]
|
||||
);
|
||||
|
||||
const handleApplyFilters = useCallback(() => {
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
if (!filterStartDate) {
|
||||
errors.start_date = 'Tanggal mulai wajib diisi';
|
||||
}
|
||||
if (!filterEndDate) {
|
||||
errors.end_date = 'Tanggal akhir wajib diisi';
|
||||
}
|
||||
if (!filterLocation) {
|
||||
errors.location = 'Lokasi wajib dipilih';
|
||||
}
|
||||
if (!filterProjectFlock) {
|
||||
errors.project_flock = 'Project Flock wajib dipilih';
|
||||
}
|
||||
if (!filterKandang) {
|
||||
errors.kandang = 'Kandang wajib dipilih';
|
||||
}
|
||||
|
||||
setFilterErrors(errors);
|
||||
|
||||
if (Object.keys(errors).length === 0) {
|
||||
setIsSubmitted(true);
|
||||
filterModal.closeModal();
|
||||
}
|
||||
}, [
|
||||
filterModal,
|
||||
filterStartDate,
|
||||
filterEndDate,
|
||||
filterLocation,
|
||||
filterProjectFlock,
|
||||
filterKandang,
|
||||
]);
|
||||
handleFormSubmit(
|
||||
new Event('submit') as unknown as React.FormEvent<HTMLFormElement>
|
||||
);
|
||||
}, [handleFormSubmit]);
|
||||
|
||||
const selectedRowIds = useMemo(() => {
|
||||
return Object.keys(rowSelection)
|
||||
@@ -1134,42 +1190,46 @@ const UniformityTable = () => {
|
||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Error List Alert */}
|
||||
{formErrorList.length > 0 && (
|
||||
<div className='w-full px-4'>
|
||||
<AlertErrorList formErrorList={formErrorList} onClose={close} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='space-y-4 px-4'>
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 sm:gap-4'>
|
||||
<div>
|
||||
<DateInput
|
||||
label='Tanggal'
|
||||
name='start_date'
|
||||
value={filterStartDate}
|
||||
onChange={(e) => {
|
||||
setFilterStartDate(e.target.value);
|
||||
setFilterErrors((prev) => ({ ...prev, start_date: '' }));
|
||||
}}
|
||||
value={filterFormik.values.start_date}
|
||||
onChange={handleFilterStartDateChange}
|
||||
onBlur={filterFormik.handleBlur}
|
||||
isError={
|
||||
filterFormik.touched.start_date &&
|
||||
Boolean(filterFormik.errors.start_date)
|
||||
}
|
||||
errorMessage={filterFormik.errors.start_date}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
{filterErrors.start_date && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.start_date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<DateInput
|
||||
label=' '
|
||||
name='end_date'
|
||||
value={filterEndDate}
|
||||
onChange={(e) => {
|
||||
setFilterEndDate(e.target.value);
|
||||
setFilterErrors((prev) => ({ ...prev, end_date: '' }));
|
||||
}}
|
||||
value={filterFormik.values.end_date}
|
||||
onChange={handleFilterEndDateChange}
|
||||
onBlur={filterFormik.handleBlur}
|
||||
isError={
|
||||
filterFormik.touched.end_date &&
|
||||
Boolean(filterFormik.errors.end_date)
|
||||
}
|
||||
errorMessage={filterFormik.errors.end_date}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
{filterErrors.end_date && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.end_date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1177,65 +1237,65 @@ const UniformityTable = () => {
|
||||
<SelectInput
|
||||
label='Lokasi'
|
||||
placeholder='Pilih Lokasi...'
|
||||
value={filterLocation}
|
||||
value={filterFormik.values.location}
|
||||
onChange={(value) => {
|
||||
handleFilterLocationChange(value);
|
||||
setFilterErrors((prev) => ({ ...prev, location: '' }));
|
||||
}}
|
||||
options={filterLocationOptions}
|
||||
onInputChange={setFilterLocationInputValue}
|
||||
isLoading={isLoadingFilterLocations}
|
||||
onMenuScrollToBottom={loadMoreFilterLocations}
|
||||
isError={
|
||||
filterFormik.touched.location &&
|
||||
Boolean(filterFormik.errors.location)
|
||||
}
|
||||
errorMessage={filterFormik.errors.location}
|
||||
isClearable
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
{filterErrors.location && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.location}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SelectInput
|
||||
label='Project Flock'
|
||||
placeholder='Pilih Project Flock...'
|
||||
value={filterProjectFlock}
|
||||
value={filterFormik.values.project_flock}
|
||||
onChange={(value) => {
|
||||
handleFilterProjectFlockChange(value);
|
||||
setFilterErrors((prev) => ({ ...prev, project_flock: '' }));
|
||||
}}
|
||||
options={filterProjectFlockOptions}
|
||||
onInputChange={setFilterProjectFlockSearchValue}
|
||||
isLoading={isLoadingFilterProjectFlocks}
|
||||
onMenuScrollToBottom={loadMoreFilterProjectFlocks}
|
||||
isDisabled={!filterLocation}
|
||||
isDisabled={!filterFormik.values.location}
|
||||
isError={
|
||||
filterFormik.touched.project_flock &&
|
||||
Boolean(filterFormik.errors.project_flock)
|
||||
}
|
||||
errorMessage={filterFormik.errors.project_flock}
|
||||
isClearable
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
{filterErrors.project_flock && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.project_flock}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SelectInput
|
||||
label='Kandang'
|
||||
placeholder='Pilih Kandang...'
|
||||
value={filterKandang}
|
||||
value={filterFormik.values.kandang}
|
||||
onChange={(value) => {
|
||||
handleFilterKandangChange(value);
|
||||
setFilterErrors((prev) => ({ ...prev, kandang: '' }));
|
||||
}}
|
||||
options={filterKandangOptions}
|
||||
isDisabled={!filterProjectFlock}
|
||||
isDisabled={!filterFormik.values.project_flock}
|
||||
isError={
|
||||
filterFormik.touched.kandang &&
|
||||
Boolean(filterFormik.errors.kandang)
|
||||
}
|
||||
errorMessage={filterFormik.errors.kandang}
|
||||
isClearable
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
{filterErrors.kandang && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.kandang}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { OptionType } from '@/components/input/SelectInput';
|
||||
import * as yup from 'yup';
|
||||
|
||||
export type UniformityTableFilterType = {
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
location: OptionType | null;
|
||||
location_id: number;
|
||||
project_flock: OptionType | null;
|
||||
project_flock_id: number;
|
||||
project_flock_kandang_id: number | undefined;
|
||||
kandang: OptionType | null;
|
||||
kandang_id: number;
|
||||
};
|
||||
|
||||
export const UniformityTableFilterSchema = yup.object({
|
||||
start_date: yup.string().required('Tanggal mulai wajib diisi'),
|
||||
end_date: yup.string().required('Tanggal akhir wajib diisi'),
|
||||
location: yup
|
||||
.mixed<OptionType>()
|
||||
.required('Lokasi wajib dipilih')
|
||||
.test('is-not-empty', 'Lokasi wajib dipilih', (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length > 0;
|
||||
}
|
||||
return !!value;
|
||||
}),
|
||||
location_id: yup
|
||||
.number()
|
||||
.min(1, 'Location wajib diisi!')
|
||||
.required('Location wajib diisi!')
|
||||
.typeError('Location wajib diisi!'),
|
||||
project_flock: yup
|
||||
.mixed<OptionType>()
|
||||
.required('Project Flock wajib dipilih')
|
||||
.test('is-not-empty', 'Project Flock wajib dipilih', (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length > 0;
|
||||
}
|
||||
return !!value;
|
||||
}),
|
||||
project_flock_id: yup
|
||||
.number()
|
||||
.min(1, 'Project flock wajib diisi!')
|
||||
.required('Project flock wajib diisi!')
|
||||
.typeError('Project flock wajib diisi!'),
|
||||
project_flock_kandang_id: yup.number().optional(),
|
||||
kandang: yup
|
||||
.mixed<OptionType>()
|
||||
.required('Kandang wajib dipilih')
|
||||
.test('is-not-empty', 'Kandang wajib dipilih', (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length > 0;
|
||||
}
|
||||
return !!value;
|
||||
}),
|
||||
kandang_id: yup
|
||||
.number()
|
||||
.min(1, 'Kandang wajib diisi!')
|
||||
.required('Kandang wajib diisi!')
|
||||
.typeError('Kandang wajib diisi!'),
|
||||
}) as yup.ObjectSchema<UniformityTableFilterType>;
|
||||
|
||||
export type UniformityTableFilterValues = yup.InferType<
|
||||
typeof UniformityTableFilterSchema
|
||||
>;
|
||||
Reference in New Issue
Block a user