From adc995dbe7dd19cdc4dcda9af19de3c30d063285 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 24 Oct 2025 11:00:14 +0700 Subject: [PATCH] feat(US-114): enhance auto-calculation logic in RecordingForm to handle manual edits --- .../recording/form/RecordingForm.tsx | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index e5a2531c..10d3aa30 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -45,6 +45,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // Track which average weight field is being edited to prevent auto-calculation override const [editingAverageIndex, setEditingAverageIndex] = useState(null); + // Track which rows have been manually edited to prevent auto-calculation override + const [manuallyEditedRows, setManuallyEditedRows] = useState>(new Set()); + // State for Location search and selection const [locationSearchValue, setLocationSearchValue] = useState(''); const [selectedLocation, setSelectedLocation] = useState(null); @@ -223,10 +226,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // Auto-calculate average weight when weight or qty changes (but not when editing average weight manually) useEffect(() => { + // Only run auto-calculation if no field is being edited if (formik.values.body_weights && editingAverageIndex === null) { const updatedBodyWeights = formik.values.body_weights.map((weight, idx) => { - // Skip auto-calculation for the field being manually edited - if (idx === editingAverageIndex) { + // Skip the field that's being edited or has been manually edited + if (idx === editingAverageIndex || manuallyEditedRows.has(idx)) { return weight; } @@ -234,7 +238,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ...weight, average_weight: weight.qty > 0 && weight.weight > 0 - ? Math.round(weight.weight / weight.qty) + ? parseFloat((weight.weight / weight.qty).toFixed(2)) : 0, }; }); @@ -243,11 +247,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const hasChanges = updatedBodyWeights.some( (updated, idx) => idx !== editingAverageIndex && // Skip the field being edited + !manuallyEditedRows.has(idx) && // Skip manually edited rows updated.average_weight !== (formik.values.body_weights[idx]?.average_weight || 0) ); if (hasChanges) { + // Use false to prevent triggering validation and other side effects formik.setFieldValue('body_weights', updatedBodyWeights, false); } } @@ -255,6 +261,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { formik.values.body_weights?.map((w) => w.weight), formik.values.body_weights?.map((w) => w.qty), editingAverageIndex, // Include editing index in dependencies + manuallyEditedRows, // Include manually edited rows in dependencies ]); // EVENT HANDLERS - Body Weights @@ -274,11 +281,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const handleWeightChange = (idx: number, value: number) => { formik.setFieldValue(`body_weights.${idx}.weight`, value); + // Reset manual edit flag when weight changes (user wants auto-calculation) + 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 averageWeight = Math.round(value / qty); + const averageWeight = parseFloat((value / qty).toFixed(2)); formik.setFieldValue(`body_weights.${idx}.average_weight`, averageWeight); } else { formik.setFieldValue(`body_weights.${idx}.average_weight`, 0); @@ -290,11 +304,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const handleQtyChange = (idx: number, value: number) => { formik.setFieldValue(`body_weights.${idx}.qty`, value); + // Reset manual edit flag when qty changes (user wants auto-calculation) + 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 averageWeight = Math.round(weight / value); + const averageWeight = parseFloat((weight / value).toFixed(2)); formik.setFieldValue(`body_weights.${idx}.average_weight`, averageWeight); } else { formik.setFieldValue(`body_weights.${idx}.average_weight`, 0); @@ -320,12 +341,20 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // Create wrapper handlers that match NumberInput's onChange signature const handleWeightChangeWrapper = (idx: number) => (e: React.ChangeEvent) => { - const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0; + // Parse the value more carefully to handle decimal numbers properly + const rawValue = e.target.value.replace(/[^\d,.-]/g, ''); + // Convert comma thousand separator to nothing, but keep decimal point + const normalizedValue = rawValue.replace(/,/g, ''); + const value = parseFloat(normalizedValue) || 0; handleWeightChange(idx, value); }; const handleQtyChangeWrapper = (idx: number) => (e: React.ChangeEvent) => { - const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0; + // Parse the value more carefully to handle decimal numbers properly + const rawValue = e.target.value.replace(/[^\d,.-]/g, ''); + // Convert comma thousand separator to nothing, but keep decimal point + const normalizedValue = rawValue.replace(/,/g, ''); + const value = parseFloat(normalizedValue) || 0; handleQtyChange(idx, value); }; @@ -333,11 +362,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // Set focus state to prevent auto-calculation override setEditingAverageIndex(idx); - const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0; + // Mark this row as manually edited + setManuallyEditedRows(prev => new Set(prev).add(idx)); + + // Parse the value more carefully to handle decimal numbers properly + const rawValue = e.target.value.replace(/[^\d,.-]/g, ''); + // Convert comma thousand separator to nothing, but keep decimal point + const normalizedValue = rawValue.replace(/,/g, ''); + const value = parseFloat(normalizedValue) || 0; handleAverageWeightChange(idx, value); }; - const handleAverageWeightBlur = () => { + const handleAverageWeightBlur = (idx: number) => { // Clear focus state when user leaves the field to re-enable auto-calculation setEditingAverageIndex(null); }; @@ -700,7 +736,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={bw.average_weight || 0} onChange={handleAverageWeightChangeWrapper(idx)} onBlur={(e) => { - handleAverageWeightBlur(); + handleAverageWeightBlur(idx); formik.handleBlur(e); }} maskType='weight'