Enhance Filter Functionality in Dashboard Components

- Integrated property filter value into the QuickPropertyFilters component for improved filtering capabilities.
- Updated ObjectTable to register page sorter and manage filter states more effectively.
- Refactored QuickPropertyFilters to include category labels and clear filter functionality, enhancing user experience.
- Adjusted styles and layout for better usability and visual consistency across components.
This commit is contained in:
Tom Butcher 2026-09-01 20:13:59 +01:00
parent 415aa0cd9d
commit e49c7c23ed
3 changed files with 81 additions and 22 deletions

View File

@ -935,6 +935,7 @@ const FilterInput = ({
<QuickPropertyFilters <QuickPropertyFilters
modelType={propertyFilter.modelType} modelType={propertyFilter.modelType}
propertyName={propertyFilter.propertyName} propertyName={propertyFilter.propertyName}
value={propertyFilterValue}
onChange={handlePropertyFilterChange} onChange={handlePropertyFilterChange}
onModalOpenChange={handleQuickFilterModalOpenChange} onModalOpenChange={handleQuickFilterModalOpenChange}
useCard={false} useCard={false}

View File

@ -38,7 +38,6 @@ import ObjectProperty from './ObjectProperty'
import ObjectCard from './ObjectCard' import ObjectCard from './ObjectCard'
import FilterSidebar from './FilterSidebar' import FilterSidebar from './FilterSidebar'
import SortSidebar from './SortSidebar' import SortSidebar from './SortSidebar'
import XMarkIcon from '../../Icons/XMarkIcon'
import CheckIcon from '../../Icons/CheckIcon' import CheckIcon from '../../Icons/CheckIcon'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon' import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
@ -139,13 +138,6 @@ const ColumnFilterDropdown = ({
confirm() confirm()
} }
const resetFilter = () => {
setExpression('')
setDraft([])
clearFilters()
confirm()
}
return ( return (
<div style={{ padding: 8 }}> <div style={{ padding: 8 }}>
<Flex vertical gap='small'> <Flex vertical gap='small'>
@ -155,14 +147,14 @@ const ColumnFilterDropdown = ({
value={expression} value={expression}
onChange={handleExpressionChange} onChange={handleExpressionChange}
onPressEnter={applyFilter} onPressEnter={applyFilter}
style={{ width: 200 }} style={{ width: 260 }}
/> />
<Button onClick={resetFilter} icon={<XMarkIcon />} />
<Button type='primary' onClick={applyFilter} icon={<CheckIcon />} /> <Button type='primary' onClick={applyFilter} icon={<CheckIcon />} />
</Space.Compact> </Space.Compact>
<QuickPropertyFilters <QuickPropertyFilters
modelType={modelType} modelType={modelType}
propertyName={propertyName} propertyName={propertyName}
value={draft}
onChange={handleDraftChange} onChange={handleDraftChange}
/> />
<Card size='small' styles={{ body: { padding: 0, height: 200 } }}> <Card size='small' styles={{ body: { padding: 0, height: 200 } }}>
@ -291,7 +283,8 @@ const ObjectTable = forwardRef(
persistFilter, persistFilter,
persistSort, persistSort,
persistTableState, persistTableState,
registerPageFilter registerPageFilter,
registerPageSorter
} = useTableStatePersistence({ } = useTableStatePersistence({
scope: type, scope: type,
pagePath: location.pathname, pagePath: location.pathname,
@ -1116,6 +1109,11 @@ const ObjectTable = forwardRef(
return () => registerPageFilter({}) return () => registerPageFilter({})
}, [sidebarFilter, registerPageFilter]) }, [sidebarFilter, registerPageFilter])
useEffect(() => {
registerPageSorter(tableSorter)
return () => registerPageSorter({})
}, [tableSorter, registerPageSorter])
const getFilterDropdown = ({ const getFilterDropdown = ({
setSelectedKeys, setSelectedKeys,
selectedKeys, selectedKeys,

View File

@ -4,6 +4,7 @@ import {
Card, Card,
DatePicker, DatePicker,
Descriptions, Descriptions,
Dropdown,
Flex, Flex,
Input, Input,
Modal, Modal,
@ -18,6 +19,7 @@ import {
getModelProperties getModelProperties
} from '../../../database/ObjectModels' } from '../../../database/ObjectModels'
import { isDateTimePropertyType } from './filterExpression' import { isDateTimePropertyType } from './filterExpression'
import { CaretRightFilled } from '@ant-design/icons'
const { Text } = Typography const { Text } = Typography
@ -135,6 +137,22 @@ const getPropertyCategory = (property) => {
return 'text' return 'text'
} }
const getCategoryLabel = (category) => {
switch (category) {
case 'boolean':
return 'Boolean'
case 'number':
return 'Number'
case 'date':
return 'Date'
case 'object':
return 'Object'
case 'text':
default:
return 'Text'
}
}
const getFilterOptions = (category) => { const getFilterOptions = (category) => {
switch (category) { switch (category) {
case 'boolean': case 'boolean':
@ -146,6 +164,7 @@ const getFilterOptions = (category) => {
return [ return [
{ key: 'equals', label: 'Equals', needsValue: true }, { key: 'equals', label: 'Equals', needsValue: true },
{ key: 'notEquals', label: 'Does not equal', needsValue: true }, { key: 'notEquals', label: 'Does not equal', needsValue: true },
{ type: 'divider' },
{ key: 'greaterThan', label: 'Greater than', needsValue: true }, { key: 'greaterThan', label: 'Greater than', needsValue: true },
{ {
key: 'greaterOrEqual', key: 'greaterOrEqual',
@ -158,13 +177,16 @@ const getFilterOptions = (category) => {
label: 'Less than or equal', label: 'Less than or equal',
needsValue: true needsValue: true
}, },
{ type: 'divider' },
{ key: 'between', label: 'Between', needsRange: true } { key: 'between', label: 'Between', needsRange: true }
] ]
case 'date': case 'date':
return [ return [
{ key: 'equals', label: 'On', needsValue: true }, { key: 'equals', label: 'On', needsValue: true },
{ type: 'divider' },
{ key: 'lessThan', label: 'Before', needsValue: true }, { key: 'lessThan', label: 'Before', needsValue: true },
{ key: 'greaterThan', label: 'After', needsValue: true }, { key: 'greaterThan', label: 'After', needsValue: true },
{ type: 'divider' },
{ key: 'between', label: 'Between', needsRange: true } { key: 'between', label: 'Between', needsRange: true }
] ]
case 'object': case 'object':
@ -181,20 +203,36 @@ const getFilterOptions = (category) => {
label: 'Does not start with', label: 'Does not start with',
needsValue: true needsValue: true
}, },
{ type: 'divider' },
{ key: 'contains', label: 'Contains', needsValue: true }, { key: 'contains', label: 'Contains', needsValue: true },
{ key: 'notContains', label: 'Does not contain', needsValue: true }, { key: 'notContains', label: 'Does not contain', needsValue: true },
{ type: 'divider' },
{ key: 'endsWith', label: 'Ends with', needsValue: true }, { key: 'endsWith', label: 'Ends with', needsValue: true },
{ key: 'notEndsWith', label: 'Does not end with', needsValue: true }, { key: 'notEndsWith', label: 'Does not end with', needsValue: true },
{ type: 'divider' },
{ key: 'equals', label: 'Equals', needsValue: true }, { key: 'equals', label: 'Equals', needsValue: true },
{ key: 'notEquals', label: 'Does not equal', needsValue: true }, { key: 'notEquals', label: 'Does not equal', needsValue: true },
{ type: 'divider' },
{ key: 'isEmpty', label: 'Is empty' } { key: 'isEmpty', label: 'Is empty' }
] ]
} }
} }
const buildFilterMenuItems = (options, onOptionClick) =>
options.map((item) =>
item.type === 'divider'
? item
: {
key: item.key,
label: item.label,
onClick: () => onOptionClick(item)
}
)
const QuickPropertyFilters = ({ const QuickPropertyFilters = ({
modelType, modelType,
propertyName, propertyName,
value = [],
onChange, onChange,
onModalOpenChange, onModalOpenChange,
useCard = true useCard = true
@ -216,6 +254,11 @@ const QuickPropertyFilters = ({
const options = useMemo(() => getFilterOptions(category), [category]) const options = useMemo(() => getFilterOptions(category), [category])
const propertyLabel = property?.label || propertyName const propertyLabel = property?.label || propertyName
const categoryLabel = getCategoryLabel(category)
const handleClear = () => {
onChange?.([])
}
const closeModal = () => { const closeModal = () => {
onModalOpenChange?.(false) onModalOpenChange?.(false)
@ -317,25 +360,41 @@ const QuickPropertyFilters = ({
) )
} }
const filterMenuItems = buildFilterMenuItems(options, handleOptionClick)
if (!property) return null if (!property) return null
const content = ( const content = (
<Flex vertical gap={0}> <Flex gap={0} vertical>
{options.map((option) => ( <Button
disabled={!value?.length}
onClick={handleClear}
style={{
textAlign: 'left',
padding: '5px 12px',
justifyContent: 'flex-start'
}}
type='text'
>
Clear Filter from &quot;{propertyLabel}&quot;
</Button>
<Dropdown
menu={{ items: filterMenuItems }}
trigger={['hover']}
placement='rightTop'
>
<Button <Button
key={option.key}
type='text'
block
style={{ style={{
justifyContent: 'flex-start',
textAlign: 'left', textAlign: 'left',
padding: '5px 12px' padding: '5px 12px',
justifyContent: 'flex-start'
}} }}
onClick={() => handleOptionClick(option)} type='text'
> >
{option.label} {categoryLabel} Filters
{<CaretRightFilled style={{ marginLeft: 'auto' }} />}
</Button> </Button>
))} </Dropdown>
</Flex> </Flex>
) )
@ -376,7 +435,7 @@ const QuickPropertyFilters = ({
<Text> <Text>
{activeOption.needsRange {activeOption.needsRange
? `Enter a range to continue:` ? `Enter a range to continue:`
: `Enter a value to continue:`} : `Enter a ${categoryLabel.toLowerCase()} value to continue:`}
</Text> </Text>
{activeOption.needsRange ? ( {activeOption.needsRange ? (
<Flex vertical gap='middle'> <Flex vertical gap='middle'>
@ -435,6 +494,7 @@ const QuickPropertyFilters = ({
QuickPropertyFilters.propTypes = { QuickPropertyFilters.propTypes = {
modelType: PropTypes.string, modelType: PropTypes.string,
propertyName: PropTypes.string, propertyName: PropTypes.string,
value: PropTypes.array,
onChange: PropTypes.func, onChange: PropTypes.func,
onModalOpenChange: PropTypes.func, onModalOpenChange: PropTypes.func,
useCard: PropTypes.bool useCard: PropTypes.bool