- 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.
194 lines
6.0 KiB
JavaScript
194 lines
6.0 KiB
JavaScript
import { useState, useEffect, useContext, useCallback, useRef } from 'react'
|
|
import { Form } from 'antd'
|
|
import { ApiServerContext } from '../context/ApiServerContext'
|
|
import { useMessageContext } from '../context/MessageContext'
|
|
import PropTypes from 'prop-types'
|
|
import set from 'lodash/set'
|
|
import { getModelByName } from '../../../database/ObjectModels'
|
|
import {
|
|
mergeFormData,
|
|
stripNestedObjectProperties,
|
|
calculateModelComputedEntries
|
|
} from '../utils/Utils'
|
|
|
|
const buildObjectFromEntries = (entries = []) => {
|
|
return entries.reduce((acc, entry) => {
|
|
const { namePath, value } = entry || {}
|
|
if (!Array.isArray(namePath) || value === undefined) {
|
|
return acc
|
|
}
|
|
set(acc, namePath, value)
|
|
return acc
|
|
}, {})
|
|
}
|
|
|
|
const applyComputedEntries = (base, entries = []) => {
|
|
const result = mergeFormData(base || {})
|
|
entries.forEach((entry) => {
|
|
const { namePath, value } = entry || {}
|
|
if (!Array.isArray(namePath) || value === undefined) return
|
|
set(result, namePath, value)
|
|
})
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* NewObjectForm is a reusable form component for creating new objects.
|
|
* It handles form validation, submission, and error handling logic.
|
|
*
|
|
* Props:
|
|
* - type: string (required)
|
|
* - formItems: array (for ObjectInfo/ObjectProperty items)
|
|
* - defaultValues: object (optional) - initial values for the form
|
|
* - children: function({
|
|
* loading, submitLoading, disabled, handleSubmit, form, formValid, objectData, setObjectData
|
|
* }) => ReactNode
|
|
*/
|
|
const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|
const [objectData, setObjectData] = useState({
|
|
...defaultValues,
|
|
_isEditing: true
|
|
})
|
|
const [submitLoading, setSubmitLoading] = useState(false)
|
|
const [formValid, setFormValid] = useState(false)
|
|
const [form] = Form.useForm()
|
|
const validationRunRef = useRef(0)
|
|
const formUpdateValues = Form.useWatch([], form)
|
|
const { showSuccess, showError: showMessageError } = useMessageContext()
|
|
const { createObject, showError } = useContext(ApiServerContext)
|
|
|
|
const validateForm = useCallback(() => {
|
|
const validationRun = ++validationRunRef.current
|
|
let cancelled = false
|
|
const timeoutId = setTimeout(() => {
|
|
form
|
|
.validateFields({ validateOnly: true })
|
|
.then(() => {
|
|
if (!cancelled && validationRun === validationRunRef.current) {
|
|
setFormValid(true)
|
|
}
|
|
})
|
|
.catch(() => {
|
|
if (!cancelled && validationRun === validationRunRef.current) {
|
|
setFormValid(false)
|
|
}
|
|
})
|
|
}, 0)
|
|
|
|
return () => {
|
|
cancelled = true
|
|
clearTimeout(timeoutId)
|
|
}
|
|
}, [form])
|
|
|
|
const model = getModelByName(type)
|
|
|
|
const calculateComputedValues = useCallback(
|
|
(currentData, modelDefinition, options = {}) => {
|
|
return calculateModelComputedEntries(currentData, modelDefinition, options)
|
|
},
|
|
[]
|
|
)
|
|
|
|
// Set initial form values when defaultValues change
|
|
useEffect(() => {
|
|
if (Object.keys(defaultValues).length > 0) {
|
|
const computedEntries = calculateComputedValues(defaultValues, model)
|
|
const initialFormData = applyComputedEntries(
|
|
defaultValues,
|
|
computedEntries
|
|
)
|
|
form.setFieldsValue(initialFormData)
|
|
setObjectData((prev) => mergeFormData(prev, initialFormData))
|
|
return validateForm()
|
|
}
|
|
}, [form, defaultValues, calculateComputedValues, model, validateForm])
|
|
|
|
// Validate form on change
|
|
useEffect(() => {
|
|
return validateForm()
|
|
}, [validateForm, formUpdateValues])
|
|
|
|
const handleSubmit = async () => {
|
|
try {
|
|
setSubmitLoading(true)
|
|
const currentFormValues = form.getFieldsValue()
|
|
const currentFormData = mergeFormData(objectData || {}, currentFormValues)
|
|
const computedEntries = calculateComputedValues(currentFormData, model)
|
|
const computedObjectData = applyComputedEntries(
|
|
currentFormData,
|
|
computedEntries
|
|
)
|
|
const payload = stripNestedObjectProperties(computedObjectData, model)
|
|
const newObject = await createObject(type, payload)
|
|
showSuccess('Object created successfully')
|
|
return newObject
|
|
} catch (err) {
|
|
console.error(err)
|
|
if (err.errorFields) {
|
|
return
|
|
}
|
|
showMessageError('Failed to create object')
|
|
showError(
|
|
`Failed to create object. Message: ${err.message}. Code: ${err.code}`,
|
|
() => handleSubmit()
|
|
)
|
|
} finally {
|
|
setSubmitLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Form
|
|
form={form}
|
|
layout='vertical'
|
|
style={style}
|
|
disabled={submitLoading}
|
|
onValuesChange={(_changedValues, allFormValues) => {
|
|
const currentFormData = mergeFormData(objectData || {}, allFormValues)
|
|
const computedEntries = calculateComputedValues(currentFormData, model)
|
|
|
|
if (Array.isArray(computedEntries) && computedEntries.length > 0) {
|
|
computedEntries.forEach(({ namePath, value }) => {
|
|
if (!Array.isArray(namePath) || value === undefined) return
|
|
const currentValue = form.getFieldValue(namePath)
|
|
if (currentValue !== value) {
|
|
if (typeof form.setFieldValue === 'function') {
|
|
form.setFieldValue(namePath, value)
|
|
} else {
|
|
const fallbackPayload = buildObjectFromEntries([
|
|
{ namePath, value }
|
|
])
|
|
form.setFieldsValue(fallbackPayload)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const allValues = applyComputedEntries(allFormValues, computedEntries)
|
|
setObjectData((prev) => mergeFormData(prev, allValues))
|
|
}}
|
|
>
|
|
{children({
|
|
loading: submitLoading,
|
|
submitLoading,
|
|
disabled: submitLoading,
|
|
handleSubmit,
|
|
form,
|
|
formValid,
|
|
objectData,
|
|
setObjectData
|
|
})}
|
|
</Form>
|
|
)
|
|
}
|
|
|
|
NewObjectForm.propTypes = {
|
|
type: PropTypes.string.isRequired,
|
|
children: PropTypes.func.isRequired,
|
|
style: PropTypes.object,
|
|
defaultValues: PropTypes.object
|
|
}
|
|
|
|
export default NewObjectForm
|