Enhance ObjectSelect Component with Group Value Handling and Selection Path Logic
Some checks reported errors
farmcontrol/farmcontrol-ui/pipeline/head Something is wrong with the build of this commit
Some checks reported errors
farmcontrol/farmcontrol-ui/pipeline/head Something is wrong with the build of this commit
- Introduced utility functions for managing group value tokens and matching logic, improving the handling of null and omitted references. - Refactored selection path building to streamline the process of generating value filters and path keys based on object properties. - Updated comparison logic in tree structure manipulation to utilize new group value matching, enhancing accuracy in node handling. - Added support for expanded keys management during value updates, ensuring consistent state across selections.
This commit is contained in:
parent
82d76db106
commit
9b86114d99
@ -17,6 +17,56 @@ import { getModelProperty } from '../../../database/ObjectModels'
|
||||
const { SHOW_CHILD } = TreeSelect
|
||||
|
||||
const EMPTY_OBJECT = {}
|
||||
const EMPTY_GROUP_TOKEN = '__null__'
|
||||
|
||||
// Null and omitted references share one token. JSON drops undefined, so the
|
||||
// empty group has to use the same key the select expands with.
|
||||
const getGroupValueToken = (value) => {
|
||||
if (value == null || value === '' || value === EMPTY_GROUP_TOKEN) {
|
||||
return EMPTY_GROUP_TOKEN
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => getGroupValueToken(item)).join(',')
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
if (value._id != null && value._id !== '') return String(value._id)
|
||||
if (value.name != null && value.name !== '') return String(value.name)
|
||||
return EMPTY_GROUP_TOKEN
|
||||
}
|
||||
return String(value)
|
||||
}
|
||||
|
||||
const groupValuesMatch = (left, right) =>
|
||||
getGroupValueToken(left) === getGroupValueToken(right)
|
||||
|
||||
// Property groups always have `property` and no top-level `_id`. An empty
|
||||
// reference omits `value`, and must not be treated as a leaf.
|
||||
const isPropertyGroup = (item) =>
|
||||
Boolean(item?.property) && (item._id == null || item._id === '')
|
||||
|
||||
const buildSelectionPath = (fullValue, properties) => {
|
||||
const valueFilter = {}
|
||||
const pathKeys = []
|
||||
const parentKeys = []
|
||||
const looksComplete =
|
||||
fullValue &&
|
||||
typeof fullValue === 'object' &&
|
||||
Object.keys(fullValue).some((key) => key !== '_id')
|
||||
|
||||
properties.forEach((prop) => {
|
||||
const hasProp =
|
||||
fullValue && Object.prototype.hasOwnProperty.call(fullValue, prop)
|
||||
if (!hasProp && !looksComplete) return
|
||||
|
||||
const filterValue = hasProp ? fullValue[prop] : null
|
||||
const valueString = getGroupValueToken(filterValue)
|
||||
valueFilter[prop] = valueString
|
||||
pathKeys.push(parentKeys.concat(`${prop}:${valueString}`).join('-'))
|
||||
parentKeys.push(valueString)
|
||||
})
|
||||
|
||||
return { valueFilter, pathKeys }
|
||||
}
|
||||
|
||||
// Helper to check if two values are equal (handling objects/ids)
|
||||
const areValuesEqual = (v1, v2) => {
|
||||
@ -134,7 +184,7 @@ const getBranchChildren = (data, filterPath) => {
|
||||
const match = nodeSpecificChildren.find(
|
||||
(group) =>
|
||||
group.property === pathItem.property &&
|
||||
areValuesEqual(group.value, pathItem.value)
|
||||
groupValuesMatch(group.value, pathItem.value)
|
||||
)
|
||||
if (match) {
|
||||
nodeSpecificChildren = match.children
|
||||
@ -151,7 +201,7 @@ const replaceBranchChildren = (nodes, filterPath, newChildren, depth = 0) => {
|
||||
|
||||
const { property, value } = filterPath[depth]
|
||||
return nodes.map((node) => {
|
||||
if (node.property !== property || !areValuesEqual(node.value, value)) {
|
||||
if (node.property !== property || !groupValuesMatch(node.value, value)) {
|
||||
return node
|
||||
}
|
||||
const nextDepth = depth + 1
|
||||
@ -250,6 +300,7 @@ const ObjectSelect = ({
|
||||
const filterRef = useRef(filter)
|
||||
const masterFilterRef = useRef(masterFilter)
|
||||
const clearedMissingValueRef = useRef(false)
|
||||
const valueExpandRef = useRef(null)
|
||||
const loadGenerationRef = useRef(0)
|
||||
const loadedKeysRef = useRef(new Set())
|
||||
const treeSelectValueRef = useRef(null)
|
||||
@ -328,22 +379,15 @@ const ObjectSelect = ({
|
||||
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}`
|
||||
}
|
||||
const getGroupKey = (item) =>
|
||||
`${item.property}:${getGroupValueToken(item.value)}`
|
||||
|
||||
for (const item of incoming) {
|
||||
if (item.property && item.value !== undefined) {
|
||||
// It's a group node
|
||||
if (isPropertyGroup(item)) {
|
||||
// It's a group node, including an empty reference with no value
|
||||
const itemKey = getGroupKey(item)
|
||||
const existingIdx = merged.findIndex(
|
||||
(x) =>
|
||||
x.property && x.value !== undefined && getGroupKey(x) === itemKey
|
||||
(x) => isPropertyGroup(x) && getGroupKey(x) === itemKey
|
||||
)
|
||||
|
||||
if (existingIdx > -1) {
|
||||
@ -506,13 +550,7 @@ const ObjectSelect = ({
|
||||
if (!group.property) return null
|
||||
|
||||
const { property, value, children } = group
|
||||
var valueString = value
|
||||
if (value && typeof value === 'object' && value._id) {
|
||||
valueString = value._id
|
||||
}
|
||||
if (Array.isArray(valueString)) {
|
||||
valueString = valueString.join(',')
|
||||
}
|
||||
const valueString = getGroupValueToken(value)
|
||||
const nodeKey = parentKeys
|
||||
.concat(property + ':' + valueString)
|
||||
.join('-')
|
||||
@ -885,6 +923,7 @@ const ObjectSelect = ({
|
||||
loadedKeysRef.current = new Set()
|
||||
setInitialized(false)
|
||||
valueRef.current = null
|
||||
valueExpandRef.current = null
|
||||
setTreeSelectValue(null)
|
||||
setInitialLoading(true)
|
||||
setReloading(false)
|
||||
@ -957,6 +996,18 @@ const ObjectSelect = ({
|
||||
getValueIdentity(valueRef.current) !== valueIdentity
|
||||
) {
|
||||
valueRef.current = value
|
||||
const loadedValues = multiple
|
||||
? Array.isArray(value)
|
||||
? value
|
||||
: []
|
||||
: [value]
|
||||
const pathKeys = loadedValues.flatMap(
|
||||
(item) => buildSelectionPath(item, properties).pathKeys
|
||||
)
|
||||
if (pathKeys.length > 0) {
|
||||
setExpandedKeys([...new Set(pathKeys)])
|
||||
pathKeys.forEach((key) => loadedKeysRef.current.add(key))
|
||||
}
|
||||
setTreeSelectValue(
|
||||
multiple
|
||||
? ids.map((id) => toSelectValue(id)).filter((id) => id != null)
|
||||
@ -973,6 +1024,7 @@ const ObjectSelect = ({
|
||||
getValueIdentity(valueRef.current) !== getValueIdentity(value) &&
|
||||
type != 'unknown'
|
||||
) {
|
||||
valueExpandRef.current = getValueIdentity(value)
|
||||
valueRef.current = value
|
||||
const fullValues = await Promise.all(value.map(fetchFullObjectIfNeeded))
|
||||
if (generation !== loadGenerationRef.current) return
|
||||
@ -984,41 +1036,24 @@ const ObjectSelect = ({
|
||||
if (Array.isArray(data)) applyTreeFromData(data)
|
||||
} else {
|
||||
for (const fullValue of fullValues) {
|
||||
const valueFilter = { ...filter }
|
||||
const parentKeys = []
|
||||
const { valueFilter, pathKeys: valuePathKeys } = buildSelectionPath(
|
||||
fullValue,
|
||||
properties
|
||||
)
|
||||
pathKeys.push(...valuePathKeys)
|
||||
|
||||
properties.forEach((prop) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(fullValue, prop)) return
|
||||
|
||||
const filterValue = fullValue[prop]
|
||||
let valueString = filterValue
|
||||
if (
|
||||
filterValue &&
|
||||
typeof filterValue === 'object' &&
|
||||
filterValue._id
|
||||
) {
|
||||
valueString = filterValue._id
|
||||
} else if (filterValue?.name) {
|
||||
valueString = filterValue.name
|
||||
} else if (Array.isArray(filterValue)) {
|
||||
valueString = filterValue.join(',')
|
||||
}
|
||||
|
||||
valueFilter[prop] = valueString
|
||||
pathKeys.push(
|
||||
parentKeys.concat(prop + ':' + valueString).join('-')
|
||||
)
|
||||
parentKeys.push(valueString)
|
||||
const data = await handleFetchObjectsProperties({
|
||||
...filter,
|
||||
...valueFilter
|
||||
})
|
||||
|
||||
const data = await handleFetchObjectsProperties(valueFilter)
|
||||
if (generation !== loadGenerationRef.current) return
|
||||
if (Array.isArray(data)) applyTreeFromData(data)
|
||||
}
|
||||
}
|
||||
|
||||
setExpandedKeys([...new Set(pathKeys)])
|
||||
pathKeys.forEach((key) => loadedKeysRef.current.add(key))
|
||||
const expandedPathKeys = [...new Set(pathKeys)]
|
||||
expandedPathKeys.forEach((key) => loadedKeysRef.current.add(key))
|
||||
setExpandedKeys(expandedPathKeys)
|
||||
setTreeSelectValue(
|
||||
value
|
||||
.map((item) => toSelectValue(getValueId(item)))
|
||||
@ -1034,51 +1069,29 @@ const ObjectSelect = ({
|
||||
getValueIdentity(valueRef.current) !== getValueIdentity(value) &&
|
||||
type != 'unknown'
|
||||
) {
|
||||
valueExpandRef.current = getValueIdentity(value)
|
||||
valueRef.current = value
|
||||
const fullValue = await fetchFullObjectIfNeeded(value)
|
||||
if (generation !== loadGenerationRef.current) return
|
||||
const valueFilter = { ...filter }
|
||||
const pathKeys = []
|
||||
const parentKeys = []
|
||||
properties.forEach((prop) => {
|
||||
if (Object.prototype.hasOwnProperty.call(fullValue, prop)) {
|
||||
const filterValue = fullValue[prop]
|
||||
let valueString = filterValue
|
||||
if (
|
||||
filterValue &&
|
||||
typeof filterValue === 'object' &&
|
||||
filterValue._id
|
||||
) {
|
||||
valueFilter[prop] = filterValue._id
|
||||
valueString = filterValue._id
|
||||
} else if (filterValue?.name) {
|
||||
valueFilter[prop] = filterValue.name
|
||||
valueString = filterValue.name
|
||||
} else if (Array.isArray(filterValue)) {
|
||||
valueFilter[prop] = filterValue.join(',')
|
||||
valueString = filterValue.join(',')
|
||||
} else {
|
||||
valueFilter[prop] = filterValue
|
||||
valueString = filterValue
|
||||
}
|
||||
const nodeKey = parentKeys
|
||||
.concat(prop + ':' + valueString)
|
||||
.join('-')
|
||||
pathKeys.push(nodeKey)
|
||||
parentKeys.push(valueString)
|
||||
}
|
||||
})
|
||||
setExpandedKeys(pathKeys)
|
||||
const { valueFilter, pathKeys } = buildSelectionPath(
|
||||
fullValue,
|
||||
properties
|
||||
)
|
||||
pathKeys.forEach((key) => loadedKeysRef.current.add(key))
|
||||
const data = await handleFetchObjectsProperties(valueFilter)
|
||||
const data = await handleFetchObjectsProperties({
|
||||
...filter,
|
||||
...valueFilter
|
||||
})
|
||||
if (generation !== loadGenerationRef.current) return
|
||||
if (Array.isArray(data)) applyTreeFromData(data)
|
||||
setExpandedKeys(pathKeys)
|
||||
setTreeSelectValue(toSelectValue(valueRef.current._id))
|
||||
setInitialized(true)
|
||||
return
|
||||
}
|
||||
if (
|
||||
!initialized &&
|
||||
valueExpandRef.current == null &&
|
||||
token != null &&
|
||||
type != 'unknown' &&
|
||||
type != undefined &&
|
||||
@ -1093,6 +1106,7 @@ const ObjectSelect = ({
|
||||
type == undefined ||
|
||||
connected == false
|
||||
) {
|
||||
valueExpandRef.current = null
|
||||
setTreeSelectValue(null)
|
||||
setInitialLoading(false)
|
||||
setInitialized(true)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user