From c99227816e5e485adcbfcb9859c270748a44f0c2 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sat, 22 Aug 2026 21:01:50 +0100 Subject: [PATCH] Refactor Object Form and Child Table for Improved Data Handling and Computed Values Management - Introduced applyComputedEntries function to streamline the merging of computed entries with existing object data, enhancing clarity and maintainability. - Updated NewObjectForm and ObjectForm components to utilize applyComputedEntries for setting initial form data and computed values, improving data handling consistency. - Refactored ObjectChildTable to ensure proper handling of value props and fallback mechanisms, enhancing user experience during editing. - Removed unused DeleteObjectModal from ObjectForm, simplifying the component structure. --- .../Dashboard/common/NewObjectForm.jsx | 34 +++++++------- .../Dashboard/common/ObjectChildTable.jsx | 24 +++++----- .../Dashboard/common/ObjectForm.jsx | 45 +++++++++---------- 3 files changed, 48 insertions(+), 55 deletions(-) diff --git a/src/components/Dashboard/common/NewObjectForm.jsx b/src/components/Dashboard/common/NewObjectForm.jsx index aba99983..0b11e1fc 100644 --- a/src/components/Dashboard/common/NewObjectForm.jsx +++ b/src/components/Dashboard/common/NewObjectForm.jsx @@ -3,6 +3,7 @@ import { Form } from 'antd' import { ApiServerContext } from '../context/ApiServerContext' import { useMessageContext } from '../context/MessageContext' import PropTypes from 'prop-types' +import merge from 'lodash/merge' import mergeWith from 'lodash/mergeWith' import set from 'lodash/set' import { getModelByName } from '../../../database/ObjectModels' @@ -18,6 +19,16 @@ const buildObjectFromEntries = (entries = []) => { }, {}) } +const applyComputedEntries = (base, entries = []) => { + const result = merge({}, base || {}) + entries.forEach((entry) => { + const { namePath, value } = entry || {} + if (!Array.isArray(namePath) || value === undefined) return + set(result, namePath, value) + }) + return result +} + const arrayReplaceCustomizer = (objValue, srcValue, key) => { if (Array.isArray(srcValue)) { return srcValue @@ -172,12 +183,9 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => { if (Object.keys(defaultValues).length > 0) { // Calculate computed values for initial data const computedEntries = calculateComputedValues(defaultValues, model) - const computedValuesObject = buildObjectFromEntries(computedEntries) - const initialFormData = mergeWith( - {}, + const initialFormData = applyComputedEntries( defaultValues, - computedValuesObject, - arrayReplaceCustomizer + computedEntries ) form.setFieldsValue(initialFormData) setObjectData((prev) => @@ -196,12 +204,9 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => { try { setSubmitLoading(true) const computedEntries = calculateComputedValues(objectData, model) - const computedValuesObject = buildObjectFromEntries(computedEntries) - const computedObjectData = mergeWith( - {}, + const computedObjectData = applyComputedEntries( objectData, - computedValuesObject, - arrayReplaceCustomizer + computedEntries ) const newObject = await createObject(type, computedObjectData) showSuccess('Object created successfully') @@ -253,14 +258,7 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => { }) } - // Merge all values (user input + computed values) - const computedValuesObject = buildObjectFromEntries(computedEntries) - const allValues = mergeWith( - {}, - allFormValues, - computedValuesObject, - arrayReplaceCustomizer - ) + const allValues = applyComputedEntries(allFormValues, computedEntries) setObjectData((prev) => { return mergeWith({}, prev, allValues, arrayReplaceCustomizer) }) diff --git a/src/components/Dashboard/common/ObjectChildTable.jsx b/src/components/Dashboard/common/ObjectChildTable.jsx index 6ef12fad..46cef44d 100644 --- a/src/components/Dashboard/common/ObjectChildTable.jsx +++ b/src/components/Dashboard/common/ObjectChildTable.jsx @@ -64,7 +64,7 @@ const ObjectChildTable = ({ additionalColumns = [], emptyText = 'No items', isEditing = false, - value = [], + value, rollups = [], onChange, hiddenPropertyWidth = '300px', @@ -159,10 +159,13 @@ const ObjectChildTable = ({ ) }, [columns, properties]) - // When used inside antd Form.Item without Form.List, `value` will be the controlled array. + // Form.Item may not have hydrated yet; fall back to objectData the same way + // rollups already do. const itemsSource = useMemo(() => { - return value ?? [] - }, [value]) + if (Array.isArray(value)) return value + if (name && Array.isArray(objectData?.[name])) return objectData[name] + return [] + }, [value, name, objectData]) const handleRemoveItem = useCallback( (record, index) => { @@ -265,12 +268,11 @@ const ObjectChildTable = ({ ) @@ -384,10 +386,7 @@ const ObjectChildTable = ({ const rollupDataSource = useMemo(() => { if (!rollups || rollups.length === 0) return [] - // Use value from form/props, or fall back to objectData when entering edit mode - // (form may not have populated the field yet) - const itemsForRollup = - value ?? (name && objectData ? objectData[name] : null) ?? [] + const itemsForRollup = itemsSource // Build parent object with children array for rollup functions (e.g. objectData.parts) const updatedObjectData = { ...objectData } @@ -420,7 +419,7 @@ const ObjectChildTable = ({ }) return [summaryRow] - }, [properties, rollups, objectData, value, name]) + }, [properties, rollups, objectData, itemsSource, name]) const rollupColumns = useMemo(() => { const propertyColumns = resolvedProperties.map((property, index) => { @@ -634,7 +633,8 @@ const ObjectChildTable = ({ setMinimalModelOpen(true) }} > - {value?.length || 0} {value?.length == 1 ? 'item' : 'items'} + {itemsSource?.length || 0}{' '} + {itemsSource?.length == 1 ? 'item' : 'items'} { }, {}) } +// Patch computed fields onto a full object. Building them as a sparse tree and +// merge-replacing arrays would wipe stored child-row fields (shipment, amount, +// etc.) while leaving calculated columns intact. +const applyComputedEntries = (base, entries = []) => { + const result = merge({}, base || {}) + entries.forEach((entry) => { + const { namePath, value } = entry || {} + if (!Array.isArray(namePath) || value === undefined) return + set(result, namePath, value) + }) + return result +} + const getEditDisabled = (model, objectData, userProfile) => { if (model?.readOnly === true) { return true @@ -356,12 +369,9 @@ const ObjectForm = forwardRef( serverObjectData.current, model ) - const computedValuesObject = buildObjectFromEntries(computedEntries) - const resetFormData = mergeWith( - {}, + const resetFormData = applyComputedEntries( serverObjectData.current, - computedValuesObject, - arrayReplaceCustomizer + computedEntries ) setIsEditing(false) isEditingRef.current = false @@ -498,13 +508,7 @@ const ObjectForm = forwardRef( // Calculate and set computed values on initial load const computedEntries = calculateComputedValues(data, model) - const computedValuesObject = buildObjectFromEntries(computedEntries) - const initialFormData = mergeWith( - {}, - data, - computedValuesObject, - arrayReplaceCustomizer - ) + const initialFormData = applyComputedEntries(data, computedEntries) setObjectData({ ...initialFormData, _isEditing: isEditingRef.current }) form.setFieldsValue(initialFormData) setFetchLoading(false) @@ -703,17 +707,11 @@ const ObjectForm = forwardRef( setIsEditing(true) const computedEntries = calculateComputedValues(objectData, model) - const computedValuesObject = buildObjectFromEntries(computedEntries) - setObjectData((prev) => ({ - ...prev, - ...computedValuesObject, - _isEditing: true - })) const nextObjectData = { - ...objectData, - ...computedValuesObject, + ...applyComputedEntries(objectData, computedEntries), _isEditing: true } + setObjectData(nextObjectData) onStateChangeRef.current({ isEditing: true, objectData: nextObjectData, @@ -868,12 +866,9 @@ const ObjectForm = forwardRef( }) } - const computedValuesObject = buildObjectFromEntries(computedEntries) - const mergedFormValues = mergeWith( - {}, + const mergedFormValues = applyComputedEntries( allFormValues, - computedValuesObject, - arrayReplaceCustomizer + computedEntries ) mergedFormValues._isEditing = isEditingRef.current