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.
This commit is contained in:
parent
978f444538
commit
c99227816e
@ -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)
|
||||
})
|
||||
|
||||
@ -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 = ({
|
||||
<ObjectProperty
|
||||
{...property}
|
||||
longId={false}
|
||||
inTable={true}
|
||||
objectData={record}
|
||||
parentData={objectData}
|
||||
isEditing={isEditing}
|
||||
useFormItem={false}
|
||||
name={undefined}
|
||||
value={record[property.name]}
|
||||
onChange={handleCellChange}
|
||||
/>
|
||||
)
|
||||
@ -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'}
|
||||
</Link>
|
||||
<Modal
|
||||
open={minimalModelOpen}
|
||||
|
||||
@ -79,6 +79,19 @@ const buildObjectFromEntries = (entries = []) => {
|
||||
}, {})
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user