From 75348620d7891389674ba28ba329fa561d8dc2ef Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 30 Oct 2025 19:09:21 +0700 Subject: [PATCH] feat(FE-170,174): enhance daily recording form with weight validation and dynamic average weight calculation --- .../recording/form/RecordingForm.schema.ts | 6 + .../recording/form/RecordingForm.tsx | 170 ++++++++++++++++-- 2 files changed, 161 insertions(+), 15 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.schema.ts b/src/components/pages/production/recording/form/RecordingForm.schema.ts index bd331793..5506481e 100644 --- a/src/components/pages/production/recording/form/RecordingForm.schema.ts +++ b/src/components/pages/production/recording/form/RecordingForm.schema.ts @@ -38,6 +38,10 @@ export const RecordingGrowingFormSchema = Yup.object({ 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!') @@ -118,11 +122,13 @@ export const getRecordingGrowingFormInitialValues = ( project_flock_kandangs_id: initialValues?.project_flock_kandangs_id ?? 0, body_weights: initialValues?.body_weights?.map( (bw: NonNullable[0]) => ({ + weight: bw.avg_weight * bw.qty, avg_weight: bw.avg_weight, qty: bw.qty, }) ) ?? [ { + weight: 0, avg_weight: 0, qty: 0, }, diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 161766f6..f5a6b505 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useMemo, useState, useCallback } from 'react'; +import { useMemo, useState, useEffect, useCallback } from 'react'; import { useFormik } from 'formik'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; @@ -46,6 +46,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const [selectedStocks, setSelectedStocks] = useState([]); const [selectedDepletions, setSelectedDepletions] = useState([]); + const [editingAverageIndex, setEditingAverageIndex] = useState( + null + ); + const [manuallyEditedRows, setManuallyEditedRows] = useState>( + new Set() + ); + const [locationSearchValue, setLocationSearchValue] = useState(''); const [selectedLocation, setSelectedLocation] = useState( null @@ -612,6 +619,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const newBodyWeights = [ ...(formik.values.body_weights || []), { + weight: 0, avg_weight: 0, qty: 1, }, @@ -619,14 +627,75 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { formik.setFieldValue('body_weights', newBodyWeights); }; + const handleWeightChange = (idx: number, value: number) => { + formik.setFieldValue(`body_weights.${idx}.weight`, value); + + setManuallyEditedRows((prev) => { + const newSet = new Set(prev); + newSet.delete(idx); + return newSet; + }); + + const currentWeight = formik.values.body_weights?.[idx]; + if (currentWeight) { + const qty = currentWeight.qty; + if (qty > 0 && value > 0) { + const avgWeight = parseFloat((value / qty).toFixed(2)); + formik.setFieldValue(`body_weights.${idx}.avg_weight`, avgWeight); + } else { + formik.setFieldValue(`body_weights.${idx}.avg_weight`, 0); + } + } + }; + const handleAvgWeightChange = (idx: number, value: number) => { formik.setFieldValue(`body_weights.${idx}.avg_weight`, value); + + setManuallyEditedRows((prev) => { + const newSet = new Set(prev); + newSet.add(idx); + return newSet; + }); + + const currentWeight = formik.values.body_weights?.[idx]; + if (currentWeight) { + const qty = currentWeight.qty; + if (qty > 0 && value > 0) { + const totalWeight = value * qty; + formik.setFieldValue(`body_weights.${idx}.weight`, totalWeight); + } else { + formik.setFieldValue(`body_weights.${idx}.weight`, 0); + } + } }; const handleQtyChange = (idx: number, value: number) => { formik.setFieldValue(`body_weights.${idx}.qty`, value); + + setManuallyEditedRows((prev) => { + const newSet = new Set(prev); + newSet.delete(idx); + return newSet; + }); + + const currentWeight = formik.values.body_weights?.[idx]; + if (currentWeight) { + const weight = currentWeight.weight; + if (value > 0 && weight > 0) { + const avgWeight = parseFloat((weight / value).toFixed(2)); + formik.setFieldValue(`body_weights.${idx}.avg_weight`, avgWeight); + } else { + formik.setFieldValue(`body_weights.${idx}.avg_weight`, 0); + } + } }; + const handleWeightChangeWrapper = + (idx: number) => (e: React.ChangeEvent) => { + const value = parseFloat(e.target.value) || 0; + handleWeightChange(idx, value); + }; + const handleAvgWeightChangeWrapper = (idx: number) => (e: React.ChangeEvent) => { const value = parseFloat(e.target.value) || 0; @@ -722,6 +791,41 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setSelectedDepletions([]); }; + // ===== EFFECTS ===== + useEffect(() => { + if (formik.values.body_weights && editingAverageIndex === null) { + const updatedBodyWeights = formik.values.body_weights.map( + (weight, idx) => { + if (idx === editingAverageIndex || manuallyEditedRows.has(idx)) { + return weight; + } + return { + ...weight, + avg_weight: + weight.qty > 0 && weight.weight > 0 + ? parseFloat((weight.weight / weight.qty).toFixed(2)) + : 0, + }; + } + ); + const hasChanges = updatedBodyWeights.some( + (updated, idx) => + idx !== editingAverageIndex && + !manuallyEditedRows.has(idx) && + updated.avg_weight !== + (formik.values.body_weights[idx]?.avg_weight || 0) + ); + if (hasChanges) { + formik.setFieldValue('body_weights', updatedBodyWeights, false); + } + } + }, [ + formik.values.body_weights?.map((w) => w.weight), + formik.values.body_weights?.map((w) => w.qty), + editingAverageIndex, + manuallyEditedRows, + ]); + return ( <>
@@ -843,7 +947,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { )} - Rata-rata Berat Ayam (gram) + Berat Ayam (gram) { * + + Rata-rata Berat Ayam (gram) + + * + + {type !== 'detail' && Action} @@ -895,9 +1008,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { { decimalSeparator='.' inputSuffix='gram' isError={ - isRepeaterInputError( - 'body_weights', - 'avg_weight', - idx - ).isError + isRepeaterInputError('body_weights', 'weight', idx) + .isError } errorMessage={ - isRepeaterInputError( - 'body_weights', - 'avg_weight', - idx - ).errorMessage + isRepeaterInputError('body_weights', 'weight', idx) + .errorMessage } readOnly={type === 'detail'} className={{ @@ -949,6 +1056,39 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }} /> + + + + {type !== 'detail' && (