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',
|
submitText = 'Done',
|
||||||
disabled = false
|
disabled = false
|
||||||
}) => {
|
}) => {
|
||||||
|
const controlsDisabled = disabled || submitLoading
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify='end'>
|
<Flex justify='end'>
|
||||||
{totalSteps > 1 ? (
|
{totalSteps > 1 ? (
|
||||||
<Button
|
<Button
|
||||||
style={{ margin: '0 8px' }}
|
style={{ margin: '0 8px' }}
|
||||||
onClick={onPrevious}
|
onClick={onPrevious}
|
||||||
disabled={currentStep === 0}
|
disabled={currentStep === 0 || controlsDisabled}
|
||||||
>
|
>
|
||||||
Previous
|
Previous
|
||||||
</Button>
|
</Button>
|
||||||
@ -27,7 +29,7 @@ const NewObjectButtons = ({
|
|||||||
{currentStep < totalSteps - 1 ? (
|
{currentStep < totalSteps - 1 ? (
|
||||||
<Button
|
<Button
|
||||||
type='primary'
|
type='primary'
|
||||||
disabled={!formValid || disabled}
|
disabled={!formValid || controlsDisabled}
|
||||||
onClick={onNext}
|
onClick={onNext}
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
@ -36,7 +38,7 @@ const NewObjectButtons = ({
|
|||||||
<Button
|
<Button
|
||||||
type='primary'
|
type='primary'
|
||||||
loading={submitLoading}
|
loading={submitLoading}
|
||||||
disabled={!formValid || disabled}
|
disabled={!formValid || controlsDisabled}
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
{submitText}
|
{submitText}
|
||||||
|
|||||||
@ -5,7 +5,11 @@ import { useMessageContext } from '../context/MessageContext'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import set from 'lodash/set'
|
import set from 'lodash/set'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import { mergeFormData, stripNestedObjectProperties } from '../utils/Utils'
|
import {
|
||||||
|
mergeFormData,
|
||||||
|
stripNestedObjectProperties,
|
||||||
|
calculateModelComputedEntries
|
||||||
|
} from '../utils/Utils'
|
||||||
|
|
||||||
const buildObjectFromEntries = (entries = []) => {
|
const buildObjectFromEntries = (entries = []) => {
|
||||||
return entries.reduce((acc, entry) => {
|
return entries.reduce((acc, entry) => {
|
||||||
@ -37,7 +41,7 @@ const applyComputedEntries = (base, entries = []) => {
|
|||||||
* - formItems: array (for ObjectInfo/ObjectProperty items)
|
* - formItems: array (for ObjectInfo/ObjectProperty items)
|
||||||
* - defaultValues: object (optional) - initial values for the form
|
* - defaultValues: object (optional) - initial values for the form
|
||||||
* - children: function({
|
* - children: function({
|
||||||
* loading, isSubmitting, handleSubmit, form, formValid, objectData, setObjectData
|
* loading, submitLoading, disabled, handleSubmit, form, formValid, objectData, setObjectData
|
||||||
* }) => ReactNode
|
* }) => ReactNode
|
||||||
*/
|
*/
|
||||||
const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
||||||
@ -77,93 +81,11 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|||||||
}
|
}
|
||||||
}, [form])
|
}, [form])
|
||||||
|
|
||||||
// Get the model definition for this object type
|
|
||||||
const model = getModelByName(type)
|
const model = getModelByName(type)
|
||||||
|
|
||||||
// Function to calculate computed values from model properties
|
|
||||||
const calculateComputedValues = useCallback(
|
const calculateComputedValues = useCallback(
|
||||||
(currentData, modelDefinition) => {
|
(currentData, modelDefinition, options = {}) => {
|
||||||
if (!modelDefinition || !Array.isArray(modelDefinition.properties)) {
|
return calculateModelComputedEntries(currentData, modelDefinition, options)
|
||||||
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
|
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
@ -171,7 +93,6 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|||||||
// Set initial form values when defaultValues change
|
// Set initial form values when defaultValues change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Object.keys(defaultValues).length > 0) {
|
if (Object.keys(defaultValues).length > 0) {
|
||||||
// Calculate computed values for initial data
|
|
||||||
const computedEntries = calculateComputedValues(defaultValues, model)
|
const computedEntries = calculateComputedValues(defaultValues, model)
|
||||||
const initialFormData = applyComputedEntries(
|
const initialFormData = applyComputedEntries(
|
||||||
defaultValues,
|
defaultValues,
|
||||||
@ -191,9 +112,11 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
setSubmitLoading(true)
|
setSubmitLoading(true)
|
||||||
const computedEntries = calculateComputedValues(objectData, model)
|
const currentFormValues = form.getFieldsValue()
|
||||||
|
const currentFormData = mergeFormData(objectData || {}, currentFormValues)
|
||||||
|
const computedEntries = calculateComputedValues(currentFormData, model)
|
||||||
const computedObjectData = applyComputedEntries(
|
const computedObjectData = applyComputedEntries(
|
||||||
objectData,
|
currentFormData,
|
||||||
computedEntries
|
computedEntries
|
||||||
)
|
)
|
||||||
const payload = stripNestedObjectProperties(computedObjectData, model)
|
const payload = stripNestedObjectProperties(computedObjectData, model)
|
||||||
@ -220,8 +143,8 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|||||||
form={form}
|
form={form}
|
||||||
layout='vertical'
|
layout='vertical'
|
||||||
style={style}
|
style={style}
|
||||||
|
disabled={submitLoading}
|
||||||
onValuesChange={(_changedValues, allFormValues) => {
|
onValuesChange={(_changedValues, allFormValues) => {
|
||||||
// Calculate computed values based on current form data
|
|
||||||
const currentFormData = mergeFormData(objectData || {}, allFormValues)
|
const currentFormData = mergeFormData(objectData || {}, allFormValues)
|
||||||
const computedEntries = calculateComputedValues(currentFormData, model)
|
const computedEntries = calculateComputedValues(currentFormData, model)
|
||||||
|
|
||||||
@ -247,7 +170,9 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children({
|
{children({
|
||||||
|
loading: submitLoading,
|
||||||
submitLoading,
|
submitLoading,
|
||||||
|
disabled: submitLoading,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
form,
|
form,
|
||||||
formValid,
|
formValid,
|
||||||
|
|||||||
@ -814,7 +814,7 @@ const ObjectProperty = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inputProps = useFormItem
|
const inputProps = useFormItem
|
||||||
? {}
|
? { disabled }
|
||||||
: {
|
: {
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
|||||||
@ -195,7 +195,7 @@ const WizardView = ({
|
|||||||
|
|
||||||
{showButtons && (
|
{showButtons && (
|
||||||
<NewObjectButtons
|
<NewObjectButtons
|
||||||
disabled={disabled}
|
disabled={disabled || loading}
|
||||||
currentStep={currentStep}
|
currentStep={currentStep}
|
||||||
totalSteps={steps.length}
|
totalSteps={steps.length}
|
||||||
onPrevious={() => setCurrentStep(currentStep - 1)}
|
onPrevious={() => setCurrentStep(currentStep - 1)}
|
||||||
|
|||||||
@ -144,3 +144,137 @@ const stripProperties = (data, properties) => {
|
|||||||
export function stripNestedObjectProperties(data, modelDefinition) {
|
export function stripNestedObjectProperties(data, modelDefinition) {
|
||||||
return stripProperties(data, modelDefinition?.properties)
|
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