Compare commits
3 Commits
05d7864da8
...
f2bdb973d1
| Author | SHA1 | Date | |
|---|---|---|---|
| f2bdb973d1 | |||
| 10a4b33620 | |||
| 2e8f627c83 |
@ -345,3 +345,8 @@ body {
|
|||||||
.rollup-table .ant-table {
|
.rollup-table .ant-table {
|
||||||
border-radius: 0px !important;
|
border-radius: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-select-selection-item .ant-tag,
|
||||||
|
.ant-select-tree-title .ant-tag {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|||||||
@ -14,8 +14,16 @@ import { AuthContext } from '../context/AuthContext'
|
|||||||
import ObjectProperty from './ObjectProperty'
|
import ObjectProperty from './ObjectProperty'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import merge from 'lodash/merge'
|
import merge from 'lodash/merge'
|
||||||
|
import { getModelProperty } from '../../../database/ObjectModels'
|
||||||
const { SHOW_CHILD } = TreeSelect
|
const { SHOW_CHILD } = TreeSelect
|
||||||
|
|
||||||
|
// Helper to check if two values are equal (handling objects/ids)
|
||||||
|
const areValuesEqual = (v1, v2) => {
|
||||||
|
const id1 = v1 && typeof v1 === 'object' && v1._id ? v1._id : v1
|
||||||
|
const id2 = v2 && typeof v2 === 'object' && v2._id ? v2._id : v2
|
||||||
|
return String(id1) === String(id2)
|
||||||
|
}
|
||||||
|
|
||||||
const ObjectSelect = ({
|
const ObjectSelect = ({
|
||||||
type = 'unknown',
|
type = 'unknown',
|
||||||
showSearch = false,
|
showSearch = false,
|
||||||
@ -31,7 +39,7 @@ const ObjectSelect = ({
|
|||||||
const { token } = useContext(AuthContext)
|
const { token } = useContext(AuthContext)
|
||||||
// --- State ---
|
// --- State ---
|
||||||
const [treeData, setTreeData] = useState([])
|
const [treeData, setTreeData] = useState([])
|
||||||
const [objectPropertiesTree, setObjectPropertiesTree] = useState({})
|
const [objectPropertiesTree, setObjectPropertiesTree] = useState([])
|
||||||
const [initialized, setInitialized] = useState(false)
|
const [initialized, setInitialized] = useState(false)
|
||||||
const [error, setError] = useState(false)
|
const [error, setError] = useState(false)
|
||||||
const properties = useMemo(() => getModelByName(type).group || [], [type])
|
const properties = useMemo(() => getModelByName(type).group || [], [type])
|
||||||
@ -69,6 +77,50 @@ const ObjectSelect = ({
|
|||||||
[isMinimalObject, fetchObject, type]
|
[isMinimalObject, fetchObject, type]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const mergeGroups = useCallback((current, incoming) => {
|
||||||
|
if (!current) return incoming
|
||||||
|
if (!incoming) return current
|
||||||
|
if (!Array.isArray(current) || !Array.isArray(incoming)) return incoming
|
||||||
|
|
||||||
|
const merged = [...current]
|
||||||
|
|
||||||
|
// Helper to generate a unique key for a group node
|
||||||
|
const getGroupKey = (item) => {
|
||||||
|
const val = item.value
|
||||||
|
const valPart =
|
||||||
|
val && typeof val === 'object' && val._id
|
||||||
|
? val._id
|
||||||
|
: JSON.stringify(val)
|
||||||
|
return `${item.property}:${valPart}`
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of incoming) {
|
||||||
|
if (item.property && item.value !== undefined) {
|
||||||
|
// It's a group node
|
||||||
|
const itemKey = getGroupKey(item)
|
||||||
|
const existingIdx = merged.findIndex(
|
||||||
|
(x) =>
|
||||||
|
x.property && x.value !== undefined && getGroupKey(x) === itemKey
|
||||||
|
)
|
||||||
|
|
||||||
|
if (existingIdx > -1) {
|
||||||
|
merged[existingIdx] = {
|
||||||
|
...merged[existingIdx],
|
||||||
|
children: mergeGroups(merged[existingIdx].children, item.children)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
merged.push(item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// It's a leaf object
|
||||||
|
if (!merged.some((x) => String(x._id) === String(item._id))) {
|
||||||
|
merged.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Fetch the object properties tree from the API
|
// Fetch the object properties tree from the API
|
||||||
const handleFetchObjectsProperties = useCallback(
|
const handleFetchObjectsProperties = useCallback(
|
||||||
async (customFilter = filter) => {
|
async (customFilter = filter) => {
|
||||||
@ -78,11 +130,14 @@ const ObjectSelect = ({
|
|||||||
filter: customFilter,
|
filter: customFilter,
|
||||||
masterFilter
|
masterFilter
|
||||||
})
|
})
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
setObjectPropertiesTree((prev) => merge([], prev, data))
|
setObjectPropertiesTree((prev) => mergeGroups(prev, data))
|
||||||
} else {
|
} else {
|
||||||
setObjectPropertiesTree((prev) => merge({}, prev, data))
|
// Fallback if API returns something unexpected
|
||||||
|
setObjectPropertiesTree((prev) => merge([], prev, data))
|
||||||
}
|
}
|
||||||
|
|
||||||
setInitialLoading(false)
|
setInitialLoading(false)
|
||||||
setError(false)
|
setError(false)
|
||||||
return data
|
return data
|
||||||
@ -92,24 +147,31 @@ const ObjectSelect = ({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[type, fetchObjectsByProperty, properties, filter, masterFilter]
|
[
|
||||||
|
type,
|
||||||
|
fetchObjectsByProperty,
|
||||||
|
properties,
|
||||||
|
filter,
|
||||||
|
masterFilter,
|
||||||
|
mergeGroups
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Convert the API response to AntD TreeSelect treeData
|
// Convert the API response to AntD TreeSelect treeData
|
||||||
const buildTreeData = useCallback(
|
const buildTreeData = useCallback(
|
||||||
(data, pIdx = 0, parentKeys = [], filterPath = []) => {
|
(data, pIdx = 0, parentKeys = [], filterPath = []) => {
|
||||||
if (!data) return []
|
if (!data || !Array.isArray(data)) return []
|
||||||
if (Array.isArray(data)) {
|
console.log(data, pIdx, properties.length)
|
||||||
|
// If we are past the grouping properties, these are leaf objects
|
||||||
|
if (pIdx >= properties.length) {
|
||||||
return data.map((object) => {
|
return data.map((object) => {
|
||||||
setObjectList((prev) => {
|
setObjectList((prev) => {
|
||||||
const filtered = prev.filter(
|
if (prev.some((p) => p._id === object._id)) return prev
|
||||||
(prevObject) => prevObject._id != object._id
|
return [...prev, object]
|
||||||
)
|
|
||||||
return [...filtered, object]
|
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
title: (
|
title: (
|
||||||
<div style={{ paddingTop: '2px' }}>
|
<div style={{ paddingTop: 0 }}>
|
||||||
<ObjectProperty
|
<ObjectProperty
|
||||||
key={object._id}
|
key={object._id}
|
||||||
type='object'
|
type='object'
|
||||||
@ -123,48 +185,63 @@ const ObjectSelect = ({
|
|||||||
value: object._id,
|
value: object._id,
|
||||||
key: object._id,
|
key: object._id,
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
property: properties[pIdx - 1], // previous property
|
|
||||||
parentKeys,
|
parentKeys,
|
||||||
filterPath
|
filterPath
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (typeof data == 'object') {
|
|
||||||
const property = properties[pIdx] || null
|
// Group Nodes
|
||||||
return Object.entries(data)
|
return data
|
||||||
.map(([key, value]) => {
|
.map((group) => {
|
||||||
if (property != null && typeof value === 'object') {
|
// Only process if it looks like a group
|
||||||
const newFilterPath = filterPath.concat({ property, value: key })
|
if (!group.property) return null
|
||||||
return {
|
|
||||||
title: <ObjectProperty type={property} value={key} />,
|
const { property, value, children } = group
|
||||||
value: parentKeys.concat(key).join('-'),
|
var valueString = value
|
||||||
filterValue: key,
|
if (value && typeof value === 'object' && value._id) {
|
||||||
key: parentKeys.concat(key).join('-'),
|
valueString = value._id
|
||||||
|
}
|
||||||
|
if (Array.isArray(valueString)) {
|
||||||
|
valueString = valueString.join(',')
|
||||||
|
}
|
||||||
|
const nodeKey = parentKeys
|
||||||
|
.concat(property + ':' + valueString)
|
||||||
|
.join('-')
|
||||||
|
const newFilterPath = filterPath.concat({
|
||||||
property,
|
property,
|
||||||
parentKeys: parentKeys.concat(key || '-'),
|
value: valueString
|
||||||
|
})
|
||||||
|
|
||||||
|
const modelProperty = getModelProperty(type, property)
|
||||||
|
return {
|
||||||
|
title: <ObjectProperty {...modelProperty} value={value} />,
|
||||||
|
value: nodeKey,
|
||||||
|
key: nodeKey,
|
||||||
|
property,
|
||||||
|
filterValue: valueString,
|
||||||
|
parentKeys: parentKeys.concat(valueString),
|
||||||
filterPath: newFilterPath,
|
filterPath: newFilterPath,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
|
isLeaf: false,
|
||||||
children: buildTreeData(
|
children: buildTreeData(
|
||||||
value,
|
children,
|
||||||
pIdx + 1,
|
pIdx + 1,
|
||||||
parentKeys.concat(key),
|
parentKeys.concat(valueString),
|
||||||
newFilterPath
|
newFilterPath
|
||||||
),
|
)
|
||||||
isLeaf: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[properties, type]
|
[properties, type]
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- loadData for async loading on expand ---
|
// --- loadData for async loading on expand ---
|
||||||
const loadData = async (node) => {
|
const loadData = async (node) => {
|
||||||
// node.property is the property name, node.value is the value
|
// node.property is the property name, node.value is the value key
|
||||||
if (!node.property) return
|
if (!node.property) return
|
||||||
|
if (type == 'unknown') return
|
||||||
// Build filter for this node by merging all parent property-value pairs
|
// Build filter for this node by merging all parent property-value pairs
|
||||||
const customFilter = { ...filter }
|
const customFilter = { ...filter }
|
||||||
if (Array.isArray(node.filterPath)) {
|
if (Array.isArray(node.filterPath)) {
|
||||||
@ -172,26 +249,40 @@ const ObjectSelect = ({
|
|||||||
customFilter[property] = value
|
customFilter[property] = value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// Ensure current node is in filter (should be covered by filterPath, but redundancy is safe)
|
||||||
customFilter[node.property] = node.filterValue
|
customFilter[node.property] = node.filterValue
|
||||||
// Fetch children for this node
|
// Fetch children for this node
|
||||||
const data = await handleFetchObjectsProperties(customFilter)
|
const data = await handleFetchObjectsProperties(customFilter)
|
||||||
if (!data) return
|
if (!data) return
|
||||||
// Extract only the children for the specific node that was expanded
|
|
||||||
let nodeSpecificData = data
|
// Navigate to the specific node's children in the response
|
||||||
if (typeof data === 'object' && !Array.isArray(data)) {
|
let nodeSpecificChildren = data
|
||||||
// If the API returns an object with multiple keys, get only the data for this node
|
|
||||||
nodeSpecificData = data[node.value] || {}
|
if (node.filterPath && Array.isArray(node.filterPath)) {
|
||||||
|
for (const pathItem of node.filterPath) {
|
||||||
|
if (!Array.isArray(nodeSpecificChildren)) break
|
||||||
|
const match = nodeSpecificChildren.find(
|
||||||
|
(g) =>
|
||||||
|
g.property === pathItem.property &&
|
||||||
|
areValuesEqual(g.value, pathItem.value)
|
||||||
|
)
|
||||||
|
if (match) {
|
||||||
|
nodeSpecificChildren = match.children
|
||||||
|
} else {
|
||||||
|
nodeSpecificChildren = []
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build new tree children only for this specific node
|
// Build new tree children only for this specific node
|
||||||
const children = buildTreeData(
|
const children = buildTreeData(
|
||||||
nodeSpecificData,
|
nodeSpecificChildren,
|
||||||
properties.indexOf(node.property) + 1,
|
properties.indexOf(node.property) + 1,
|
||||||
node.parentKeys || [],
|
node.parentKeys || [],
|
||||||
(node.filterPath || []).concat({
|
node.filterPath
|
||||||
property: node.property,
|
|
||||||
value: node.filterValue
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Update treeData with new children for this node only
|
// Update treeData with new children for this node only
|
||||||
setTreeData((prevTreeData) => {
|
setTreeData((prevTreeData) => {
|
||||||
// Helper to recursively update the correct node
|
// Helper to recursively update the correct node
|
||||||
@ -250,7 +341,8 @@ const ObjectSelect = ({
|
|||||||
value &&
|
value &&
|
||||||
typeof value === 'object' &&
|
typeof value === 'object' &&
|
||||||
value !== null &&
|
value !== null &&
|
||||||
!initialized
|
!initialized &&
|
||||||
|
type != 'unknown'
|
||||||
) {
|
) {
|
||||||
// Check if value is a minimal object and fetch full object if needed
|
// Check if value is a minimal object and fetch full object if needed
|
||||||
const fullValue = await fetchFullObjectIfNeeded(value)
|
const fullValue = await fetchFullObjectIfNeeded(value)
|
||||||
@ -260,7 +352,13 @@ const ObjectSelect = ({
|
|||||||
properties.forEach((prop) => {
|
properties.forEach((prop) => {
|
||||||
if (Object.prototype.hasOwnProperty.call(fullValue, prop)) {
|
if (Object.prototype.hasOwnProperty.call(fullValue, prop)) {
|
||||||
const filterValue = fullValue[prop]
|
const filterValue = fullValue[prop]
|
||||||
if (filterValue?.name) {
|
if (
|
||||||
|
filterValue &&
|
||||||
|
typeof filterValue === 'object' &&
|
||||||
|
filterValue._id
|
||||||
|
) {
|
||||||
|
valueFilter[prop] = filterValue._id
|
||||||
|
} else if (filterValue?.name) {
|
||||||
valueFilter[prop] = filterValue.name
|
valueFilter[prop] = filterValue.name
|
||||||
} else if (Array.isArray(filterValue)) {
|
} else if (Array.isArray(filterValue)) {
|
||||||
valueFilter[prop] = filterValue.join(',')
|
valueFilter[prop] = filterValue.join(',')
|
||||||
@ -275,12 +373,13 @@ const ObjectSelect = ({
|
|||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!initialized && token != null) {
|
if (!initialized && token != null && type != 'unknown') {
|
||||||
handleFetchObjectsProperties()
|
handleFetchObjectsProperties()
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
}
|
}
|
||||||
if (value == null) {
|
if (value == null || type == 'unknown') {
|
||||||
setTreeSelectValue(null)
|
setTreeSelectValue(null)
|
||||||
|
setInitialLoading(false)
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -292,7 +391,8 @@ const ObjectSelect = ({
|
|||||||
handleFetchObjectsProperties,
|
handleFetchObjectsProperties,
|
||||||
initialized,
|
initialized,
|
||||||
token,
|
token,
|
||||||
fetchFullObjectIfNeeded
|
fetchFullObjectIfNeeded,
|
||||||
|
type
|
||||||
])
|
])
|
||||||
|
|
||||||
const prevValuesRef = useRef({ type, masterFilter })
|
const prevValuesRef = useRef({ type, masterFilter })
|
||||||
@ -341,6 +441,14 @@ const ObjectSelect = ({
|
|||||||
}
|
}
|
||||||
}, [value])
|
}, [value])
|
||||||
|
|
||||||
|
const placeholder = useMemo(
|
||||||
|
() =>
|
||||||
|
type == 'unknown'
|
||||||
|
? 'n/a'
|
||||||
|
: `Select a ${getModelByName(type).label.toLowerCase()}...`,
|
||||||
|
[type]
|
||||||
|
)
|
||||||
|
|
||||||
// --- Error UI ---
|
// --- Error UI ---
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
@ -373,12 +481,12 @@ const ObjectSelect = ({
|
|||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
loadData={loadData}
|
loadData={loadData}
|
||||||
showCheckedStrategy={SHOW_CHILD}
|
showCheckedStrategy={SHOW_CHILD}
|
||||||
placeholder={`Select a ${getModelByName(type).label.toLowerCase()}...`}
|
placeholder={placeholder}
|
||||||
{...treeSelectProps}
|
{...treeSelectProps}
|
||||||
{...rest}
|
{...rest}
|
||||||
value={treeSelectValue}
|
value={treeSelectValue}
|
||||||
onChange={onTreeSelectChange}
|
onChange={onTreeSelectChange}
|
||||||
disabled={disabled}
|
disabled={disabled || type == 'unknown'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const ObjectTypeSelect = ({
|
|||||||
const options = objectModels
|
const options = objectModels
|
||||||
.sort((a, b) => a.label.localeCompare(b.label))
|
.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
.filter((model) => {
|
.filter((model) => {
|
||||||
if (masterFilter == null) {
|
if (masterFilter == null || Object.keys(masterFilter).length == 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return masterFilter.includes(model?.name)
|
return masterFilter.includes(model?.name)
|
||||||
|
|||||||
@ -665,7 +665,12 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
`${config.backendUrl}/${type.toLowerCase()}s/properties`,
|
`${config.backendUrl}/${type.toLowerCase()}s/properties`,
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
...filter,
|
...Object.keys(filter).reduce((acc, key) => {
|
||||||
|
acc[key] = Array.isArray(filter[key])
|
||||||
|
? filter[key].join(',')
|
||||||
|
: filter[key]
|
||||||
|
return acc
|
||||||
|
}, {}),
|
||||||
properties: properties.join(','), // Convert array to comma-separated string
|
properties: properties.join(','), // Convert array to comma-separated string
|
||||||
masterFilter: JSON.stringify(masterFilter)
|
masterFilter: JSON.stringify(masterFilter)
|
||||||
},
|
},
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export const SubJob = {
|
|||||||
columns: ['_id', 'printer', 'printer._id', 'job._id', 'state', 'createdAt'],
|
columns: ['_id', 'printer', 'printer._id', 'job._id', 'state', 'createdAt'],
|
||||||
filters: ['state', '_id', 'job._id', 'printer._id'],
|
filters: ['state', '_id', 'job._id', 'printer._id'],
|
||||||
sorters: ['createdAt', 'state'],
|
sorters: ['createdAt', 'state'],
|
||||||
|
group: ['job'],
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
name: '_id',
|
name: '_id',
|
||||||
@ -67,6 +68,12 @@ export const SubJob = {
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 175
|
columnWidth: 175
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'job',
|
||||||
|
label: 'Job',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'job'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'job._id',
|
name: 'job._id',
|
||||||
label: 'Job ID',
|
label: 'Job ID',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user