From e116311dc2d60bc8b0e79bf85c588b87d2943f47 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sun, 2 Nov 2025 23:04:54 +0700 Subject: [PATCH] refactor(FE-170,174): restructure schema and validation for body weights, stocks, and depletions; improve handling of input values --- .../recording/form/RecordingForm.schema.ts | 307 +++++++++++------- .../recording/form/RecordingForm.tsx | 44 +-- 2 files changed, 207 insertions(+), 144 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.schema.ts b/src/components/pages/production/recording/form/RecordingForm.schema.ts index fb58b048..5bd914b2 100644 --- a/src/components/pages/production/recording/form/RecordingForm.schema.ts +++ b/src/components/pages/production/recording/form/RecordingForm.schema.ts @@ -7,107 +7,165 @@ import { CreateGradingPayload, } from '@/types/api/production/recording'; -export const RecordingGrowingFormSchema = Yup.object({ - project_flock_kandang: Yup.object({ - value: Yup.number().min(1).required(), - label: Yup.string().required(), - }).nullable(), - project_flock_kandangs_id: Yup.number() - .default(0) - .typeError('Project Flock Kandang wajib diisi!') - .test( - 'is-valid-project-flock-kandang', - 'Project Flock Kandang wajib diisi!', - (value) => value !== undefined && value !== null && value > 0 - ) - .required('Project Flock Kandang wajib diisi!') - .test( - 'not-already-recorded', - 'Project Flock ini sudah direcord hari ini!', - function (value) { - const recordedProjectFlockIds = this.options.context - ?.recordedProjectFlockIds as Set; - const formType = this.options.context?.type as - | 'add' - | 'edit' - | 'detail'; - if (formType !== 'add') return true; - if (value && recordedProjectFlockIds?.has(value)) { - return false; - } - return true; - } - ), - body_weights: Yup.array() - .of( - Yup.object({ - weight: Yup.number() - .required('Berat ayam total wajib diisi!') - .min(1, 'Berat ayam total minimal 1 gram!') - .typeError('Berat ayam total harus berupa angka!'), - avg_weight: Yup.number() - .required('Berat ayam rata-rata wajib diisi!') - .min(1, 'Berat ayam rata-rata minimal 1 gram!') - .typeError('Berat ayam rata-rata harus berupa angka!'), - qty: Yup.number() - .required('Jumlah ayam wajib diisi!') - .min(1, 'Jumlah ayam minimal 1 ekor!') - .typeError('Jumlah ayam harus berupa angka!') - .default(1), - }) - ) - .min(1, 'Minimal harus ada 1 data bobot badan!') - .required('Data bobot badan wajib diisi!'), - stocks: Yup.array() - .of( - Yup.object({ - product_warehouse_id: Yup.number() - .required('Produk wajib diisi!') - .min(1, 'Produk wajib diisi!') - .typeError('Produk harus berupa angka!'), - usage_qty: Yup.number() - .required('Jumlah penggunaan wajib diisi!') - .min(1, 'Jumlah penggunaan tidak boleh 0!') - .typeError('Jumlah penggunaan harus berupa angka!'), - }) - ) - .min(1, 'Minimal harus ada 1 data stok!') - .required('Data stok wajib diisi!'), - depletions: Yup.array() - .of( - Yup.object({ - product_warehouse_id: Yup.number() - .required('Produk depletions wajib diisi!') - .min(1, 'Produk depletions wajib diisi!') - .typeError('Produk depletions harus berupa angka!'), - qty: Yup.number() - .required('Jumlah depletions wajib diisi!') - .min(1, 'Jumlah depletions minimal 1!') - .typeError('Jumlah depletions harus berupa angka!'), - }) - ) - .min(1, 'Minimal harus ada 1 data depletions!') - .required('Data depletions wajib diisi!'), +type RecordingGrowingFormSchemaType = { + project_flock_kandang: { + value: number; + label: string; + } | null; + project_flock_kandangs_id: number; + body_weights: { + weight: number | string; + avg_weight: number | string; + qty: number | string; + }[]; + stocks: { + product_warehouse_id: number; + usage_qty: number | string; + }[]; + depletions: { + product_warehouse_id: number; + qty: number | string; + }[]; +}; + +type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & { + eggs: { + product_warehouse_id: number; + qty: number | string; + }[]; +}; + +type RecordingGradingFormSchemaType = { + recording_egg_id: number; + eggs_grading: { + grade: string; + qty: number | string; + }[]; +}; + +export type BodyWeightSchema = { + weight: number | string; + avg_weight: number | string; + qty: number | string; +}; + +export type StockSchema = { + product_warehouse_id: number; + usage_qty: number | string; +}; + +export type DepletionSchema = { + product_warehouse_id: number; + qty: number | string; +}; + +export type EggSchema = { + product_warehouse_id: number; + qty: number | string; +}; + +const BodyWeightObjectSchema: Yup.ObjectSchema = Yup.object({ + weight: Yup.number() + .required('Berat ayam total wajib diisi!') + .min(1, 'Berat ayam total minimal 1 gram!') + .typeError('Berat ayam total harus berupa angka!'), + avg_weight: Yup.number() + .required('Berat ayam rata-rata wajib diisi!') + .min(1, 'Berat ayam rata-rata minimal 1 gram!') + .typeError('Berat ayam rata-rata harus berupa angka!'), + qty: Yup.number() + .required('Jumlah ayam wajib diisi!') + .min(1, 'Jumlah ayam minimal 1 ekor!') + .typeError('Jumlah ayam harus berupa angka!'), }); -export const RecordingLayingFormSchema = RecordingGrowingFormSchema.shape({ - eggs: Yup.array() - .of( - Yup.object({ - product_warehouse_id: Yup.number() - .required('Produk telur wajib diisi!') - .min(1, 'Produk telur wajib diisi!') - .typeError('Produk telur harus berupa angka!'), - qty: Yup.number() - .required('Jumlah telur wajib diisi!') - .min(1, 'Jumlah telur tidak boleh 0!') - .typeError('Jumlah telur harus berupa angka!'), - }) - ) - .min(1, 'Minimal harus ada 1 data telur!') - .required('Data telur wajib diisi!'), +const StockObjectSchema: Yup.ObjectSchema = Yup.object({ + product_warehouse_id: Yup.number() + .required('Produk wajib diisi!') + .min(1, 'Produk wajib diisi!') + .typeError('Produk harus berupa angka!'), + usage_qty: Yup.number() + .required('Jumlah penggunaan wajib diisi!') + .min(1, 'Jumlah penggunaan tidak boleh 0!') + .typeError('Jumlah penggunaan harus berupa angka!'), }); +const DepletionObjectSchema: Yup.ObjectSchema = Yup.object({ + product_warehouse_id: Yup.number() + .required('Produk depletions wajib diisi!') + .min(1, 'Produk depletions wajib diisi!') + .typeError('Produk depletions harus berupa angka!'), + qty: Yup.number() + .required('Jumlah depletions wajib diisi!') + .min(1, 'Jumlah depletions minimal 1!') + .typeError('Jumlah depletions harus berupa angka!'), +}); + +const EggObjectSchema: Yup.ObjectSchema = Yup.object({ + product_warehouse_id: Yup.number() + .required('Produk telur wajib diisi!') + .min(1, 'Produk telur wajib diisi!') + .typeError('Produk telur harus berupa angka!'), + qty: Yup.number() + .required('Jumlah telur wajib diisi!') + .min(1, 'Jumlah telur tidak boleh 0!') + .typeError('Jumlah telur harus berupa angka!'), +}); + +export const RecordingGrowingFormSchema: Yup.ObjectSchema = + Yup.object({ + project_flock_kandang: Yup.object({ + value: Yup.number().min(1).required(), + label: Yup.string().required(), + }).nullable(), + project_flock_kandangs_id: Yup.number() + .default(0) + .typeError('Project Flock Kandang wajib diisi!') + .test( + 'is-valid-project-flock-kandang', + 'Project Flock Kandang wajib diisi!', + (value) => value !== undefined && value !== null && value > 0 + ) + .required('Project Flock Kandang wajib diisi!') + .test( + 'not-already-recorded', + 'Project Flock ini sudah direcord hari ini!', + function (value) { + const recordedProjectFlockIds = this.options.context + ?.recordedProjectFlockIds as Set; + const formType = this.options.context?.type as + | 'add' + | 'edit' + | 'detail'; + if (formType !== 'add') return true; + if (value && recordedProjectFlockIds?.has(value)) { + return false; + } + return true; + } + ), + body_weights: Yup.array() + .of(BodyWeightObjectSchema) + .min(1, 'Minimal harus ada 1 data bobot badan!') + .required('Data bobot badan wajib diisi!'), + stocks: Yup.array() + .of(StockObjectSchema) + .min(1, 'Minimal harus ada 1 data stok!') + .required('Data stok wajib diisi!'), + depletions: Yup.array() + .of(DepletionObjectSchema) + .min(1, 'Minimal harus ada 1 data depletions!') + .required('Data depletions wajib diisi!'), + }); + +export const RecordingLayingFormSchema: Yup.ObjectSchema = + RecordingGrowingFormSchema.shape({ + eggs: Yup.array() + .of(EggObjectSchema) + .min(1, 'Minimal harus ada 1 data telur!') + .required('Data telur wajib diisi!'), + }); + export const UpdateRecordingGrowingFormSchema = RecordingGrowingFormSchema.shape({ project_flock_kandangs_id: Yup.number() @@ -133,26 +191,27 @@ export const UpdateRecordingLayingFormSchema = RecordingLayingFormSchema.shape({ .required('Project Flock Kandang wajib diisi!'), }); -export const RecordingGradingFormSchema = Yup.object({ - recording_egg_id: Yup.number() - .required('Recording Egg ID wajib diisi!') - .min(1, 'Recording Egg ID minimal 1!') - .typeError('Recording Egg ID harus berupa angka!'), - eggs_grading: Yup.array() - .of( - Yup.object({ - grade: Yup.string() - .required('Grade telur wajib diisi!') - .typeError('Grade telur harus berupa string!'), - qty: Yup.number() - .required('Jumlah telur wajib diisi!') - .min(1, 'Jumlah telur minimal 1!') - .typeError('Jumlah telur harus berupa angka!'), - }) - ) - .min(1, 'Minimal harus ada 1 data grading telur!') - .required('Data grading telur wajib diisi!'), -}); +export const RecordingGradingFormSchema: Yup.ObjectSchema = + Yup.object({ + recording_egg_id: Yup.number() + .required('Recording Egg ID wajib diisi!') + .min(1, 'Recording Egg ID minimal 1!') + .typeError('Recording Egg ID harus berupa angka!'), + eggs_grading: Yup.array() + .of( + Yup.object({ + grade: Yup.string() + .required('Grade telur wajib diisi!') + .typeError('Grade telur harus berupa string!'), + qty: Yup.number() + .required('Jumlah telur wajib diisi!') + .min(1, 'Jumlah telur minimal 1!') + .typeError('Jumlah telur harus berupa angka!'), + }) + ) + .min(1, 'Minimal harus ada 1 data grading telur!') + .required('Data grading telur wajib diisi!'), + }); export const UpdateRecordingGradingFormSchema = RecordingGradingFormSchema.shape({ @@ -199,9 +258,9 @@ export const getRecordingGrowingFormInitialValues = ( }) ) ?? [ { - weight: 0, - avg_weight: 0, - qty: 0, + weight: '', + avg_weight: '', + qty: '', }, ], stocks: initialValues?.stocks?.map( @@ -212,7 +271,7 @@ export const getRecordingGrowingFormInitialValues = ( ) ?? [ { product_warehouse_id: 0, - usage_qty: 0, + usage_qty: '', }, ], depletions: initialValues?.depletions?.map( @@ -225,7 +284,7 @@ export const getRecordingGrowingFormInitialValues = ( ) ?? [ { product_warehouse_id: 0, - qty: 0, + qty: '', }, ], }); @@ -241,7 +300,7 @@ export const getRecordingLayingFormInitialValues = ( })) ?? [ { product_warehouse_id: 0, - qty: 0, + qty: '', }, ], }); @@ -256,7 +315,7 @@ export const getRecordingGradingFormInitialValues = ( })) ?? [ { grade: '', - qty: 0, + qty: '', }, ], }); diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 5dfddbdd..020de967 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -773,7 +773,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const currentWeight = formik.values.body_weights?.[idx]; if (currentWeight) { - const qty = currentWeight.qty; + const qty = Number(currentWeight.qty) || 0; if (qty > 0 && value > 0) { const avgWeight = parseFloat((value / qty).toFixed(2)); formik.setFieldValue(`body_weights.${idx}.avg_weight`, avgWeight); @@ -794,7 +794,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const currentWeight = formik.values.body_weights?.[idx]; if (currentWeight) { - const qty = currentWeight.qty; + const qty = Number(currentWeight.qty) || 0; if (qty > 0 && value > 0) { const totalWeight = value * qty; formik.setFieldValue(`body_weights.${idx}.weight`, totalWeight); @@ -815,7 +815,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const currentWeight = formik.values.body_weights?.[idx]; if (currentWeight) { - const weight = currentWeight.weight; + const weight = Number(currentWeight.weight) || 0; if (value > 0 && weight > 0) { const avgWeight = parseFloat((weight / value).toFixed(2)); formik.setFieldValue(`body_weights.${idx}.avg_weight`, avgWeight); @@ -864,7 +864,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ...(formik.values.stocks || []), { product_warehouse_id: 0, - usage_qty: 0, + usage_qty: '', }, ]; formik.setFieldValue('stocks', newStocks); @@ -897,7 +897,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ...(formik.values.depletions || []), { product_warehouse_id: 0, - qty: 0, + qty: '', }, ]; formik.setFieldValue('depletions', newDepletions); @@ -932,7 +932,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ...((formik.values as RecordingLayingFormValues).eggs || []), { product_warehouse_id: 0, - qty: 0, + qty: '', }, ]; formik.setFieldValue('eggs', newEggs); @@ -966,7 +966,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { if (isLayingCategory && (type as 'add' | 'edit' | 'detail') !== 'detail') { const layingValues = formik.values as RecordingLayingFormValues; if (!layingValues.eggs || layingValues.eggs.length === 0) { - formik.setFieldValue('eggs', [{ product_warehouse_id: 0, qty: 0 }]); + formik.setFieldValue('eggs', [{ product_warehouse_id: 0, qty: '' }]); } } }, [isLayingCategory, type, formik]); @@ -998,11 +998,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { if (idx === editingAverageIndex || manuallyEditedRows.has(idx)) { return weight; } + const qty = Number(weight.qty) || 0; + const weightValue = Number(weight.weight) || 0; return { ...weight, avg_weight: - weight.qty > 0 && weight.weight > 0 - ? parseFloat((weight.weight / weight.qty).toFixed(2)) + qty > 0 && weightValue > 0 + ? parseFloat((weightValue / qty).toFixed(2)) : 0, }; } @@ -1303,7 +1305,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { { thousandSeparator=',' decimalSeparator='.' inputSuffix='gram' + placeholder='Masukkan berat total...' isError={ isRepeaterInputError('body_weights', 'weight', idx) .isError @@ -1329,13 +1332,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { { { thousandSeparator=',' decimalSeparator='.' inputSuffix='gram' + placeholder='Masukkan berat rata-rata...' isError={ isRepeaterInputError( 'body_weights', @@ -1574,7 +1579,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { { className={{ wrapper: 'w-full min-w-24', }} - placeholder='Jumlah Pakai' + placeholder='Masukkan jumlah pakai' /> {(type as 'add' | 'edit' | 'detail') !== 'detail' && getStockUsageAdornment(idx)} @@ -1787,7 +1792,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { { className={{ wrapper: 'w-full min-w-24', }} - placeholder='Jumlah' + placeholder='Masukkan jumlah deplesi' /> {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( @@ -1998,7 +2003,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { { className={{ wrapper: 'w-full min-w-24', }} - placeholder='Jumlah' + placeholder='Masukkan jumlah telur' /> @@ -2191,10 +2196,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { toast.success( 'Recording berhasil disimpan! Mengalihkan ke form Grading...' ); - // Redirect ke grading form setelah submit berhasil setTimeout(() => { router.push( - '/production/recording/grading/add?recording_id=new' + '/production/recording/grading/add?recording_id=1' ); }, 1000); }