793 lines
22 KiB
JavaScript
793 lines
22 KiB
JavaScript
import PropTypes from 'prop-types'
|
|
import {
|
|
Typography,
|
|
Badge,
|
|
Input,
|
|
InputNumber,
|
|
Form,
|
|
Select,
|
|
DatePicker,
|
|
Switch
|
|
} from 'antd'
|
|
import IdDisplay from './IdDisplay'
|
|
import TimeDisplay from './TimeDisplay'
|
|
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 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 StateDisplay from './StateDisplay'
|
|
import AlertsDisplay from './AlertsDisplay'
|
|
import FileUpload from './FileUpload'
|
|
import DataTree from './DataTree'
|
|
import FileList from './FileList'
|
|
|
|
const { Text } = Typography
|
|
|
|
const MATERIAL_OPTIONS = [
|
|
{ value: 'PLA', label: 'PLA' },
|
|
{ value: 'PETG', label: 'PETG' },
|
|
{ value: 'ABS', label: 'ABS' },
|
|
{ value: 'ASA', label: 'ASA' },
|
|
{ value: 'HIPS', label: 'HIPS' },
|
|
{ value: 'TPU', label: 'TPU' }
|
|
]
|
|
|
|
const ObjectProperty = ({
|
|
type = 'text',
|
|
prefix,
|
|
suffix,
|
|
value,
|
|
min,
|
|
max,
|
|
difference,
|
|
step,
|
|
isEditing = false,
|
|
formItemProps = {},
|
|
required = false,
|
|
name,
|
|
label,
|
|
showLabel = false,
|
|
masterFilter = {},
|
|
language = '',
|
|
objectData = null,
|
|
objectType = 'unknown',
|
|
readOnly = false,
|
|
disabled = false,
|
|
empty = false,
|
|
initial = false,
|
|
height = 'auto',
|
|
minimal = false,
|
|
previewOpen = false,
|
|
showPreview = true,
|
|
options = [],
|
|
showHyperlink,
|
|
...rest
|
|
}) => {
|
|
if (value && typeof value == 'function' && objectData) {
|
|
value = value(objectData)
|
|
}
|
|
|
|
if (objectType && typeof objectType == 'function' && objectData) {
|
|
objectType = objectType(objectData)
|
|
}
|
|
|
|
if (disabled && typeof disabled == 'function' && objectData) {
|
|
disabled = disabled(objectData)
|
|
}
|
|
|
|
if (empty && typeof empty == 'function' && objectData) {
|
|
empty = empty(objectData)
|
|
}
|
|
|
|
if (difference && typeof difference == 'function' && objectData) {
|
|
difference = difference(objectData)
|
|
}
|
|
|
|
if (prefix && typeof prefix == 'function' && objectData) {
|
|
prefix = prefix(objectData)
|
|
}
|
|
|
|
if (suffix && typeof suffix == 'function' && objectData) {
|
|
suffix = suffix(objectData)
|
|
}
|
|
|
|
if (masterFilter && typeof masterFilter == 'function' && objectData) {
|
|
masterFilter = masterFilter(objectData)
|
|
}
|
|
|
|
if (!value) {
|
|
value = getPropertyValue(objectData, name)
|
|
}
|
|
|
|
// Split the name by "." to handle nested object properties
|
|
var formItemName = name
|
|
|
|
if (name?.includes('.')) {
|
|
formItemName = name ? name.split('.') : undefined
|
|
}
|
|
|
|
var textParams = { style: { whiteSpace: 'nowrap', minWidth: '0' } }
|
|
|
|
if (disabled == true) {
|
|
textParams = { ...textParams, delete: true, type: 'secondary' }
|
|
}
|
|
|
|
const renderProperty = () => {
|
|
if (empty == true) {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
if (!isEditing || (readOnly && !initial)) {
|
|
switch (type) {
|
|
case 'netGross':
|
|
return (
|
|
<NetGrossDisplay value={value} prefix={prefix} suffix={suffix} />
|
|
)
|
|
case 'secret':
|
|
if (value != null) {
|
|
return <SecretDisplay value={value} {...rest} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
case 'wsprotocol':
|
|
switch (value) {
|
|
case 'ws':
|
|
return <Text {...textParams}>Websocket</Text>
|
|
case 'wss':
|
|
return <Text {...textParams}>Websocket Secure</Text>
|
|
default:
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
case 'priceMode':
|
|
switch (value) {
|
|
case 'margin':
|
|
return <Text {...textParams}>Margin %</Text>
|
|
case 'amount':
|
|
return <Text {...textParams}>£ Amount</Text>
|
|
default:
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
case 'bool': {
|
|
if (value != null) {
|
|
return <BoolDisplay value={value} yesNo={true} {...rest} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'dateTime': {
|
|
if (value != null) {
|
|
return <TimeDisplay dateTime={value} {...rest} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'country': {
|
|
if (value != null) {
|
|
return <CountryDisplay countryCode={value} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'color': {
|
|
if (value) {
|
|
return <Badge color={value} text={value} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'number': {
|
|
if (value != null) {
|
|
if (Array.isArray(value)) {
|
|
return (
|
|
<Text {...textParams}>
|
|
{prefix}
|
|
{value.length}
|
|
{suffix}
|
|
</Text>
|
|
)
|
|
} else {
|
|
return (
|
|
<Text {...textParams}>
|
|
{prefix}
|
|
{typeof value === 'number' ? value.toFixed(2) : value}
|
|
{suffix}
|
|
</Text>
|
|
)
|
|
}
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'variance': {
|
|
if (value != null) {
|
|
return (
|
|
<VarianceDisplay value={value} prefix={prefix} suffix={suffix} />
|
|
)
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'text':
|
|
if (value != null && value != '') {
|
|
return (
|
|
<Text ellipsis>
|
|
{prefix}
|
|
{value}
|
|
{suffix}
|
|
</Text>
|
|
)
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
case 'codeBlock':
|
|
if (value != null && value != '') {
|
|
return (
|
|
<CodeBlockEditor
|
|
code={value}
|
|
language={language}
|
|
height={height}
|
|
readOnly={true}
|
|
minimal={minimal}
|
|
/>
|
|
)
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
case 'markdown':
|
|
if (value != null && value != '') {
|
|
return <MarkdownDisplay content={value} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
case 'email':
|
|
if (value != null && value != '') {
|
|
return <EmailDisplay email={value} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
case 'url':
|
|
if (value != null && value != '') {
|
|
return <UrlDisplay url={value} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
case 'object': {
|
|
if (value && value._id) {
|
|
return <ObjectDisplay object={value} objectType={objectType} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'objectType': {
|
|
if (value) {
|
|
return <ObjectTypeDisplay objectType={value} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'objectList': {
|
|
return <ObjectList value={value} objectType={objectType} />
|
|
}
|
|
case 'state': {
|
|
if (value && value?.type) {
|
|
return <StateDisplay {...rest} state={value} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'material': {
|
|
if (value) {
|
|
return <Text {...textParams}>{value}</Text>
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'id': {
|
|
if (value) {
|
|
return (
|
|
<IdDisplay
|
|
id={value}
|
|
type={objectType}
|
|
showHyperlink={showHyperlink}
|
|
{...rest}
|
|
/>
|
|
)
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'density': {
|
|
if (value != null) {
|
|
return <Text {...textParams}>{`${value} g/cm³`}</Text>
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'alerts': {
|
|
if (value != null && value?.length != 0) {
|
|
return <AlertsDisplay alerts={value} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'tags': {
|
|
if (value != null || value?.length != 0) {
|
|
return <TagsDisplay tags={value} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'operation': {
|
|
if (value != null) {
|
|
return <OperationDisplay operation={value} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
case 'propertyChanges': {
|
|
return <PropertyChanges type={objectType} value={value} />
|
|
}
|
|
case 'data': {
|
|
return <DataTree data={value} />
|
|
}
|
|
case 'file': {
|
|
if (value == null || value?.length == 0 || value == undefined) {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
} else {
|
|
return (
|
|
<FileList
|
|
files={value}
|
|
multiple={false}
|
|
card={false}
|
|
defaultPreviewOpen={previewOpen}
|
|
showPreview={showPreview}
|
|
showInfo={showHyperlink}
|
|
/>
|
|
)
|
|
}
|
|
}
|
|
case 'fileList': {
|
|
return (
|
|
<FileList
|
|
files={value}
|
|
multiple={true}
|
|
defaultPreviewOpen={previewOpen}
|
|
showPreview={showPreview}
|
|
showInfo={showHyperlink}
|
|
/>
|
|
)
|
|
}
|
|
default: {
|
|
if (value) {
|
|
return <Text {...textParams}>{value}</Text>
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Editable mode: wrap in Form.Item
|
|
// Merge required rule if needed
|
|
let mergedFormItemProps = { ...formItemProps, style: { flexGrow: 1 } }
|
|
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
|
|
}
|
|
// 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 || {})
|
|
}
|
|
switch (type) {
|
|
case 'netGross':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<NetGrossInput
|
|
difference={difference}
|
|
prefix={prefix}
|
|
suffix={suffix}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'secret':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<Input.Password
|
|
placeholder={label}
|
|
disabled={disabled}
|
|
{...mergedFormItemProps}
|
|
iconRender={(visible) =>
|
|
visible ? <EyeSlashIcon /> : <EyeIcon />
|
|
}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'wsprotocol':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<Select
|
|
defaultValue='ws'
|
|
disabled={disabled}
|
|
options={[
|
|
{ value: 'ws', label: 'Websocket' },
|
|
{ value: 'wss', label: 'Websocket Secure' }
|
|
]}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'select':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<Select
|
|
defaultValue={value}
|
|
placeholder={'Select a ' + label.toLowerCase() + '...'}
|
|
disabled={disabled}
|
|
options={options}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'priceMode':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<Select
|
|
defaultValue='margin'
|
|
disabled={disabled}
|
|
options={[
|
|
{ value: 'margin', label: 'Margin %' },
|
|
{ value: 'amount', label: '£ Amount' }
|
|
]}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'bool':
|
|
return (
|
|
<Form.Item
|
|
name={formItemName}
|
|
{...mergedFormItemProps}
|
|
valuePropName='checked'
|
|
>
|
|
<Switch disabled={disabled} />
|
|
</Form.Item>
|
|
)
|
|
case 'dateTime':
|
|
return (
|
|
<Form.Item
|
|
name={formItemName}
|
|
{...mergedFormItemProps}
|
|
getValueProps={(v) => ({ value: v ? dayjs(v) : null })}
|
|
>
|
|
<DatePicker
|
|
showTime
|
|
style={{ width: '100%' }}
|
|
disabled={disabled}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'country':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<CountrySelect disabled={disabled} />
|
|
</Form.Item>
|
|
)
|
|
case 'color':
|
|
return (
|
|
<Form.Item
|
|
name={formItemName}
|
|
{...mergedFormItemProps}
|
|
valuePropName='value'
|
|
getValueFromEvent={(v) => v}
|
|
>
|
|
<ColorSelector required={required} disabled={disabled} />
|
|
</Form.Item>
|
|
)
|
|
case 'weight':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<InputNumber
|
|
suffix='g'
|
|
style={{ width: '100%' }}
|
|
placeholder={label}
|
|
disabled={disabled}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'number':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<InputNumber
|
|
placeholder={label}
|
|
disabled={disabled}
|
|
prefix={prefix}
|
|
suffix={suffix}
|
|
min={min}
|
|
max={max}
|
|
step={step}
|
|
{...mergedFormItemProps}
|
|
style={{ width: '100%' }}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'text':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<Input
|
|
placeholder={label}
|
|
{...mergedFormItemProps}
|
|
disabled={disabled}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'codeBlock':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<CodeBlockEditor
|
|
code={value}
|
|
language={language}
|
|
disabled={disabled}
|
|
height={height}
|
|
minimal={minimal}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'markdown':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<MarkdownInput value={value} />
|
|
</Form.Item>
|
|
)
|
|
case 'material':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<Select
|
|
options={MATERIAL_OPTIONS}
|
|
placeholder={label}
|
|
disabled={disabled}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'id':
|
|
// id is not editable, just show view mode
|
|
if (value) {
|
|
return <IdDisplay id={value} type={objectType} {...rest} />
|
|
} else {
|
|
return (
|
|
<Text type='secondary' {...textParams}>
|
|
n/a
|
|
</Text>
|
|
)
|
|
}
|
|
case 'object':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<ObjectSelect
|
|
type={objectType}
|
|
disabled={disabled}
|
|
masterFilter={masterFilter}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'objectType':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<ObjectTypeSelect disabled={disabled} />
|
|
</Form.Item>
|
|
)
|
|
case 'objectList':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<ObjectSelect type={objectType} multiple disabled={disabled} />
|
|
</Form.Item>
|
|
)
|
|
case 'tags':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<TagsInput />
|
|
</Form.Item>
|
|
)
|
|
case 'file':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<FileUpload
|
|
value={value}
|
|
multiple={false}
|
|
defaultPreviewOpen={previewOpen}
|
|
showPreview={showPreview}
|
|
showInfo={showHyperlink}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'fileList':
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<FileUpload
|
|
value={value}
|
|
multiple={true}
|
|
defaultPreviewOpen={previewOpen}
|
|
showPreview={showPreview}
|
|
showInfo={showHyperlink}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
default:
|
|
return (
|
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
|
<Input placeholder={label} {...mergedFormItemProps} />
|
|
</Form.Item>
|
|
)
|
|
}
|
|
}
|
|
|
|
const property = renderProperty()
|
|
// Render the property directly (remove useDescriptions functionality)
|
|
return property
|
|
}
|
|
|
|
ObjectProperty.propTypes = {
|
|
type: PropTypes.string.isRequired,
|
|
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,
|
|
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,
|
|
showHyperlink: PropTypes.bool,
|
|
options: PropTypes.array
|
|
}
|
|
|
|
export default ObjectProperty
|