diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css
index 4d567f8..c08a1a5 100644
--- a/assets/stylesheets/App.css
+++ b/assets/stylesheets/App.css
@@ -1106,3 +1106,16 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
margin-inline-start: 0;
margin-top: 4px;
}
+
+.simple-date-time-property-filter {
+ margin-top: 3px;
+}
+
+.simple-date-time-property-filter.ant-tree .ant-tree-treenode {
+ margin-bottom: 0;
+ height: 40px;
+}
+
+.ant-tree-list-holder-inner > .ant-tree-treenode:last-child {
+ margin-bottom: 0;
+}
diff --git a/src/components/Dashboard/common/SimpleDateTimePropertyFilter.jsx b/src/components/Dashboard/common/SimpleDateTimePropertyFilter.jsx
index f0f30d5..7b8e099 100644
--- a/src/components/Dashboard/common/SimpleDateTimePropertyFilter.jsx
+++ b/src/components/Dashboard/common/SimpleDateTimePropertyFilter.jsx
@@ -1,9 +1,11 @@
import { useState, useEffect, useContext, useMemo, useCallback } from 'react'
-import { Spin, Tree } from 'antd'
+import { Spin, Tree, Flex, Checkbox, Typography } from 'antd'
import PropTypes from 'prop-types'
import dayjs from 'dayjs'
import { ApiServerContext } from '../context/ApiServerContext'
-import { LoadingOutlined } from '@ant-design/icons'
+import { LoadingOutlined, CaretDownOutlined } from '@ant-design/icons'
+
+const { Text } = Typography
const MONTH_NAMES = Array.from({ length: 12 }, (_, i) =>
dayjs().month(i).format('MMMM')
@@ -145,13 +147,7 @@ const buildTreeData = (hierarchy) => {
day,
hour,
minute,
- expression: toMinuteExpression(
- year,
- month,
- day,
- hour,
- minute
- ),
+ expression: toMinuteExpression(year, month, day, hour, minute),
children: secondNodes
}
})
@@ -419,10 +415,7 @@ const SimpleDateTimePropertyFilter = ({
const allKeys = useMemo(() => collectAllKeys(treeData), [treeData])
const allLeafKeys = useMemo(() => collectLeafKeys(treeData), [treeData])
- const valueKey = useMemo(
- () => JSON.stringify(value || []),
- [value]
- )
+ const valueKey = useMemo(() => JSON.stringify(value || []), [value])
useEffect(() => {
if (treeData.length === 0) return
@@ -471,31 +464,83 @@ const SimpleDateTimePropertyFilter = ({
[allLeafKeys, onChange, treeData]
)
- const handleCheck = (checked) => {
- const next = Array.isArray(checked) ? checked : checked.checked
- const query = search.trim().toLowerCase()
+ const checkedSet = useMemo(() => new Set(checkedKeys), [checkedKeys])
+ const rebuildCheckedKeys = useCallback(
+ (selectedLeaves) => {
+ const leafSet = new Set(selectedLeaves)
+ const next = []
+
+ const walk = (nodes) => {
+ for (const node of nodes) {
+ const leaves = collectLeafKeys([node])
+ if (leaves.length > 0 && leaves.every((key) => leafSet.has(key))) {
+ next.push(...collectAllKeys([node]))
+ } else if (node.children?.length) {
+ walk(node.children)
+ } else if (leafSet.has(node.key)) {
+ next.push(node.key)
+ }
+ }
+ }
+
+ walk(treeData)
+ return [...new Set(next)]
+ },
+ [treeData]
+ )
+
+ const handleNodeCheck = (node, wantChecked) => {
+ const leavesUnderNode = new Set(collectLeafKeys([node]))
+ const currentLeaves = checkedKeys.filter((key) => {
+ const n = nodeByKey.get(key)
+ return n && !n.children?.length
+ })
+
+ let nextLeaves
+ if (wantChecked) {
+ nextLeaves = [...new Set([...currentLeaves, ...leavesUnderNode])]
+ } else {
+ nextLeaves = currentLeaves.filter((key) => !leavesUnderNode.has(key))
+ }
+
+ const query = search.trim().toLowerCase()
if (!query) {
- emitChange(next)
+ emitChange(rebuildCheckedKeys(nextLeaves))
return
}
// Preserve checked leaves that are hidden by the search filter
const visibleLeafKeys = new Set(collectLeafKeys(filteredTreeData))
- const hiddenSelectedLeaves = checkedKeys.filter((key) => {
- const node = nodeByKey.get(key)
- return node && !node.children?.length && !visibleLeafKeys.has(key)
- })
- const visibleSelectedLeaves = next.filter((key) => {
- const node = nodeByKey.get(key)
- return node && !node.children?.length
- })
+ const hiddenSelectedLeaves = currentLeaves.filter(
+ (key) => !visibleLeafKeys.has(key)
+ )
+ const visibleSelectedLeaves = nextLeaves.filter((key) =>
+ visibleLeafKeys.has(key)
+ )
emitChange(
- expandKeysForChecked(
- [...hiddenSelectedLeaves, ...visibleSelectedLeaves],
- nodeByKey
- )
+ rebuildCheckedKeys([...hiddenSelectedLeaves, ...visibleSelectedLeaves])
+ )
+ }
+
+ const titleRender = (node) => {
+ const leafKeys = collectLeafKeys([node])
+ const checkedCount = leafKeys.filter((key) => checkedSet.has(key)).length
+ const checked = leafKeys.length > 0 && checkedCount === leafKeys.length
+ const indeterminate = checkedCount > 0 && checkedCount < leafKeys.length
+
+ return (
+
+ handleNodeCheck(node, e.target.checked)}
+ onClick={(e) => e.stopPropagation()}
+ style={{ minWidth: 0 }}
+ />
+ {node.title}
+
)
}
@@ -503,11 +548,12 @@ const SimpleDateTimePropertyFilter = ({
}>
{treeData.length > 0 ? (
}
+ className='simple-date-time-property-filter'
defaultExpandAll={false}
style={{ minWidth: 0 }}
/>