2025-08-22 20:28:50 +01:00

694 lines
19 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 ObjectSelect from './ObjectSelect'
import ObjectDisplay from './ObjectDisplay'
import ObjectTypeSelect from './ObjectTypeSelect'
import ObjectTypeDisplay from './ObjectTypeDisplay'
import CodeBlockEditor from './CodeBlockEditor'
import StateDisplay from './StateDisplay'
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,
...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 (!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 = {}
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.name) {
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} {...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 'mm': {
if (value != null) {
return <Text {...textParams}>{`${value} mm`}</Text>
} 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} />
}
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) {
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 '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 '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>
)
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
}
export default ObjectProperty