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 (
) } NewObjectForm.propTypes = { type: PropTypes.string.isRequired, children: PropTypes.func.isRequired, style: PropTypes.object, defaultValues: PropTypes.object } export default NewObjectForm