Enhance NewObjectButtons, NewObjectForm, and related components with improved loading and disabled state handling
- Updated NewObjectButtons to disable controls based on loading state and current step. - Refactored NewObjectForm to integrate computed entries calculation and manage loading state more effectively. - Enhanced ObjectProperty and WizardView components to respect the disabled state during form interactions. - Introduced new utility functions for calculating model computed entries, improving data handling and clarity.
This commit is contained in:
parent
a41f168adc
commit
2feba8b3cc
@ -12,13 +12,15 @@ const NewObjectButtons = ({
|
||||
submitText = 'Done',
|
||||
disabled = false
|
||||
}) => {
|
||||
const controlsDisabled = disabled || submitLoading
|
||||
|
||||
return (
|
||||
<Flex justify='end'>
|
||||
{totalSteps > 1 ? (
|
||||
<Button
|
||||
style={{ margin: '0 8px' }}
|
||||
onClick={onPrevious}
|
||||
disabled={currentStep === 0}
|
||||
disabled={currentStep === 0 || controlsDisabled}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
@ -27,7 +29,7 @@ const NewObjectButtons = ({
|
||||
{currentStep < totalSteps - 1 ? (
|
||||
<Button
|
||||
type='primary'
|
||||
disabled={!formValid || disabled}
|
||||
disabled={!formValid || controlsDisabled}
|
||||
onClick={onNext}
|
||||
>
|
||||
Next
|
||||
@ -36,7 +38,7 @@ const NewObjectButtons = ({
|
||||
<Button
|
||||
type='primary'
|
||||
loading={submitLoading}
|
||||
disabled={!formValid || disabled}
|
||||
disabled={!formValid || controlsDisabled}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
{submitText}
|
||||
|
||||
@ -5,7 +5,11 @@ import { useMessageContext } from '../context/MessageContext'
|
||||
import PropTypes from 'prop-types'
|
||||
import set from 'lodash/set'
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
import { mergeFormData, stripNestedObjectProperties } from '../utils/Utils'
|
||||
import {
|
||||
mergeFormData,
|
||||
stripNestedObjectProperties,
|
||||
calculateModelComputedEntries
|
||||
} from '../utils/Utils'
|
||||
|
||||
const buildObjectFromEntries = (entries = []) => {
|
||||
return entries.reduce((acc, entry) => {
|
||||
@ -37,7 +41,7 @@ const applyComputedEntries = (base, entries = []) => {
|
||||
* - formItems: array (for ObjectInfo/ObjectProperty items)
|
||||
* - defaultValues: object (optional) - initial values for the form
|
||||
* - children: function({
|
||||
* loading, isSubmitting, handleSubmit, form, formValid, objectData, setObjectData
|
||||
* loading, submitLoading, disabled, handleSubmit, form, formValid, objectData, setObjectData
|
||||
* }) => ReactNode
|
||||
*/
|
||||
const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
||||
@ -77,93 +81,11 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
||||
}
|
||||
}, [form])
|
||||
|
||||
// Get the model definition for this object type
|
||||
const model = getModelByName(type)
|
||||
|
||||
// Function to calculate computed values from model properties
|
||||
const calculateComputedValues = useCallback(
|
||||
(currentData, modelDefinition) => {
|
||||
if (!modelDefinition || !Array.isArray(modelDefinition.properties)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const normalizedPath = (name, parentPath = []) => {
|
||||
if (Array.isArray(name)) {
|
||||
return [...parentPath, ...name]
|
||||
}
|
||||
if (typeof name === 'number') {
|
||||
return [...parentPath, name]
|
||||
}
|
||||
if (typeof name === 'string' && name.length > 0) {
|
||||
return [...parentPath, ...name.split('.')]
|
||||
}
|
||||
return parentPath
|
||||
}
|
||||
|
||||
const getValueAtPath = (dataSource, path) => {
|
||||
if (!Array.isArray(path) || path.length === 0) {
|
||||
return dataSource
|
||||
}
|
||||
return path.reduce((acc, key) => {
|
||||
if (acc == null) return acc
|
||||
return acc[key]
|
||||
}, dataSource)
|
||||
}
|
||||
|
||||
const computedEntries = []
|
||||
|
||||
const processProperty = (property, scopeData, parentPath = []) => {
|
||||
if (!property?.name) return
|
||||
|
||||
const propertyPath = normalizedPath(property.name, parentPath)
|
||||
|
||||
if (property.value && typeof property.value === 'function') {
|
||||
try {
|
||||
const computedValue = property.value(scopeData || {})
|
||||
if (computedValue !== undefined) {
|
||||
computedEntries.push({
|
||||
namePath: propertyPath,
|
||||
value: computedValue
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Error calculating value for property ${property.name}:`,
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(property.properties) &&
|
||||
property.properties.length > 0
|
||||
) {
|
||||
if (property.type === 'objectChildren') {
|
||||
const childValues = getValueAtPath(currentData, propertyPath)
|
||||
if (Array.isArray(childValues)) {
|
||||
childValues.forEach((childData = {}, index) => {
|
||||
property.properties.forEach((childProperty) => {
|
||||
processProperty(childProperty, childData || {}, [
|
||||
...propertyPath,
|
||||
index
|
||||
])
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const nestedScope = getValueAtPath(currentData, propertyPath) || {}
|
||||
property.properties.forEach((childProperty) => {
|
||||
processProperty(childProperty, nestedScope || {}, propertyPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modelDefinition.properties.forEach((property) => {
|
||||
processProperty(property, currentData)
|
||||
})
|
||||
|
||||
return computedEntries
|
||||
(currentData, modelDefinition, options = {}) => {
|
||||
return calculateModelComputedEntries(currentData, modelDefinition, options)
|
||||
},
|
||||
[]
|
||||
)
|
||||
@ -171,7 +93,6 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
||||
// Set initial form values when defaultValues change
|
||||
useEffect(() => {
|
||||
if (Object.keys(defaultValues).length > 0) {
|
||||
// Calculate computed values for initial data
|
||||
const computedEntries = calculateComputedValues(defaultValues, model)
|
||||
const initialFormData = applyComputedEntries(
|
||||
defaultValues,
|
||||
@ -191,9 +112,11 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setSubmitLoading(true)
|
||||
const computedEntries = calculateComputedValues(objectData, model)
|
||||
const currentFormValues = form.getFieldsValue()
|
||||
const currentFormData = mergeFormData(objectData || {}, currentFormValues)
|
||||
const computedEntries = calculateComputedValues(currentFormData, model)
|
||||
const computedObjectData = applyComputedEntries(
|
||||
objectData,
|
||||
currentFormData,
|
||||
computedEntries
|
||||
)
|
||||
const payload = stripNestedObjectProperties(computedObjectData, model)
|
||||
@ -220,8 +143,8 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
||||
form={form}
|
||||
layout='vertical'
|
||||
style={style}
|
||||
disabled={submitLoading}
|
||||
onValuesChange={(_changedValues, allFormValues) => {
|
||||
// Calculate computed values based on current form data
|
||||
const currentFormData = mergeFormData(objectData || {}, allFormValues)
|
||||
const computedEntries = calculateComputedValues(currentFormData, model)
|
||||
|
||||
@ -247,7 +170,9 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
||||
}}
|
||||
>
|
||||
{children({
|
||||
loading: submitLoading,
|
||||
submitLoading,
|
||||
disabled: submitLoading,
|
||||
handleSubmit,
|
||||
form,
|
||||
formValid,
|
||||
|
||||
@ -814,7 +814,7 @@ const ObjectProperty = ({
|
||||
}
|
||||
|
||||
const inputProps = useFormItem
|
||||
? {}
|
||||
? { disabled }
|
||||
: {
|
||||
value,
|
||||
onChange,
|
||||
|
||||
@ -195,7 +195,7 @@ const WizardView = ({
|
||||
|
||||
{showButtons && (
|
||||
<NewObjectButtons
|
||||
disabled={disabled}
|
||||
disabled={disabled || loading}
|
||||
currentStep={currentStep}
|
||||
totalSteps={steps.length}
|
||||
onPrevious={() => setCurrentStep(currentStep - 1)}
|
||||
|
||||
@ -144,3 +144,137 @@ const stripProperties = (data, properties) => {
|
||||
export function stripNestedObjectProperties(data, modelDefinition) {
|
||||
return stripProperties(data, modelDefinition?.properties)
|
||||
}
|
||||
|
||||
const collectModelPropertyPaths = (modelDefinition) => {
|
||||
const paths = []
|
||||
|
||||
const visit = (properties) => {
|
||||
;(properties || []).forEach((property) => {
|
||||
if (property?.name) {
|
||||
paths.push(property.name)
|
||||
}
|
||||
if (Array.isArray(property?.properties) && property.properties.length > 0) {
|
||||
visit(property.properties)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
visit(modelDefinition?.properties)
|
||||
return paths
|
||||
}
|
||||
|
||||
const pathToString = (path) =>
|
||||
Array.isArray(path) ? path.join('.') : String(path)
|
||||
|
||||
// Computed display fields (e.g. deviceInfo.cpu) must not replace parent objects
|
||||
// when sibling nested properties exist (e.g. deviceInfo.cpu.model).
|
||||
const hasNestedPropertyPaths = (propertyPath, allPropertyPaths) => {
|
||||
const pathStr = pathToString(propertyPath)
|
||||
const prefix = `${pathStr}.`
|
||||
return allPropertyPaths.some(
|
||||
(name) => name !== pathStr && name.startsWith(prefix)
|
||||
)
|
||||
}
|
||||
|
||||
export function calculateModelComputedEntries(
|
||||
currentData,
|
||||
modelDefinition,
|
||||
options = {}
|
||||
) {
|
||||
if (!modelDefinition || !Array.isArray(modelDefinition.properties)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const skipObjectChildrenValue = options.skipObjectChildrenValue === true
|
||||
const workingData = mergeFormData(currentData)
|
||||
|
||||
const normalizedPath = (name, parentPath = []) => {
|
||||
if (Array.isArray(name)) {
|
||||
return [...parentPath, ...name]
|
||||
}
|
||||
if (typeof name === 'number') {
|
||||
return [...parentPath, name]
|
||||
}
|
||||
if (typeof name === 'string' && name.length > 0) {
|
||||
return [...parentPath, ...name.split('.')]
|
||||
}
|
||||
return parentPath
|
||||
}
|
||||
|
||||
const getValueAtPath = (dataSource, path) => {
|
||||
if (!Array.isArray(path) || path.length === 0) {
|
||||
return dataSource
|
||||
}
|
||||
return path.reduce((acc, key) => {
|
||||
if (acc == null) return acc
|
||||
return acc[key]
|
||||
}, dataSource)
|
||||
}
|
||||
|
||||
const computedEntries = []
|
||||
const allPropertyPaths = collectModelPropertyPaths(modelDefinition)
|
||||
|
||||
const processProperty = (property, parentPath = []) => {
|
||||
if (!property?.name) return
|
||||
|
||||
const propertyPath = normalizedPath(property.name, parentPath)
|
||||
const scopeData =
|
||||
parentPath.length === 0
|
||||
? workingData
|
||||
: getValueAtPath(workingData, parentPath)
|
||||
|
||||
if (property.value && typeof property.value === 'function') {
|
||||
const skipValue =
|
||||
skipObjectChildrenValue && property.type === 'objectChildren'
|
||||
if (!skipValue) {
|
||||
try {
|
||||
const computedValue = property.value(scopeData || {})
|
||||
if (computedValue !== undefined) {
|
||||
const preserveNestedObject = hasNestedPropertyPaths(
|
||||
propertyPath,
|
||||
allPropertyPaths
|
||||
)
|
||||
if (!preserveNestedObject) {
|
||||
computedEntries.push({
|
||||
namePath: propertyPath,
|
||||
value: computedValue
|
||||
})
|
||||
set(workingData, propertyPath, computedValue)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Error calculating value for property ${property.name}:`,
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(property.properties) &&
|
||||
property.properties.length > 0
|
||||
) {
|
||||
if (property.type === 'objectChildren') {
|
||||
const childValues = getValueAtPath(workingData, propertyPath)
|
||||
if (Array.isArray(childValues)) {
|
||||
childValues.forEach((_, index) => {
|
||||
property.properties.forEach((childProperty) => {
|
||||
processProperty(childProperty, [...propertyPath, index])
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
property.properties.forEach((childProperty) => {
|
||||
processProperty(childProperty, propertyPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modelDefinition.properties.forEach((property) => {
|
||||
processProperty(property, [])
|
||||
})
|
||||
|
||||
return computedEntries
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user