mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +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 Dropdown from '@/components/Dropdown';
|
||||||
import Menu from '@/components/menu/Menu';
|
import Menu from '@/components/menu/Menu';
|
||||||
import MenuItem from '@/components/menu/MenuItem';
|
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 = ({
|
const UniformityConfirmationPreview = ({
|
||||||
uniformity,
|
uniformity,
|
||||||
@@ -314,6 +321,37 @@ const UniformityTable = () => {
|
|||||||
}
|
}
|
||||||
}, [projectFlockKandangLookup]);
|
}, [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 =====
|
// ===== BUILD SWR KEY WITH FILTERS =====
|
||||||
const uniformitySwrKey = useMemo(() => {
|
const uniformitySwrKey = useMemo(() => {
|
||||||
const basePath = UniformityApi.basePath;
|
const basePath = UniformityApi.basePath;
|
||||||
@@ -370,29 +408,54 @@ const UniformityTable = () => {
|
|||||||
const handleFilterLocationChange = useCallback(
|
const handleFilterLocationChange = useCallback(
|
||||||
(val: OptionType | OptionType[] | null) => {
|
(val: OptionType | OptionType[] | null) => {
|
||||||
const location = val as 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);
|
setFilterLocation(location);
|
||||||
setFilterProjectFlock(null);
|
setFilterProjectFlock(null);
|
||||||
setFilterKandang(null);
|
setFilterKandang(null);
|
||||||
setFilterProjectFlockLocationId(
|
setFilterProjectFlockLocationId(
|
||||||
location ? location.value.toString() : ''
|
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(
|
const handleFilterProjectFlockChange = useCallback(
|
||||||
(val: OptionType | OptionType[] | null) => {
|
(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);
|
setFilterKandang(null);
|
||||||
|
|
||||||
|
filterFormik.setFieldValue('kandang', null);
|
||||||
|
filterFormik.setFieldValue('kandang_id', 0);
|
||||||
},
|
},
|
||||||
[]
|
[filterFormik]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFilterKandangChange = useCallback(
|
const handleFilterKandangChange = useCallback(
|
||||||
(val: OptionType | OptionType[] | null) => {
|
(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(() => {
|
const handleResetFilters = useCallback(() => {
|
||||||
@@ -403,41 +466,34 @@ const UniformityTable = () => {
|
|||||||
setFilterProjectFlockKandangId(undefined);
|
setFilterProjectFlockKandangId(undefined);
|
||||||
setFilterStartDate('');
|
setFilterStartDate('');
|
||||||
setFilterEndDate('');
|
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 handleApplyFilters = useCallback(() => {
|
||||||
const errors: Record<string, string> = {};
|
handleFormSubmit(
|
||||||
|
new Event('submit') as unknown as React.FormEvent<HTMLFormElement>
|
||||||
if (!filterStartDate) {
|
);
|
||||||
errors.start_date = 'Tanggal mulai wajib diisi';
|
}, [handleFormSubmit]);
|
||||||
}
|
|
||||||
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,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const selectedRowIds = useMemo(() => {
|
const selectedRowIds = useMemo(() => {
|
||||||
return Object.keys(rowSelection)
|
return Object.keys(rowSelection)
|
||||||
@@ -1134,42 +1190,46 @@ const UniformityTable = () => {
|
|||||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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='space-y-4 px-4'>
|
||||||
<div className='grid grid-cols-1 sm:grid-cols-2 sm:gap-4'>
|
<div className='grid grid-cols-1 sm:grid-cols-2 sm:gap-4'>
|
||||||
<div>
|
<div>
|
||||||
<DateInput
|
<DateInput
|
||||||
label='Tanggal'
|
label='Tanggal'
|
||||||
name='start_date'
|
name='start_date'
|
||||||
value={filterStartDate}
|
value={filterFormik.values.start_date}
|
||||||
onChange={(e) => {
|
onChange={handleFilterStartDateChange}
|
||||||
setFilterStartDate(e.target.value);
|
onBlur={filterFormik.handleBlur}
|
||||||
setFilterErrors((prev) => ({ ...prev, start_date: '' }));
|
isError={
|
||||||
}}
|
filterFormik.touched.start_date &&
|
||||||
|
Boolean(filterFormik.errors.start_date)
|
||||||
|
}
|
||||||
|
errorMessage={filterFormik.errors.start_date}
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
{filterErrors.start_date && (
|
|
||||||
<p className='text-red-500 text-sm mt-1'>
|
|
||||||
{filterErrors.start_date}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<DateInput
|
<DateInput
|
||||||
label=' '
|
label=' '
|
||||||
name='end_date'
|
name='end_date'
|
||||||
value={filterEndDate}
|
value={filterFormik.values.end_date}
|
||||||
onChange={(e) => {
|
onChange={handleFilterEndDateChange}
|
||||||
setFilterEndDate(e.target.value);
|
onBlur={filterFormik.handleBlur}
|
||||||
setFilterErrors((prev) => ({ ...prev, end_date: '' }));
|
isError={
|
||||||
}}
|
filterFormik.touched.end_date &&
|
||||||
|
Boolean(filterFormik.errors.end_date)
|
||||||
|
}
|
||||||
|
errorMessage={filterFormik.errors.end_date}
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
{filterErrors.end_date && (
|
|
||||||
<p className='text-red-500 text-sm mt-1'>
|
|
||||||
{filterErrors.end_date}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1177,65 +1237,65 @@ const UniformityTable = () => {
|
|||||||
<SelectInput
|
<SelectInput
|
||||||
label='Lokasi'
|
label='Lokasi'
|
||||||
placeholder='Pilih Lokasi...'
|
placeholder='Pilih Lokasi...'
|
||||||
value={filterLocation}
|
value={filterFormik.values.location}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
handleFilterLocationChange(value);
|
handleFilterLocationChange(value);
|
||||||
setFilterErrors((prev) => ({ ...prev, location: '' }));
|
|
||||||
}}
|
}}
|
||||||
options={filterLocationOptions}
|
options={filterLocationOptions}
|
||||||
onInputChange={setFilterLocationInputValue}
|
onInputChange={setFilterLocationInputValue}
|
||||||
isLoading={isLoadingFilterLocations}
|
isLoading={isLoadingFilterLocations}
|
||||||
onMenuScrollToBottom={loadMoreFilterLocations}
|
onMenuScrollToBottom={loadMoreFilterLocations}
|
||||||
|
isError={
|
||||||
|
filterFormik.touched.location &&
|
||||||
|
Boolean(filterFormik.errors.location)
|
||||||
|
}
|
||||||
|
errorMessage={filterFormik.errors.location}
|
||||||
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
{filterErrors.location && (
|
|
||||||
<p className='text-red-500 text-sm mt-1'>
|
|
||||||
{filterErrors.location}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
label='Project Flock'
|
label='Project Flock'
|
||||||
placeholder='Pilih Project Flock...'
|
placeholder='Pilih Project Flock...'
|
||||||
value={filterProjectFlock}
|
value={filterFormik.values.project_flock}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
handleFilterProjectFlockChange(value);
|
handleFilterProjectFlockChange(value);
|
||||||
setFilterErrors((prev) => ({ ...prev, project_flock: '' }));
|
|
||||||
}}
|
}}
|
||||||
options={filterProjectFlockOptions}
|
options={filterProjectFlockOptions}
|
||||||
onInputChange={setFilterProjectFlockSearchValue}
|
onInputChange={setFilterProjectFlockSearchValue}
|
||||||
isLoading={isLoadingFilterProjectFlocks}
|
isLoading={isLoadingFilterProjectFlocks}
|
||||||
onMenuScrollToBottom={loadMoreFilterProjectFlocks}
|
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' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
{filterErrors.project_flock && (
|
|
||||||
<p className='text-red-500 text-sm mt-1'>
|
|
||||||
{filterErrors.project_flock}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
label='Kandang'
|
label='Kandang'
|
||||||
placeholder='Pilih Kandang...'
|
placeholder='Pilih Kandang...'
|
||||||
value={filterKandang}
|
value={filterFormik.values.kandang}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
handleFilterKandangChange(value);
|
handleFilterKandangChange(value);
|
||||||
setFilterErrors((prev) => ({ ...prev, kandang: '' }));
|
|
||||||
}}
|
}}
|
||||||
options={filterKandangOptions}
|
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' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
{filterErrors.kandang && (
|
|
||||||
<p className='text-red-500 text-sm mt-1'>
|
|
||||||
{filterErrors.kandang}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</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