Enhance styling and functionality of dashboard components
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Added new CSS variables for padding in ScrollBox to improve layout. - Updated ObjectForm to include console logs for debugging during editing cancellation. - Refactored ObjectInfo to use mergeWith for better array handling in state updates. - Enhanced ObjectProperty and ObjectTable components to support new props for better integration with tags. - Improved TagsDisplay and TagsInput components for better tag management and display, including loading states and dynamic options fetching. - Introduced getModelPropertyValues function in ApiServerContext for fetching model-specific property values, enhancing data handling capabilities.
This commit is contained in:
parent
a6fce60d04
commit
b16cdd6e96
@ -139,6 +139,8 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--unit-100vh: 100vh;
|
--unit-100vh: 100vh;
|
||||||
|
--scrollbox-vertical-right-padding: 16px;
|
||||||
|
--scrollbox-horizontal-bottom-padding: 16px;
|
||||||
}
|
}
|
||||||
@supports (height: 100dvh) {
|
@supports (height: 100dvh) {
|
||||||
:root {
|
:root {
|
||||||
@ -352,15 +354,36 @@ body {
|
|||||||
margin-left: 1px !important;
|
margin-left: 1px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tags-input.ant-select-multiple .ant-select-selection-item,
|
||||||
|
.tags-input.ant-tree-select.ant-select-multiple .ant-select-selection-item {
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin-inline-end: 0 !important;
|
||||||
|
height: auto !important;
|
||||||
|
line-height: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-input.ant-select-multiple .ant-select-selection-item-remove,
|
||||||
|
.tags-input.ant-tree-select.ant-select-multiple
|
||||||
|
.ant-select-selection-item-remove {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-badge.ant-badge-status {
|
.ant-badge.ant-badge-status {
|
||||||
line-height: 18.5px;
|
line-height: 18.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.simplebar-track.simplebar-vertical {
|
.simplebar-track.simplebar-vertical {
|
||||||
right: -16px;
|
right: calc(-1 * var(--scrollbox-vertical-right-padding));
|
||||||
width: 8px !important;
|
width: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.simplebar-track.simplebar-horizontal {
|
||||||
|
bottom: calc(-1 * var(--scrollbox-horizontal-bottom-padding));
|
||||||
|
height: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.simplebar-scrollbar:before {
|
.simplebar-scrollbar:before {
|
||||||
background: #78787854 !important;
|
background: #78787854 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -402,6 +402,8 @@ const ObjectForm = forwardRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelEditing = () => {
|
const cancelEditing = () => {
|
||||||
|
console.log('cancelEditing')
|
||||||
|
|
||||||
if (serverObjectData.current) {
|
if (serverObjectData.current) {
|
||||||
// Recalculate computed values when canceling
|
// Recalculate computed values when canceling
|
||||||
const computedEntries = calculateComputedValues(
|
const computedEntries = calculateComputedValues(
|
||||||
@ -417,6 +419,7 @@ const ObjectForm = forwardRef(
|
|||||||
setIsEditing(false)
|
setIsEditing(false)
|
||||||
isEditingRef.current = false
|
isEditingRef.current = false
|
||||||
form.setFieldsValue(resetFormData)
|
form.setFieldsValue(resetFormData)
|
||||||
|
console.log('resetFormData', resetFormData)
|
||||||
setObjectData({ ...resetFormData, _isEditing: isEditingRef.current })
|
setObjectData({ ...resetFormData, _isEditing: isEditingRef.current })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,13 @@ import { LoadingOutlined } from '@ant-design/icons'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ObjectProperty from './ObjectProperty'
|
import ObjectProperty from './ObjectProperty'
|
||||||
import { getModelProperties } from '../../../database/ObjectModels'
|
import { getModelProperties } from '../../../database/ObjectModels'
|
||||||
import merge from 'lodash/merge'
|
import mergeWith from 'lodash/mergeWith'
|
||||||
|
|
||||||
|
const arrayReplaceCustomizer = (objValue, srcValue) => {
|
||||||
|
if (Array.isArray(srcValue)) {
|
||||||
|
return srcValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ObjectInfo = ({
|
const ObjectInfo = ({
|
||||||
loading = false,
|
loading = false,
|
||||||
@ -36,7 +42,9 @@ const ObjectInfo = ({
|
|||||||
const [combinedObjectData, setCombinedObjectData] = useState(objectData)
|
const [combinedObjectData, setCombinedObjectData] = useState(objectData)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCombinedObjectData((prev) => merge({}, prev, objectData))
|
setCombinedObjectData((prev) =>
|
||||||
|
mergeWith({}, prev, objectData, arrayReplaceCustomizer)
|
||||||
|
)
|
||||||
}, [objectData])
|
}, [objectData])
|
||||||
|
|
||||||
// If properties array is empty, show all properties
|
// If properties array is empty, show all properties
|
||||||
@ -108,9 +116,8 @@ const ObjectInfo = ({
|
|||||||
parentData={parentData}
|
parentData={parentData}
|
||||||
showSince={true}
|
showSince={true}
|
||||||
useFormItem={isControlled ? false : objectPropertyProps.useFormItem}
|
useFormItem={isControlled ? false : objectPropertyProps.useFormItem}
|
||||||
value={
|
value={isControlled ? combinedObjectData?.[item.name] : undefined}
|
||||||
isControlled ? combinedObjectData?.[item.name] : undefined
|
modelType={type}
|
||||||
}
|
|
||||||
onChange={
|
onChange={
|
||||||
isControlled
|
isControlled
|
||||||
? (newVal) => onPropertyChange(item.name, newVal)
|
? (newVal) => onPropertyChange(item.name, newVal)
|
||||||
|
|||||||
@ -83,6 +83,7 @@ const timeEditFormItemProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ObjectProperty = ({
|
const ObjectProperty = ({
|
||||||
|
modelType = 'unknown',
|
||||||
type = 'text',
|
type = 'text',
|
||||||
prefix,
|
prefix,
|
||||||
size,
|
size,
|
||||||
@ -96,6 +97,7 @@ const ObjectProperty = ({
|
|||||||
formItemProps = {},
|
formItemProps = {},
|
||||||
required = false,
|
required = false,
|
||||||
name,
|
name,
|
||||||
|
inTable = false,
|
||||||
label,
|
label,
|
||||||
showLabel = false,
|
showLabel = false,
|
||||||
masterFilter = {},
|
masterFilter = {},
|
||||||
@ -567,7 +569,14 @@ const ObjectProperty = ({
|
|||||||
}
|
}
|
||||||
case 'tags': {
|
case 'tags': {
|
||||||
if (value != null || value?.length != 0) {
|
if (value != null || value?.length != 0) {
|
||||||
return <TagsDisplay tags={value} />
|
return (
|
||||||
|
<TagsDisplay
|
||||||
|
tags={value}
|
||||||
|
propertyName={name}
|
||||||
|
scrollHorizontal={inTable}
|
||||||
|
objectType={objectType}
|
||||||
|
/>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Text type='secondary' {...textParams}>
|
<Text type='secondary' {...textParams}>
|
||||||
@ -930,7 +939,15 @@ const ObjectProperty = ({
|
|||||||
case 'objectList':
|
case 'objectList':
|
||||||
return <ObjectSelect type={objectType} multiple {...inputProps} />
|
return <ObjectSelect type={objectType} multiple {...inputProps} />
|
||||||
case 'tags':
|
case 'tags':
|
||||||
return <TagsInput {...inputProps} />
|
return (
|
||||||
|
<TagsInput
|
||||||
|
propertyName={name}
|
||||||
|
modelType={modelType}
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={label}
|
||||||
|
{...inputProps}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'address':
|
case 'address':
|
||||||
return (
|
return (
|
||||||
<AddressDisplay
|
<AddressDisplay
|
||||||
@ -1012,6 +1029,7 @@ const ObjectProperty = ({
|
|||||||
|
|
||||||
ObjectProperty.propTypes = {
|
ObjectProperty.propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
|
modelType: PropTypes.string,
|
||||||
value: PropTypes.oneOfType([PropTypes.any, PropTypes.func]),
|
value: PropTypes.oneOfType([PropTypes.any, PropTypes.func]),
|
||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
formItemProps: PropTypes.object,
|
formItemProps: PropTypes.object,
|
||||||
@ -1038,6 +1056,7 @@ ObjectProperty.propTypes = {
|
|||||||
showHyperlink: PropTypes.bool,
|
showHyperlink: PropTypes.bool,
|
||||||
options: PropTypes.array,
|
options: PropTypes.array,
|
||||||
showSince: PropTypes.bool,
|
showSince: PropTypes.bool,
|
||||||
|
inTable: PropTypes.bool,
|
||||||
loading: PropTypes.bool,
|
loading: PropTypes.bool,
|
||||||
rollups: PropTypes.arrayOf(PropTypes.object),
|
rollups: PropTypes.arrayOf(PropTypes.object),
|
||||||
canAddRemove: PropTypes.bool,
|
canAddRemove: PropTypes.bool,
|
||||||
|
|||||||
@ -977,6 +977,7 @@ const ObjectTable = forwardRef(
|
|||||||
<ObjectProperty
|
<ObjectProperty
|
||||||
{...prop}
|
{...prop}
|
||||||
longId={false}
|
longId={false}
|
||||||
|
inTable={true}
|
||||||
objectData={record}
|
objectData={record}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,9 +2,22 @@ import PropTypes from 'prop-types'
|
|||||||
import SimpleBar from 'simplebar-react'
|
import SimpleBar from 'simplebar-react'
|
||||||
import 'simplebar-react/dist/simplebar.min.css'
|
import 'simplebar-react/dist/simplebar.min.css'
|
||||||
|
|
||||||
const ScrollBox = ({ children, style, ...rest }) => {
|
const ScrollBox = ({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
horizontalBottomPadding = 16,
|
||||||
|
verticalRightPadding = 16,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', minHeight: '0' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
minHeight: '0',
|
||||||
|
'--scrollbox-vertical-right-padding': `${verticalRightPadding}px`,
|
||||||
|
'--scrollbox-horizontal-bottom-padding': `${horizontalBottomPadding}px`
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SimpleBar style={{ height: '100%', ...style }} {...rest}>
|
<SimpleBar style={{ height: '100%', ...style }} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
</SimpleBar>
|
</SimpleBar>
|
||||||
@ -14,7 +27,9 @@ const ScrollBox = ({ children, style, ...rest }) => {
|
|||||||
|
|
||||||
ScrollBox.propTypes = {
|
ScrollBox.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
style: PropTypes.object
|
style: PropTypes.object,
|
||||||
|
horizontalBottomPadding: PropTypes.number,
|
||||||
|
verticalRightPadding: PropTypes.number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ScrollBox
|
export default ScrollBox
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
import { Tag, Space, Typography } from 'antd'
|
import { Tag, Flex, Typography } from 'antd'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import ScrollBox from './ScrollBox'
|
||||||
|
|
||||||
const TagsDisplay = ({ tags, style }) => {
|
const TagsDisplay = ({ tags, style, scrollHorizontal = false }) => {
|
||||||
let tagArray = []
|
const [tagArray, setTagArray] = useState([])
|
||||||
if (typeof tags === 'string') {
|
|
||||||
tagArray = [tags]
|
useEffect(() => {
|
||||||
} else if (Array.isArray(tags)) {
|
if (typeof tags === 'string') {
|
||||||
tagArray = tags
|
setTagArray([tags])
|
||||||
}
|
} else if (Array.isArray(tags)) {
|
||||||
|
setTagArray(tags)
|
||||||
|
}
|
||||||
|
}, [tags])
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!tagArray ||
|
!tagArray ||
|
||||||
@ -19,20 +23,27 @@ const TagsDisplay = ({ tags, style }) => {
|
|||||||
return <Text type='secondary'>n/a</Text>
|
return <Text type='secondary'>n/a</Text>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const tagContents = (
|
||||||
<Space size={'small'} wrap style={style}>
|
<Flex gap={'4px'} wrap={!scrollHorizontal} style={style} justify={'start'}>
|
||||||
{tagArray.map((tag, index) => (
|
{tagArray.map((tag, index) => (
|
||||||
<Tag key={index} color='blue' style={{ margin: 0 }}>
|
<Tag key={index} color='blue' style={{ margin: 0 }}>
|
||||||
{tag}
|
{tag}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Flex>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (scrollHorizontal) {
|
||||||
|
return <ScrollBox horizontalBottomPadding={10}>{tagContents}</ScrollBox>
|
||||||
|
} else {
|
||||||
|
return tagContents
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TagsDisplay.propTypes = {
|
TagsDisplay.propTypes = {
|
||||||
tags: PropTypes.arrayOf(PropTypes.string),
|
tags: PropTypes.arrayOf(PropTypes.string),
|
||||||
style: PropTypes.object
|
style: PropTypes.object,
|
||||||
|
scrollHorizontal: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TagsDisplay
|
export default TagsDisplay
|
||||||
|
|||||||
@ -1,56 +1,179 @@
|
|||||||
import { useState } from 'react'
|
import { useState, useEffect, useContext, useMemo } from 'react'
|
||||||
import { Space, Tag, Input, Button } from 'antd'
|
import { Space, Button, TreeSelect, Tag } from 'antd'
|
||||||
import PlusIcon from '../../Icons/PlusIcon'
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
|
import TagsDisplay from './TagsDisplay'
|
||||||
|
|
||||||
const TagsInput = ({ value = [], onChange }) => {
|
const { SHOW_CHILD } = TreeSelect
|
||||||
const [inputValue, setInputValue] = useState('')
|
|
||||||
|
|
||||||
const handleTagClose = (removedTag) => {
|
const TagsInput = ({
|
||||||
const newTags = value.filter((tag) => tag !== removedTag)
|
value = [],
|
||||||
onChange && onChange(newTags)
|
onChange,
|
||||||
}
|
propertyName,
|
||||||
|
modelType,
|
||||||
|
placeholder = 'Select or create tags',
|
||||||
|
disabled = false
|
||||||
|
}) => {
|
||||||
|
const { getModelPropertyValues } = useContext(ApiServerContext)
|
||||||
|
const [options, setOptions] = useState([])
|
||||||
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [delayedLoading, setDelayedLoading] = useState(true)
|
||||||
|
|
||||||
const handleTagAdd = () => {
|
const tags = Array.isArray(value) ? value : []
|
||||||
const newTag = inputValue.trim()
|
|
||||||
if (newTag && !value.includes(newTag)) {
|
useEffect(() => {
|
||||||
const newTags = [...value, newTag]
|
let cancelled = false
|
||||||
onChange && onChange(newTags)
|
|
||||||
setInputValue('')
|
const loadOptions = async () => {
|
||||||
|
if (!modelType || !propertyName) return
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const values = await getModelPropertyValues(modelType, propertyName)
|
||||||
|
if (cancelled) return
|
||||||
|
const unique = [
|
||||||
|
...new Set(
|
||||||
|
(values || []).filter(
|
||||||
|
(v) => v != null && v !== '' && typeof v === 'string'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
setOptions(unique.map((tag) => ({ label: tag, value: tag })))
|
||||||
|
} finally {
|
||||||
|
if (!cancelled) setLoading(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadOptions()
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [modelType, propertyName, getModelPropertyValues])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
const timer = setTimeout(() => setDelayedLoading(false), 100)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}
|
||||||
|
setDelayedLoading(true)
|
||||||
|
}, [loading])
|
||||||
|
|
||||||
|
const mergedOptions = useMemo(() => {
|
||||||
|
const map = new Map(options.map((option) => [option.value, option]))
|
||||||
|
tags.forEach((tag) => {
|
||||||
|
if (tag != null && tag !== '' && !map.has(tag)) {
|
||||||
|
map.set(tag, { label: tag, value: tag })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Array.from(map.values())
|
||||||
|
}, [options, tags])
|
||||||
|
|
||||||
|
const treeData = useMemo(
|
||||||
|
() =>
|
||||||
|
mergedOptions.map((option) => ({
|
||||||
|
title: (
|
||||||
|
<div style={{ paddingTop: 1 }}>
|
||||||
|
<TagsDisplay tags={[option.label]} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
value: option.value,
|
||||||
|
key: option.value,
|
||||||
|
isLeaf: true
|
||||||
|
})),
|
||||||
|
[mergedOptions]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleChange = (nextTags) => {
|
||||||
|
onChange?.(nextTags)
|
||||||
|
setSearchValue('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
const newTag = searchValue.trim()
|
||||||
|
if (!newTag || tags.includes(newTag)) return
|
||||||
|
onChange?.([...tags, newTag])
|
||||||
|
setSearchValue('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onInputKeyDown = (e) => {
|
||||||
|
if (e.key !== 'Enter' || e.defaultPrevented) return
|
||||||
|
const newTag = searchValue.trim()
|
||||||
|
if (!newTag || tags.includes(newTag)) return
|
||||||
|
const existsInOptions = mergedOptions.some(
|
||||||
|
(option) => option.value === newTag
|
||||||
|
)
|
||||||
|
if (existsInOptions) return
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
onChange?.([...tags, newTag])
|
||||||
|
setSearchValue('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagRender = ({ label, value: tagValue, closable, onClose }) => (
|
||||||
|
<Tag
|
||||||
|
color='blue'
|
||||||
|
closable={closable}
|
||||||
|
onClose={onClose}
|
||||||
|
style={{ marginInlineEnd: 4 }}
|
||||||
|
>
|
||||||
|
{typeof label === 'string' ? label : tagValue}
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Space.Compact block>
|
||||||
<Space size={'small'} wrap style={{ marginBottom: 4, maxWidth: '300px' }}>
|
<div style={{ position: 'relative', flex: 1, width: '100%' }}>
|
||||||
{value.map((tag) => (
|
<TreeSelect
|
||||||
<Tag
|
multiple
|
||||||
key={tag}
|
treeDataSimpleMode={false}
|
||||||
color='blue'
|
treeDefaultExpandAll
|
||||||
closable
|
showCheckedStrategy={SHOW_CHILD}
|
||||||
onClose={() => handleTagClose(tag)}
|
className='object-select-multiple tags-input'
|
||||||
style={{ marginBottom: 12, marginRight: 0 }}
|
style={{ width: '100%', opacity: delayedLoading ? 0 : 1 }}
|
||||||
>
|
placeholder={placeholder}
|
||||||
{tag}
|
value={tags}
|
||||||
</Tag>
|
onChange={handleChange}
|
||||||
))}
|
treeData={treeData}
|
||||||
</Space>
|
showSearch
|
||||||
<Space.Compact block>
|
treeNodeFilterProp='value'
|
||||||
<Input
|
searchValue={searchValue}
|
||||||
placeholder='Add new tag'
|
onSearch={setSearchValue}
|
||||||
value={inputValue}
|
onInputKeyDown={onInputKeyDown}
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
tagRender={tagRender}
|
||||||
onPressEnter={handleTagAdd}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleTagAdd} icon={<PlusIcon />} />
|
{delayedLoading && (
|
||||||
</Space.Compact>
|
<TreeSelect
|
||||||
</>
|
disabled
|
||||||
|
loading
|
||||||
|
placeholder='Loading...'
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={handleAdd}
|
||||||
|
icon={<PlusIcon />}
|
||||||
|
disabled={disabled || !searchValue.trim()}
|
||||||
|
/>
|
||||||
|
</Space.Compact>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
TagsInput.propTypes = {
|
TagsInput.propTypes = {
|
||||||
value: PropTypes.arrayOf(PropTypes.string),
|
value: PropTypes.arrayOf(PropTypes.string),
|
||||||
onChange: PropTypes.func
|
onChange: PropTypes.func,
|
||||||
|
propertyName: PropTypes.string,
|
||||||
|
modelType: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
placeholder: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TagsInput
|
export default TagsInput
|
||||||
|
|||||||
@ -1403,6 +1403,30 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getModelPropertyValues = async (objectType, property) => {
|
||||||
|
logger.debug('Fetching property values for model type:', objectType, property)
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${config.backendUrl}/${getObjectEndpoint(objectType)}/values`,
|
||||||
|
{
|
||||||
|
params: { property },
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
logger.debug('Fetched property values for model type:', objectType, property)
|
||||||
|
return Array.isArray(response.data) ? response.data : []
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
showError(err, () => {
|
||||||
|
getModelPropertyValues(objectType, property)
|
||||||
|
})
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getModelHistory = async (objectType, startDate, endDate) => {
|
const getModelHistory = async (objectType, startDate, endDate) => {
|
||||||
logger.debug('Fetching history for model type:', objectType)
|
logger.debug('Fetching history for model type:', objectType)
|
||||||
const encodedStartDate = encodeURIComponent(startDate.toISOString())
|
const encodedStartDate = encodeURIComponent(startDate.toISOString())
|
||||||
@ -1967,6 +1991,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
searchObjects,
|
searchObjects,
|
||||||
fetchSpotlightData,
|
fetchSpotlightData,
|
||||||
getModelStats,
|
getModelStats,
|
||||||
|
getModelPropertyValues,
|
||||||
getModelHistory,
|
getModelHistory,
|
||||||
fetchLoading,
|
fetchLoading,
|
||||||
showError,
|
showError,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user