Compare commits

..

No commits in common. "f2bdb973d17b1103de7ff1e09c5af2cfd3348a14" and "05d7864da8222397d9beb8d3913abb690e6c17e8" have entirely different histories.

5 changed files with 58 additions and 183 deletions

View File

@ -345,8 +345,3 @@ 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;
}

View File

@ -14,16 +14,8 @@ 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,
@ -39,7 +31,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])
@ -77,50 +69,6 @@ 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) => {
@ -130,14 +78,11 @@ const ObjectSelect = ({
filter: customFilter, filter: customFilter,
masterFilter masterFilter
}) })
if (Array.isArray(data)) { if (Array.isArray(data)) {
setObjectPropertiesTree((prev) => mergeGroups(prev, data))
} else {
// Fallback if API returns something unexpected
setObjectPropertiesTree((prev) => merge([], prev, data)) setObjectPropertiesTree((prev) => merge([], prev, data))
} else {
setObjectPropertiesTree((prev) => merge({}, prev, data))
} }
setInitialLoading(false) setInitialLoading(false)
setError(false) setError(false)
return data return data
@ -147,31 +92,24 @@ 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 || !Array.isArray(data)) return [] if (!data) return []
console.log(data, pIdx, properties.length) if (Array.isArray(data)) {
// 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) => {
if (prev.some((p) => p._id === object._id)) return prev const filtered = prev.filter(
return [...prev, object] (prevObject) => prevObject._id != object._id
)
return [...filtered, object]
}) })
return { return {
title: ( title: (
<div style={{ paddingTop: 0 }}> <div style={{ paddingTop: '2px' }}>
<ObjectProperty <ObjectProperty
key={object._id} key={object._id}
type='object' type='object'
@ -185,63 +123,48 @@ 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
return Object.entries(data)
.map(([key, value]) => {
if (property != null && typeof value === 'object') {
const newFilterPath = filterPath.concat({ property, value: key })
return {
title: <ObjectProperty type={property} value={key} />,
value: parentKeys.concat(key).join('-'),
filterValue: key,
key: parentKeys.concat(key).join('-'),
property,
parentKeys: parentKeys.concat(key || '-'),
filterPath: newFilterPath,
selectable: false,
// Group Nodes children: buildTreeData(
return data value,
.map((group) => { pIdx + 1,
// Only process if it looks like a group parentKeys.concat(key),
if (!group.property) return null newFilterPath
),
const { property, value, children } = group isLeaf: false
var valueString = value }
if (value && typeof value === 'object' && value._id) { }
valueString = value._id
}
if (Array.isArray(valueString)) {
valueString = valueString.join(',')
}
const nodeKey = parentKeys
.concat(property + ':' + valueString)
.join('-')
const newFilterPath = filterPath.concat({
property,
value: valueString
}) })
.filter(Boolean)
const modelProperty = getModelProperty(type, property) }
return {
title: <ObjectProperty {...modelProperty} value={value} />,
value: nodeKey,
key: nodeKey,
property,
filterValue: valueString,
parentKeys: parentKeys.concat(valueString),
filterPath: newFilterPath,
selectable: false,
isLeaf: false,
children: buildTreeData(
children,
pIdx + 1,
parentKeys.concat(valueString),
newFilterPath
)
}
})
.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 key // node.property is the property name, node.value is the value
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)) {
@ -249,40 +172,26 @@ 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
// Navigate to the specific node's children in the response let nodeSpecificData = data
let nodeSpecificChildren = data if (typeof data === 'object' && !Array.isArray(data)) {
// If the API returns an object with multiple keys, get only the data for this node
if (node.filterPath && Array.isArray(node.filterPath)) { nodeSpecificData = data[node.value] || {}
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(
nodeSpecificChildren, nodeSpecificData,
properties.indexOf(node.property) + 1, properties.indexOf(node.property) + 1,
node.parentKeys || [], node.parentKeys || [],
node.filterPath (node.filterPath || []).concat({
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
@ -341,8 +250,7 @@ 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)
@ -352,13 +260,7 @@ 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 ( if (filterValue?.name) {
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(',')
@ -373,13 +275,12 @@ const ObjectSelect = ({
setInitialized(true) setInitialized(true)
return return
} }
if (!initialized && token != null && type != 'unknown') { if (!initialized && token != null) {
handleFetchObjectsProperties() handleFetchObjectsProperties()
setInitialized(true) setInitialized(true)
} }
if (value == null || type == 'unknown') { if (value == null) {
setTreeSelectValue(null) setTreeSelectValue(null)
setInitialLoading(false)
setInitialized(true) setInitialized(true)
} }
} }
@ -391,8 +292,7 @@ const ObjectSelect = ({
handleFetchObjectsProperties, handleFetchObjectsProperties,
initialized, initialized,
token, token,
fetchFullObjectIfNeeded, fetchFullObjectIfNeeded
type
]) ])
const prevValuesRef = useRef({ type, masterFilter }) const prevValuesRef = useRef({ type, masterFilter })
@ -441,14 +341,6 @@ 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 (
@ -481,12 +373,12 @@ const ObjectSelect = ({
multiple={multiple} multiple={multiple}
loadData={loadData} loadData={loadData}
showCheckedStrategy={SHOW_CHILD} showCheckedStrategy={SHOW_CHILD}
placeholder={placeholder} placeholder={`Select a ${getModelByName(type).label.toLowerCase()}...`}
{...treeSelectProps} {...treeSelectProps}
{...rest} {...rest}
value={treeSelectValue} value={treeSelectValue}
onChange={onTreeSelectChange} onChange={onTreeSelectChange}
disabled={disabled || type == 'unknown'} disabled={disabled}
/> />
) )
} }

View File

@ -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 || Object.keys(masterFilter).length == 0) { if (masterFilter == null) {
return true return true
} }
return masterFilter.includes(model?.name) return masterFilter.includes(model?.name)

View File

@ -665,12 +665,7 @@ const ApiServerProvider = ({ children }) => {
`${config.backendUrl}/${type.toLowerCase()}s/properties`, `${config.backendUrl}/${type.toLowerCase()}s/properties`,
{ {
params: { params: {
...Object.keys(filter).reduce((acc, key) => { ...filter,
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)
}, },

View File

@ -31,7 +31,6 @@ 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',
@ -68,12 +67,6 @@ 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',