refactor(FE-114,136): update RecordingForm validation and input handling for feed and vaccination data

This commit is contained in:
rstubryan
2025-10-18 12:25:04 +07:00
parent c25b49c179
commit e4f554bcd4
2 changed files with 80 additions and 74 deletions
@@ -49,20 +49,19 @@ export const RecordingFormSchema = Yup.object({
.of( .of(
Yup.object({ Yup.object({
feed_id: Yup.string().required('Nama pakan wajib diisi!'), feed_id: Yup.string().required('Nama pakan wajib diisi!'),
feed_qty: Yup.number() feed_qty: Yup.mixed<number | ''>().notRequired(),
.required('Qty pakan wajib diisi!')
.min(1, 'Qty minimal 1!')
.typeError('Qty pakan wajib diisi!'),
feed_stock: Yup.number() feed_stock: Yup.number()
.required('Stock pakan wajib diisi!') .required('Jumlah pakan yang digunakan wajib diisi!')
.min(1, 'Stock minimal 1!') .min(1, 'Jumlah pakan minimal 1 kg!')
.typeError('Stock pakan wajib diisi!') .typeError('Jumlah pakan yang digunakan harus berupa angka!')
.test( .test(
'is-not-exceed-qty', 'is-not-exceed-qty',
'Feed stock tidak boleh melebihi feed qty yang tersedia!', 'Jumlah pakan yang digunakan tidak boleh melebihi stok tersedia!',
function (value) { function (value) {
const { feed_qty } = this.parent; const { feed_qty } = this.parent;
return value === undefined || value <= feed_qty; if (value === undefined) return true;
if (feed_qty === undefined || feed_qty === '' || typeof feed_qty !== 'number') return true;
return value <= feed_qty;
} }
), ),
}) })
@@ -74,13 +73,16 @@ export const RecordingFormSchema = Yup.object({
Yup.object({ Yup.object({
chicken_weight: Yup.number() chicken_weight: Yup.number()
.required('Berat ayam wajib diisi!') .required('Berat ayam wajib diisi!')
.min(1, 'Berat minimal 1!'), .min(1, 'Berat ayam minimal 1 gram!')
.typeError('Berat ayam harus berupa angka!'),
chicken_count: Yup.number() chicken_count: Yup.number()
.required('Jumlah ayam wajib diisi!') .required('Jumlah ayam wajib diisi!')
.min(1, 'Jumlah minimal 1!'), .min(1, 'Jumlah ayam minimal 1 ekor!')
.typeError('Jumlah ayam harus berupa angka!'),
average_chicken_weight: Yup.number() average_chicken_weight: Yup.number()
.required('Rata-rata berat ayam wajib diisi!') .required('Rata-rata berat ayam wajib diisi!')
.min(1, 'Rata-rata minimal 1!'), .min(1, 'Rata-rata berat ayam minimal 1 gram!')
.typeError('Rata-rata berat ayam harus berupa angka!'),
}) })
) )
.min(1, 'Minimal harus ada 1 data bobot badan!') .min(1, 'Minimal harus ada 1 data bobot badan!')
@@ -89,20 +91,19 @@ export const RecordingFormSchema = Yup.object({
.of( .of(
Yup.object({ Yup.object({
vaccine_id: Yup.string().required('Nama vaksin wajib diisi!'), vaccine_id: Yup.string().required('Nama vaksin wajib diisi!'),
total_stock: Yup.number() total_stock: Yup.mixed<number | ''>().notRequired(),
.required('Total stock wajib diisi!')
.min(1, 'Total stock minimal 1!')
.typeError('Total stock wajib diisi!'),
used_stock: Yup.number() used_stock: Yup.number()
.required('Jumlah stock wajib diisi!') .required('Jumlah vaksin yang digunakan wajib diisi!')
.min(1, 'Jumlah stock minimal 1!') .min(1, 'Jumlah vaksin minimal 1!')
.typeError('Jumlah stock wajib diisi!') .typeError('Jumlah vaksin yang digunakan harus berupa angka!')
.test( .test(
'is-not-exceed-total', 'is-not-exceed-total',
'Used stock tidak boleh melebihi total stock yang tersedia!', 'Jumlah vaksin yang digunakan tidak boleh melebihi stok tersedia!',
function (value) { function (value) {
const { total_stock } = this.parent; const { total_stock } = this.parent;
return value === undefined || value <= total_stock; if (value === undefined) return true;
if (total_stock === undefined || total_stock === '' || typeof total_stock !== 'number') return true;
return value <= total_stock;
} }
), ),
}) })
@@ -119,8 +120,9 @@ export const RecordingFormSchema = Yup.object({
) )
.required('Kondisi wajib diisi!'), .required('Kondisi wajib diisi!'),
count: Yup.number() count: Yup.number()
.required('Jumlah wajib diisi!') .required('Jumlah mortalitas wajib diisi!')
.min(1, 'Jumlah minimal 1!'), .min(1, 'Jumlah mortalitas minimal 1 ekor!')
.typeError('Jumlah mortalitas harus berupa angka!'),
}) })
) )
.min(1, 'Minimal harus ada 1 data mortalitas!') .min(1, 'Minimal harus ada 1 data mortalitas!')
@@ -167,7 +169,7 @@ export const getRecordingFormInitialValues = (
: [ : [
{ {
feed_id: '', feed_id: '',
feed_qty: 0, feed_qty: '',
feed_stock: 0, feed_stock: 0,
}, },
], ],
@@ -187,7 +189,7 @@ export const getRecordingFormInitialValues = (
: [ : [
{ {
vaccine_id: '', vaccine_id: '',
total_stock: 0, total_stock: '',
used_stock: 0, used_stock: 0,
}, },
], ],
@@ -74,7 +74,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
: '', : '',
feed_data: (values.feed_data ?? []).map((p) => ({ feed_data: (values.feed_data ?? []).map((p) => ({
feed_id: p.feed_id, feed_id: p.feed_id,
feed_qty: p.feed_qty, feed_qty: typeof p.feed_qty === 'number' ? p.feed_qty : 0,
feed_stock: p.feed_stock, feed_stock: p.feed_stock,
})), })),
body_weight: (values.body_weight ?? []).map((b) => ({ body_weight: (values.body_weight ?? []).map((b) => ({
@@ -84,7 +84,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
})), })),
vaccination: (values.vaccination ?? []).map((v) => ({ vaccination: (values.vaccination ?? []).map((v) => ({
vaccine_id: v.vaccine_id, vaccine_id: v.vaccine_id,
total_stock: v.total_stock, total_stock: typeof v.total_stock === 'number' ? v.total_stock : 0,
used_stock: v.used_stock, used_stock: v.used_stock,
})), })),
mortality: (values.mortality ?? []).map((m) => ({ mortality: (values.mortality ?? []).map((m) => ({
@@ -132,8 +132,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
// Create stock mapping for pakan (Feed) // Create stock mapping for pakan (Feed)
const pakanStockMap = useMemo(() => { const pakanStockMap = useMemo(() => {
if (!isResponseSuccess(pakanProducts)) return new Map<number, number>(); if (!isResponseSuccess(pakanProducts))
const map = new Map<number, number>(); return new Map<number, number | ''>();
const map = new Map<number, number | ''>();
pakanProducts.data.forEach((product) => { pakanProducts.data.forEach((product) => {
map.set(product.id, product.quantity); map.set(product.id, product.quantity);
}); });
@@ -155,8 +156,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
// Create stock mapping for OVK (Vaccination) // Create stock mapping for OVK (Vaccination)
const ovkStockMap = useMemo(() => { const ovkStockMap = useMemo(() => {
if (!isResponseSuccess(ovkProducts)) return new Map<number, number>(); if (!isResponseSuccess(ovkProducts)) return new Map<number, number | ''>();
const map = new Map<number, number>(); const map = new Map<number, number | ''>();
ovkProducts.data.forEach((product) => { ovkProducts.data.forEach((product) => {
map.set(product.id, product.quantity); map.set(product.id, product.quantity);
}); });
@@ -256,8 +257,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
...(formik.values.feed_data || []), ...(formik.values.feed_data || []),
{ {
feed: null, feed: null,
feed_id: 0, feed_id: '',
feed_qty: 0, feed_qty: '',
feed_stock: 0, feed_stock: 0,
}, },
]; ];
@@ -311,8 +312,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
...(formik.values.vaccination || []), ...(formik.values.vaccination || []),
{ {
vaccine: null, vaccine: null,
vaccine_id: 0, vaccine_id: '',
total_stock: 0, total_stock: '',
used_stock: 0, used_stock: 0,
}, },
]; ];
@@ -532,10 +533,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
onChange={(val) => { onChange={(val) => {
const productWarehouseId = const productWarehouseId =
(val as OptionType)?.value ?? 0; (val as OptionType)?.value ?? 0;
const stock = const stock = productWarehouseId
pakanStockMap.get( ? (pakanStockMap.get(
productWarehouseId as number productWarehouseId as number
) ?? 0; ) ?? '')
: '';
formik.setFieldValue( formik.setFieldValue(
`feed_data.${idx}.feed`, `feed_data.${idx}.feed`,
@@ -543,12 +545,17 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
); );
formik.setFieldValue( formik.setFieldValue(
`feed_data.${idx}.feed_id`, `feed_data.${idx}.feed_id`,
productWarehouseId productWarehouseId || ''
); );
formik.setFieldValue( formik.setFieldValue(
`feed_data.${idx}.feed_qty`, `feed_data.${idx}.feed_qty`,
stock stock
); );
// Reset feed_stock when changing feed
formik.setFieldValue(
`feed_data.${idx}.feed_stock`,
0
);
}} }}
options={pakanOptions} options={pakanOptions}
isLoading={false} isLoading={false}
@@ -569,21 +576,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td> </td>
<td> <td>
<TextInput <TextInput
required type='text'
type='number'
name={`feed_data.${idx}.feed_qty`} name={`feed_data.${idx}.feed_qty`}
value={feed.feed_qty} value={
feed.feed_qty === '' ||
feed.feed_qty === undefined
? ''
: String(feed.feed_qty)
}
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
isError={
isRepeaterInputError('feed_data', 'feed_qty', idx)
.isError
}
errorMessage={
isRepeaterInputError('feed_data', 'feed_qty', idx)
.errorMessage
}
readOnly={true} readOnly={true}
placeholder='Pilih pakan terlebih dahulu'
className={{ className={{
wrapper: 'w-full min-w-24', wrapper: 'w-full min-w-24',
}} }}
@@ -946,9 +950,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
onChange={(val) => { onChange={(val) => {
const productWarehouseId = const productWarehouseId =
(val as OptionType)?.value ?? 0; (val as OptionType)?.value ?? 0;
const stock = const stock = productWarehouseId
ovkStockMap.get(productWarehouseId as number) ?? ? (ovkStockMap.get(
0; productWarehouseId as number
) ?? '')
: '';
formik.setFieldValue( formik.setFieldValue(
`vaccination.${idx}.vaccine`, `vaccination.${idx}.vaccine`,
@@ -956,12 +962,17 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
); );
formik.setFieldValue( formik.setFieldValue(
`vaccination.${idx}.vaccine_id`, `vaccination.${idx}.vaccine_id`,
productWarehouseId productWarehouseId || ''
); );
formik.setFieldValue( formik.setFieldValue(
`vaccination.${idx}.total_stock`, `vaccination.${idx}.total_stock`,
stock stock
); );
// Reset used_stock when changing vaccine
formik.setFieldValue(
`vaccination.${idx}.used_stock`,
0
);
}} }}
options={ovkOptions} options={ovkOptions}
isLoading={false} isLoading={false}
@@ -988,27 +999,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td> </td>
<td> <td>
<TextInput <TextInput
required type='text'
type='number'
name={`vaccination.${idx}.total_stock`} name={`vaccination.${idx}.total_stock`}
value={vaccine.total_stock} value={
vaccine.total_stock === '' ||
vaccine.total_stock === undefined
? ''
: String(vaccine.total_stock)
}
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
isError={
isRepeaterInputError(
'vaccination',
'total_stock',
idx
).isError
}
errorMessage={
isRepeaterInputError(
'vaccination',
'total_stock',
idx
).errorMessage
}
readOnly={true} readOnly={true}
placeholder='Pilih vaksin terlebih dahulu'
className={{ className={{
wrapper: 'w-full min-w-24', wrapper: 'w-full min-w-24',
}} }}
@@ -1189,13 +1191,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
/> />
</td> </td>
<td> <td>
<TextInput <NumberInput
required required
type='number'
name={`mortality.${idx}.count`} name={`mortality.${idx}.count`}
value={mortality.count} value={mortality.count}
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
maskType='number'
decimals={0}
min={0}
isError={ isError={
isRepeaterInputError('mortality', 'count', idx) isRepeaterInputError('mortality', 'count', idx)
.isError .isError