From 0e238f945540e9e57aca0a19740ac8cbbd53497c Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Mon, 10 Aug 2026 01:47:43 +0100 Subject: [PATCH] Enhance SimpleDateTimePropertyFilter with improved styling and functionality - Added CSS styles for the SimpleDateTimePropertyFilter component to improve layout and spacing. - Updated the component to include a more flexible checkbox rendering with indeterminate states. - Refactored the handleNodeCheck function for better management of checked states and visibility during filtering. --- assets/stylesheets/App.css | 13 +++ .../common/SimpleDateTimePropertyFilter.jsx | 110 +++++++++++++----- 2 files changed, 91 insertions(+), 32 deletions(-) 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 }} />