import PropTypes from 'prop-types' import { Typography, Badge, Input, InputNumber, Form, Select, Switch, Tree } from 'antd' import IdDisplay from './IdDisplay' import TimeDisplay from './TimeDisplay' import TimeEdit from './TimeEdit' import dayjs from 'dayjs' import EmailDisplay from './EmailDisplay' import UrlDisplay from './UrlDisplay' import CountryDisplay from './CountryDisplay' import CountrySelect from './CountrySelect' import TagsDisplay from './TagsDisplay' import TagsInput from './TagsInput' import BoolDisplay from './BoolDisplay' import ColorSelector from './ColorSelector' import SecretDisplay from './SecretDisplay' import EyeIcon from '../../Icons/EyeIcon' import EyeSlashIcon from '../../Icons/EyeSlashIcon' import { getPropertyValue } from '../../../database/ObjectModels' import PropertyChanges from './PropertyChanges' import NetGrossDisplay from './NetGrossDisplay' import NetGrossInput from './NetGrossInput' import MinMaxDisplay from './MinMaxDisplay' import MinMaxInput from './MinMaxInput' import InputNumberCal from './InputNumberCal' import ObjectList from './ObjectList' import VarianceDisplay from './VarianceDisplay' import OperationDisplay from './OperationDisplay' import MarkdownDisplay from './MarkdownDisplay' import MarkdownInput from './MarkdownInput' import ObjectSelect from './ObjectSelect' import ObjectDisplay from './ObjectDisplay' import ObjectTypeSelect from './ObjectTypeSelect' import ObjectTypeDisplay from './ObjectTypeDisplay' import CodeBlockEditor from './CodeBlockEditor' import CustomSelect from './CustomSelect' import CustomTreeSelect, { findTreeLabel } from './CustomTreeSelect' import StateDisplay from './StateDisplay' import AlertsDisplay from './AlertsDisplay' import FileUpload from './FileUpload' import DataTree from './DataTree' import FileList from './FileList' import ObjectChildTable from './ObjectChildTable' import MiscId from './MiscId' import AddressDisplay from './AddressDisplay' import ArrayDisplay from './ArrayDisplay' import ArrayInput from './ArrayInput' import ElipsisText from './ElipsisText' import PermissionsMatrix from './PermissionsMatrix' import { round } from '../utils/Utils' const { Text } = Typography const getTextValue = (value) => { if (value == null || typeof value === 'string' || typeof value === 'number') { return value } if (typeof value === 'object') { if (typeof value._id === 'string') { return value._id } if (typeof value._id?._id === 'string') { return value._id._id } try { return JSON.stringify(value) } catch { return String(value) } } return String(value) } const timeEditFormItemProps = { getValueProps: (v) => ({ value: v ? dayjs(v) : null }) } const ObjectProperty = ({ modelType = 'unknown', type = 'text', prefix, size, suffix, value, min, max, difference, step, isEditing = false, formItemProps = {}, required = false, name, inTable = false, label, showLabel = false, masterFilter = {}, language = '', objectData = null, parentData = null, objectType = 'unknown', readOnly = false, disabled = false, empty = false, initial = false, height = 'auto', minimal = false, autoCompleteObject = null, errorLines = [], templateType, onCursorChange, previewOpen = false, showPreview = true, useFormItem = true, options = [], roundNumber = false, fixedNumber = false, canAddRemove = true, showHyperlink, showSince, properties = [], onChange = null, maxWidth = '100%', loading = false, rollups = [], showCard = true, showDownload = true, columns = [], style = {}, hiddenPropertyWidth = '100px', inherit = true, scrollHeight, gallery = false, ...rest }) => { if (typeof value === 'function') { value = value(objectData || {}, parentData) } if (max && typeof max == 'function' && objectData) { max = max(objectData, parentData) } if (min && typeof min == 'function' && objectData) { min = min(objectData, parentData) } if (objectType && typeof objectType == 'function' && objectData) { objectType = objectType(objectData, parentData) } if (disabled && typeof disabled == 'function' && objectData) { disabled = disabled(objectData, parentData) } if (empty && typeof empty == 'function' && objectData) { empty = empty(objectData, parentData) } if (difference && typeof difference == 'function' && objectData) { difference = difference(objectData, parentData) } if (prefix && typeof prefix == 'function' && objectData) { prefix = prefix(objectData, parentData) } if (suffix && typeof suffix == 'function' && objectData) { suffix = suffix(objectData, parentData) } if (masterFilter && typeof masterFilter == 'function' && objectData) { masterFilter = masterFilter(objectData, parentData) } if (options && typeof options == 'function' && objectData) { options = options(objectData, parentData) } if (readOnly && typeof readOnly == 'function' && objectData) { readOnly = readOnly(objectData, parentData) } if (type && typeof type == 'function' && objectData) { type = type(objectData, parentData) } if (columns && typeof columns == 'function' && objectData) { columns = columns(objectData, parentData) } if (value == null) { value = getPropertyValue(objectData, name) } // Split the name by "." to handle nested object properties var formItemName = name if (name?.includes('.')) { formItemName = name ? name.split('.') : undefined } // For state type with options (editable), form field is state.type if (type === 'state' && options?.length && !readOnly) { formItemName = ['state', 'type'] } var textParams = { style: { whiteSpace: 'nowrap', minWidth: '0' } } if (disabled == true) { textParams = { ...textParams, delete: true, type: 'secondary' } } const renderProperty = () => { if (empty == true) { return n/a } if (!isEditing || (readOnly && !initial)) { switch (type) { case 'netGross': return ( ) case 'minMax': return ( ) case 'secret': if (value != null) { return } else { return ( n/a ) } case 'wsprotocol': switch (value) { case 'ws': return Websocket case 'wss': return Websocket Secure default: return ( n/a ) } case 'select': { if (options && Array.isArray(options)) { const selectValue = options.find((option) => option.value === value) || 'n/a' return {selectValue.label} } else { return ( n/a ) } } case 'treeSelect': { if (Array.isArray(value) && value.length) { return ( ) } const treeLabel = findTreeLabel(options, value) if (treeLabel || (value != null && value !== '')) { return {treeLabel || String(value)} } return ( n/a ) } case 'priceMode': switch (value) { case 'margin': return Margin % case 'amount': return £ Amount default: return ( n/a ) } case 'bool': { if (value != null) { return } else { return ( n/a ) } } case 'dateTime': { if (value != null) { return ( ) } else { return ( n/a ) } } case 'country': { if (value != null) { return } else { return ( n/a ) } } case 'color': { if (value) { return } else { return ( n/a ) } } case 'number': { if (value != null) { if (Array.isArray(value)) { return ( {prefix} {value.length} {suffix} ) } else { var roundedValue = value if (roundNumber != false && typeof value === 'number') { roundedValue = round(value, roundNumber) } if (fixedNumber != false && typeof value === 'number') { roundedValue = value.toFixed(fixedNumber) } return ( {prefix} {typeof value === 'number' ? roundedValue : value} {suffix} ) } } else { return ( n/a ) } } case 'numberList': case 'stringList': return case 'variance': { if (value != null) { return ( ) } else { return ( n/a ) } } case 'text': if (value != null && value != '') { return ( {prefix} {getTextValue(value)} {suffix} ) } else { return ( n/a ) } case 'codeBlock': if (value != null && value != '') { return ( ) } else { return ( n/a ) } case 'markdown': if (typeof value === 'string' && value !== '') { return } else { return ( n/a ) } case 'email': if (value != null && value != '') { return } else { return ( n/a ) } case 'url': if (value != null && value != '') { return } else { return ( n/a ) } case 'object': { if (value && value._id) { return ( ) } else { return ( n/a ) } } case 'objectType': { if (value) { return } else { return ( n/a ) } } case 'objectList': { return ( ) } case 'objectChildren': { return ( ) } case 'state': { if (value && value?.type) { return } else if (value && value?.message) { return {value.message} } else { return ( n/a ) } } case 'id': { if (value) { return ( ) } else { return ( n/a ) } } case 'reference': { if (value) { return ( ) } else { return ( n/a ) } } case 'miscId': { return } case 'density': { if (value != null) { return {`${value} g/cm³`} } else { return ( n/a ) } } case 'alerts': { if (value != null && value?.length != 0) { return ( ) } else { return ( n/a ) } } case 'tags': { if (value != null || value?.length != 0) { return ( ) } else { return ( n/a ) } } case 'address': { return ( ) } case 'operation': { if (value != null) { return } else { return ( n/a ) } } case 'propertyChanges': { return } case 'data': { return } case 'permissionsMatrix': { return ( ) } case 'file': { if (value == null || value?.length == 0 || value == undefined) { return ( n/a ) } else { return ( ) } } case 'fileList': { return ( ) } default: { if (value != null && value !== '') { return {getTextValue(value)} } else { return ( n/a ) } } } } // Editable mode: wrap in Form.Item // Merge required rule if needed let mergedFormItemProps = { ...formItemProps, style: { flexGrow: 1, width: '100%', minWidth: 0 } } if (required && disabled == false) { let rules if (mergedFormItemProps.rules) { rules = [...mergedFormItemProps.rules] } else { rules = [] } const hasRequiredRule = rules.some((rule) => rule && rule.required) if (!hasRequiredRule) { rules.push({ required: true, message: '' }) } mergedFormItemProps.rules = rules } if (type === 'minMax' && disabled == false) { const rules = [...(mergedFormItemProps.rules || [])] rules.push({ validator: (_, range) => { if (range == null) return Promise.resolve() if (typeof range !== 'object' || Array.isArray(range)) { return Promise.reject(new Error('Value must be a min/max range')) } const values = [range.min, range.max].filter((item) => item != null) const valid = values.every( (item) => typeof item === 'number' && Number.isFinite(item) && (min == null || item >= min) && (max == null || item <= max) ) return valid ? Promise.resolve() : Promise.reject(new Error('Enter valid min and max values')) } }) mergedFormItemProps.rules = rules } if ((type === 'numberList' || type === 'stringList') && disabled == false) { const rules = [...(mergedFormItemProps.rules || [])] rules.push({ validator: (_, items) => { if (items == null) return Promise.resolve() if (!Array.isArray(items)) { return Promise.reject(new Error('Value must be a list')) } const valid = items.every((item) => { if (type === 'numberList') { return ( typeof item === 'number' && Number.isFinite(item) && (min == null || item >= min) && (max == null || item <= max) ) } return typeof item === 'string' && item.trim().length > 0 }) return valid ? Promise.resolve() : Promise.reject(new Error('Complete or remove empty list items')) } }) mergedFormItemProps.rules = rules } // Remove name from mergedFormItemProps if present if (mergedFormItemProps.name) { delete mergedFormItemProps.name } // If label is provided, set it on Form.Item if (label && showLabel == true) { mergedFormItemProps.label = label } // Always apply style: { margin: 0 } unless overridden mergedFormItemProps.style = { margin: 0, ...(mergedFormItemProps.style || {}) } if (typeof onChange === 'function') { mergedFormItemProps.onChange = onChange } const inputProps = useFormItem ? { disabled } : { value, onChange, disabled } const renderInput = () => { switch (type) { case 'netGross': return ( ) case 'minMax': return ( ) case 'secret': return ( visible ? : } {...mergedFormItemProps} {...inputProps} /> ) case 'wsprotocol': return ( ) case 'bool': return ( ) case 'dateTime': return case 'country': return case 'color': return ( ) case 'weight': return ( ) case 'number': return ( ) case 'numberList': return ( ) case 'stringList': return ( ) case 'text': return case 'email': if (Array.isArray(options) && options.length) { return ( ) } return case 'codeBlock': return ( ) case 'markdown': return ( ) case 'id': // id is not editable, just show view mode if (value) { return } else { return ( n/a ) } case 'object': return ( ) case 'objectType': return ( ) case 'objectList': return ( ) case 'tags': return ( ) case 'address': return ( ) case 'file': return ( ) case 'fileList': return ( ) case 'objectChildren': { return ( ) } case 'permissionsMatrix': return ( ) default: return } } if (!useFormItem) { return renderInput() } return ( v } : {})} {...(type === 'dateTime' ? timeEditFormItemProps : {})} > {renderInput()} ) } const property = renderProperty() // Render the property directly (remove useDescriptions functionality) return property } ObjectProperty.propTypes = { type: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired, modelType: PropTypes.string, value: PropTypes.oneOfType([PropTypes.any, PropTypes.func]), isEditing: PropTypes.bool, formItemProps: PropTypes.object, masterFilter: PropTypes.object, required: PropTypes.bool, name: PropTypes.string, language: PropTypes.string, autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), errorLines: PropTypes.arrayOf(PropTypes.number), templateType: PropTypes.string, onCursorChange: PropTypes.func, prefix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), min: PropTypes.number, max: PropTypes.number, step: PropTypes.number, showLabel: PropTypes.bool, objectType: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), readOnly: PropTypes.bool, disabled: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), empty: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), difference: PropTypes.oneOfType([PropTypes.any, PropTypes.func]), objectData: PropTypes.object, height: PropTypes.string, previewOpen: PropTypes.bool, showPreview: PropTypes.bool, useFormItem: PropTypes.bool, showHyperlink: PropTypes.bool, options: PropTypes.oneOfType([PropTypes.array, PropTypes.func]), showSince: PropTypes.bool, inTable: PropTypes.bool, loading: PropTypes.bool, rollups: PropTypes.arrayOf(PropTypes.object), canAddRemove: PropTypes.bool, showCard: PropTypes.bool, showDownload: PropTypes.bool, style: PropTypes.object, hiddenPropertyWidth: PropTypes.string, inherit: PropTypes.bool, scrollHeight: PropTypes.string, gallery: PropTypes.bool } export default ObjectProperty