From 7739c3d591a4ee2b1292e3ea10b47670976cdae0 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 25 Sep 2023 13:27:53 +0300 Subject: [PATCH 1/3] feat: Support header filters for number fields (#1326) Fixes #1284 --- .gitignore | 1 + .../PropertyStringFilterSpecification.java | 34 +++++++- .../crud/filter/PropertyStringFilter.java | 2 +- .../test/java/dev/hilla/crud/FilterTest.java | 73 +++++++++++++++- packages/java/tests/pom.xml | 48 ++++++----- .../test/reactgrid/AbstractGridTest.java | 5 +- .../reactgrid/ReadOnlyGridOrFilterIT.java | 6 +- .../ReadOnlyGridSinglePropertyFilterIT.java | 6 +- .../ReadOnlyGridWithHeaderFilterIT.java | 68 ++++++++++++++- packages/ts/react-grid/package.json | 4 +- packages/ts/react-grid/src/autogrid.tsx | 83 +++++------------- packages/ts/react-grid/src/field-factory.tsx | 15 ---- .../react-grid/src/header-column-context.tsx | 10 +++ .../react-grid/src/header-filter.module.css | 11 +++ packages/ts/react-grid/src/header-filter.tsx | 86 +++++++++++++++++++ packages/ts/react-grid/src/header-sorter.tsx | 8 ++ packages/ts/react-grid/src/types.d.ts | 5 ++ .../filter/PropertyStringFilter/Matcher.ts | 4 +- packages/ts/react-grid/test/autogrid.spec.tsx | 71 ++++++++++++--- .../test/test-models-and-services.ts | 11 ++- 20 files changed, 414 insertions(+), 137 deletions(-) delete mode 100644 packages/ts/react-grid/src/field-factory.tsx create mode 100644 packages/ts/react-grid/src/header-column-context.tsx create mode 100644 packages/ts/react-grid/src/header-filter.module.css create mode 100644 packages/ts/react-grid/src/header-filter.tsx create mode 100644 packages/ts/react-grid/src/header-sorter.tsx create mode 100644 packages/ts/react-grid/src/types.d.ts diff --git a/.gitignore b/.gitignore index 0e9e1430f4..527be4fd89 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ packages/ts/*/*.d.ts packages/ts/*/*.d.ts.map packages/ts/*/*.js packages/ts/*/*.js.map +packages/ts/*/*.module.css packages/ts/*/.coverage packages/ts/*/.vite packages/ts/*/types/** diff --git a/packages/java/endpoint/src/main/java/dev/hilla/crud/PropertyStringFilterSpecification.java b/packages/java/endpoint/src/main/java/dev/hilla/crud/PropertyStringFilterSpecification.java index 375a991d13..626ecfd594 100644 --- a/packages/java/endpoint/src/main/java/dev/hilla/crud/PropertyStringFilterSpecification.java +++ b/packages/java/endpoint/src/main/java/dev/hilla/crud/PropertyStringFilterSpecification.java @@ -34,13 +34,39 @@ public Predicate toPredicate(Root root, CriteriaQuery query, case CONTAINS: return criteriaBuilder.like(expr, "%" + value.toLowerCase() + "%"); + case GREATER_THAN: + throw new IllegalArgumentException( + "A string cannot be filtered using greater than"); + case LESS_THAN: + throw new IllegalArgumentException( + "A string cannot be filtered using less than"); + default: + break; } - throw new IllegalArgumentException( - "Matcher of type " + filter.getMatcher() + " is unknown"); - } else { - return criteriaBuilder.equal(propertyPath, value.toLowerCase()); + } else if (isNumber(javaType)) { + switch (filter.getMatcher()) { + case EQUALS: + return criteriaBuilder.equal(propertyPath, value); + case CONTAINS: + throw new IllegalArgumentException( + "A number cannot be filtered using contains"); + case GREATER_THAN: + return criteriaBuilder.greaterThan(propertyPath, value); + case LESS_THAN: + return criteriaBuilder.lessThan(propertyPath, value); + default: + break; + } } + throw new IllegalArgumentException("No implementation for " + javaType + + " using " + filter.getMatcher() + "."); + } + + private boolean isNumber(Class javaType) { + return javaType == int.class || javaType == Integer.class + || javaType == double.class || javaType == Double.class + || javaType == long.class || javaType == Long.class; } } diff --git a/packages/java/endpoint/src/main/java/dev/hilla/crud/filter/PropertyStringFilter.java b/packages/java/endpoint/src/main/java/dev/hilla/crud/filter/PropertyStringFilter.java index 4aa4a462a9..afedd36121 100644 --- a/packages/java/endpoint/src/main/java/dev/hilla/crud/filter/PropertyStringFilter.java +++ b/packages/java/endpoint/src/main/java/dev/hilla/crud/filter/PropertyStringFilter.java @@ -6,7 +6,7 @@ */ public class PropertyStringFilter implements Filter { public enum Matcher { - EQUALS, CONTAINS; + EQUALS, CONTAINS, LESS_THAN, GREATER_THAN; } private String propertyId; diff --git a/packages/java/endpoint/src/test/java/dev/hilla/crud/FilterTest.java b/packages/java/endpoint/src/test/java/dev/hilla/crud/FilterTest.java index 7f2142cfb4..245cbe858d 100644 --- a/packages/java/endpoint/src/test/java/dev/hilla/crud/FilterTest.java +++ b/packages/java/endpoint/src/test/java/dev/hilla/crud/FilterTest.java @@ -1,5 +1,6 @@ package dev.hilla.crud; +import java.util.ArrayList; import java.util.List; import dev.hilla.crud.filter.AndFilter; @@ -13,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.context.junit4.SpringRunner; @@ -42,8 +44,61 @@ public void filterStringPropertyUsingEquals() { assertFilterResult(filter, "John"); } + @Test(expected = InvalidDataAccessApiUsageException.class) + public void filterStringPropertyUsingLessThan() { + setupNames("Jack", "John", "Johnny", "Polly", "Josh"); + PropertyStringFilter filter = createNameFilter(Matcher.LESS_THAN, + "John"); + assertFilterResult(filter, "John"); + } + + @Test(expected = InvalidDataAccessApiUsageException.class) + public void filterStringPropertyUsingGreaterThan() { + setupNames("Jack", "John", "Johnny", "Polly", "Josh"); + PropertyStringFilter filter = createNameFilter(Matcher.GREATER_THAN, + "John"); + assertFilterResult(filter, "John"); + } + + @Test(expected = InvalidDataAccessApiUsageException.class) + public void filterNumberPropertyUsingContains() { + setupNames("Jack", "John", "Johnny", "Polly", "Josh"); + PropertyStringFilter filter = createIdFilter(Matcher.CONTAINS, "2"); + assertFilterResult(filter, "John"); + } + + @Test + public void filterNumberPropertyUsingEquals() { + List created = setupNames("Jack", "John", "Johnny", "Polly", + "Josh"); + Integer johnId = created.get(1).getId(); + PropertyStringFilter filter = createIdFilter(Matcher.EQUALS, + johnId + ""); + assertFilterResult(filter, "John"); + } + + @Test + public void filterNumberPropertyUsingLessThan() { + List created = setupNames("Jack", "John", "Johnny", "Polly", + "Josh"); + Integer johnnyId = created.get(2).getId(); + PropertyStringFilter filter = createIdFilter(Matcher.LESS_THAN, + johnnyId + ""); + assertFilterResult(filter, "Jack", "John"); + } + + @Test + public void filterNumberPropertyUsingGreaterThan() { + List created = setupNames("Jack", "John", "Johnny", "Polly", + "Josh"); + Integer johnnyId = created.get(2).getId(); + PropertyStringFilter filter = createIdFilter(Matcher.GREATER_THAN, + johnnyId + ""); + assertFilterResult(filter, "Polly", "Josh"); + } + @Test(expected = IllegalArgumentException.class) - public void filterNonExistingStringProperty() { + public void filterNonExistingProperty() { setupNames("Jack", "John", "Johnny", "Polly", "Josh"); PropertyStringFilter filter = createNameFilter(Matcher.EQUALS, "John"); filter.setPropertyId("foo"); @@ -95,13 +150,23 @@ private PropertyStringFilter createNameFilter(Matcher matcher, return filter; } - private void setupNames(String... names) { + private PropertyStringFilter createIdFilter(Matcher matcher, + String filterString) { + PropertyStringFilter filter = new PropertyStringFilter(); + filter.setPropertyId("id"); + filter.setFilterValue(filterString); + filter.setMatcher(matcher); + return filter; + } + + private List setupNames(String... names) { + List created = new ArrayList<>(); for (String name : names) { TestObject testObject = new TestObject(); testObject.setName(name); - entityManager.persist(testObject); + created.add(entityManager.persist(testObject)); } entityManager.flush(); - + return created; } } diff --git a/packages/java/tests/pom.xml b/packages/java/tests/pom.xml index f89bf0ad16..0efe52f05a 100644 --- a/packages/java/tests/pom.xml +++ b/packages/java/tests/pom.xml @@ -77,37 +77,43 @@ test - com.vaadin - vaadin-login-testbench - ${testbench.element.version} - test + com.vaadin + vaadin-login-testbench + ${testbench.element.version} + test - com.vaadin - vaadin-grid-testbench - ${testbench.element.version} - test + com.vaadin + vaadin-grid-testbench + ${testbench.element.version} + test - com.vaadin - vaadin-notification-testbench - ${testbench.element.version} - test + com.vaadin + vaadin-notification-testbench + ${testbench.element.version} + test - com.vaadin - vaadin-text-field-testbench - ${testbench.element.version} - test + com.vaadin + vaadin-select-testbench + ${testbench.element.version} + test + + + com.vaadin + vaadin-text-field-testbench + ${testbench.element.version} + test - org.apache.httpcomponents.client5 - httpclient5 - 5.2.1 + org.apache.httpcomponents.client5 + httpclient5 + 5.2.1 - + - + org.apache.maven.plugins diff --git a/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/AbstractGridTest.java b/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/AbstractGridTest.java index bf32a404c0..987b7719c3 100644 --- a/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/AbstractGridTest.java +++ b/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/AbstractGridTest.java @@ -39,9 +39,10 @@ protected void sortByColumn(int i) { grid.getHeaderCell(i).$("vaadin-grid-sorter").first().click(); } - protected void assertVisibleRows(int i) { + protected void assertRowCount(int i) { + grid.scrollToRow(3403034); waitUntil(driver -> { - return grid.getLastVisibleRowIndex() == i - 1; + return grid.getRowCount() == i; }); } diff --git a/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridOrFilterIT.java b/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridOrFilterIT.java index ff2918b91c..05b4ef6ca2 100644 --- a/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridOrFilterIT.java +++ b/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridOrFilterIT.java @@ -13,15 +13,15 @@ protected String getTestPath() { @Test public void findsMatches() { setFilter("car"); - assertVisibleRows(2); + assertRowCount(2); assertName(0, "Oscar", "Nelson"); assertName(1, "Abigail", "Carter"); setFilter(""); - assertVisibleRows(10); + assertRowCount(50); setFilter("zan"); - assertVisibleRows(1); + assertRowCount(1); assertName(0, "Xander", "Zane"); } diff --git a/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridSinglePropertyFilterIT.java b/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridSinglePropertyFilterIT.java index be1cc09530..9f646f059b 100644 --- a/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridSinglePropertyFilterIT.java +++ b/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridSinglePropertyFilterIT.java @@ -13,15 +13,15 @@ protected String getTestPath() { @Test public void findsMatches() { setFilter("Ali"); - assertVisibleRows(1); + assertRowCount(1); assertName(0, "Alice", "Johnson"); setFilter(""); assertName(0, "Alice", "Johnson"); - assertVisibleRows(10); + assertRowCount(50); setFilter("xan"); - assertVisibleRows(2); + assertRowCount(2); assertName(0, "Xander", "Hill"); assertName(1, "Xander", "Zane"); diff --git a/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridWithHeaderFilterIT.java b/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridWithHeaderFilterIT.java index 415ecb738e..2b7a87fe87 100644 --- a/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridWithHeaderFilterIT.java +++ b/packages/java/tests/spring/react-grid-test/src/test/java/dev/hilla/test/reactgrid/ReadOnlyGridWithHeaderFilterIT.java @@ -1,9 +1,17 @@ package dev.hilla.test.reactgrid; +import java.util.Locale; +import java.util.function.Consumer; + +import dev.hilla.crud.filter.PropertyStringFilter.Matcher; import org.junit.Test; +import com.vaadin.flow.component.select.testbench.SelectElement; +import com.vaadin.flow.component.textfield.testbench.NumberFieldElement; import com.vaadin.flow.component.textfield.testbench.TextFieldElement; +import com.vaadin.testbench.TestBench; import com.vaadin.testbench.TestBenchElement; +import com.vaadin.testbench.elementsbase.Element; public class ReadOnlyGridWithHeaderFilterIT extends AbstractGridTest { @@ -14,24 +22,78 @@ protected String getTestPath() { @Test public void stringMatchesContains() { setHeaderFilter(2, "al"); - assertVisibleRows(1); + assertRowCount(1); assertName(0, "Alice", "Johnson"); // Test that we are and:ing setHeaderFilter(3, "j"); - assertVisibleRows(1); + assertRowCount(1); assertName(0, "Alice", "Johnson"); setHeaderFilter(2, ""); - assertVisibleRows(2); + assertRowCount(2); assertName(0, "Alice", "Johnson"); assertName(1, "Henry", "Jackson"); } + @Test + public void numberFilterWorks() { + setHeaderFilter(0, null, "40"); // Default is greater_than + assertRowCount(10); + assertName(0, "Oliver", "Quinn"); + + setHeaderFilter(0, Matcher.LESS_THAN, null); + assertRowCount(39); + assertName(0, "Alice", "Johnson"); + + setHeaderFilter(0, Matcher.EQUALS, "30"); + assertRowCount(1); + assertName(0, "Dylan", "Fisher"); + + } + + @Test + public void numberFilterWithInvalidInputIgnored() { + setHeaderFilter(0, null, "a"); + assertRowCount(50); + setHeaderFilter(0, null, "49"); + assertRowCount(1); + } + private void setHeaderFilter(int columnIndex, String filter) { TestBenchElement cont = grid.getHeaderCellContent(1, columnIndex); TextFieldElement filterField = cont.$(TextFieldElement.class).first(); filterField.setValue(filter); } + private void setHeaderFilter(int columnIndex, Matcher matcher, + String filter) { + TestBenchElement cont = grid.getHeaderCellContent(1, columnIndex); + SelectElement filterSelect = cont.$(SelectElement.class).first(); + if (matcher == Matcher.GREATER_THAN) { + filterSelect.setProperty("value", "GREATER_THAN"); + } else if (matcher == Matcher.LESS_THAN) { + filterSelect.setProperty("value", "LESS_THAN"); + } else if (matcher == Matcher.EQUALS) { + filterSelect.setProperty("value", "EQUALS"); + } + TestBenchElement filterField = filterSelect + .getPropertyElement("nextElementSibling"); + if (filter != null) { + ifType(filterField, TextFieldElement.class, + e -> e.setValue(filter)); + ifType(filterField, NumberFieldElement.class, + e -> e.setValue(filter)); + } + } + + private void ifType(TestBenchElement element, + Class type, Consumer cmd) { + Element annotation = type.getAnnotation(Element.class); + if (element.getTagName().toLowerCase(Locale.ENGLISH) + .equals(annotation.value().toLowerCase(Locale.ENGLISH))) { + cmd.accept(TestBench.wrap(element, type)); + } + } + } diff --git a/packages/ts/react-grid/package.json b/packages/ts/react-grid/package.json index d1ba390c38..9104d39ce0 100644 --- a/packages/ts/react-grid/package.json +++ b/packages/ts/react-grid/package.json @@ -20,7 +20,7 @@ "build": "concurrently npm:build:*", "build:esbuild": "tsx ../../../scripts/build.ts", "build:dts": "tsc --isolatedModules -p tsconfig.build.json", - "build:copy": "cd src && copyfiles **/*.d.ts ..", + "build:copy": "cd src && copyfiles **/*.d.ts .. && copyfiles *.module.css ..", "lint": "eslint src test", "lint:fix": "eslint src test --fix", "test": "karma start ../../../karma.config.cjs --port 9879", @@ -41,7 +41,7 @@ }, "homepage": "https://hilla.dev", "files": [ - "*.{d.ts.map,d.ts,js.map,js}", + "*.{d.ts.map,d.ts,js.map,js,module.css}", "types/**" ], "publishConfig": { diff --git a/packages/ts/react-grid/src/autogrid.tsx b/packages/ts/react-grid/src/autogrid.tsx index f2248de436..296aac9840 100644 --- a/packages/ts/react-grid/src/autogrid.tsx +++ b/packages/ts/react-grid/src/autogrid.tsx @@ -10,15 +10,14 @@ import { } from '@hilla/react-components/Grid.js'; import { GridColumn } from '@hilla/react-components/GridColumn.js'; import { GridColumnGroup } from '@hilla/react-components/GridColumnGroup.js'; -import { GridSortColumn } from '@hilla/react-components/GridSortColumn.js'; -import { GridSorter } from '@hilla/react-components/GridSorter.js'; -import { useCallback, useEffect, useRef, useState, type JSX } from 'react'; +import { useEffect, useRef, useState, type JSX } from 'react'; import type { CrudService } from './crud'; -import { createFilterField } from './field-factory'; +import { HeaderColumnContext } from './header-column-context'; +import { HeaderFilter } from './header-filter'; +import { HeaderSorter } from './header-sorter'; import type AndFilter from './types/dev/hilla/crud/filter/AndFilter'; import type Filter from './types/dev/hilla/crud/filter/Filter'; import type PropertyStringFilter from './types/dev/hilla/crud/filter/PropertyStringFilter'; -import Matcher from './types/dev/hilla/crud/filter/PropertyStringFilter/Matcher'; import type Sort from './types/dev/hilla/mappedtypes/Sort'; import Direction from './types/org/springframework/data/domain/Sort/Direction'; import { getProperties, type PropertyInfo } from './utils.js'; @@ -91,49 +90,6 @@ function createDataProvider( }; } -function useHeaderFilterRenderer( - properties: React.MutableRefObject, - setPropertyFilter: React.MutableRefObject<(propertyFilter: PropertyStringFilter) => void>, -) { - return useCallback((column: any) => { - const path = pathFromColumn(column); - if (!path) { - return null; - } - const propertyInfo: PropertyInfo = properties.current.find((p) => p.name === path)!; - - return createFilterField(propertyInfo, { - onInput: (e: { target: { value: string } }) => { - const fieldValue = e.target.value; - const filterValue = fieldValue; - - const filter = { - propertyId: propertyInfo.name, - filterValue, - matcher: Matcher.CONTAINS, - }; - - // eslint-disable-next-line - (filter as any).t = 'propertyString'; - setPropertyFilter.current(filter); - }, - }); - }, []); -} - -function useHeaderSorterRenderer(properties: React.MutableRefObject) { - return useCallback((column: any) => { - const path = pathFromColumn(column); - if (!path) { - return null; - } - - const propertyInfo: PropertyInfo = properties.current.find((p) => p.name === path)!; - - return {propertyInfo.humanReadableName}; - }, []); -} - function useColumns( model: ModelConstructor>, setPropertyFilter: (propertyFilter: PropertyStringFilter) => void, @@ -144,23 +100,22 @@ function useColumns( const effectiveProperties = effectiveColumns .map((name) => properties.find((prop) => prop.name === name)) .filter(Boolean) as PropertyInfo[]; - const propertiesRef = useRef([]); - propertiesRef.current = properties; - const setPropertyFilterRef = useRef(setPropertyFilter); - setPropertyFilterRef.current = setPropertyFilter; - const headerFilterRenderer = useHeaderFilterRenderer(propertiesRef, setPropertyFilterRef); - const headerSorterRenderer = useHeaderSorterRenderer(propertiesRef); - - return effectiveProperties.map((p) => { + + return effectiveProperties.map((propertyInfo) => { + let column; if (options.headerFilters) { - return ( - - + column = ( + + ); + } else { + column = ; } return ( - + + {column} + ); }); } @@ -179,17 +134,23 @@ export function AutoGrid({ const filterIndex = internalFilter.children.findIndex( (f) => (f as PropertyStringFilter).propertyId === propertyFilter.propertyId, ); + let changed = false; if (propertyFilter.filterValue === '') { // Delete empty filter if (filterIndex >= 0) { internalFilter.children.splice(filterIndex, 1); + changed = true; } } else if (filterIndex >= 0) { internalFilter.children[filterIndex] = propertyFilter; + changed = true; } else { internalFilter.children.push(propertyFilter); + changed = true; + } + if (changed) { + setInternalFilter({ ...internalFilter }); } - setInternalFilter({ ...internalFilter }); }; // This cast should go away with #1252 diff --git a/packages/ts/react-grid/src/field-factory.tsx b/packages/ts/react-grid/src/field-factory.tsx deleted file mode 100644 index c9d33e75dc..0000000000 --- a/packages/ts/react-grid/src/field-factory.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { TextField } from '@hilla/react-components/TextField.js'; -import type { PropertyInfo } from './utils'; - -export function createFilterField(prop: PropertyInfo, additionalProps: Record): JSX.Element | null { - let field: JSX.Element | null; - let commonProps = {}; - commonProps = { ...commonProps, ...additionalProps }; - if (prop.modelType === 'string') { - field = ; - } else { - field = null; - } - - return field; -} diff --git a/packages/ts/react-grid/src/header-column-context.tsx b/packages/ts/react-grid/src/header-column-context.tsx new file mode 100644 index 0000000000..bcde31c76a --- /dev/null +++ b/packages/ts/react-grid/src/header-column-context.tsx @@ -0,0 +1,10 @@ +import { createContext } from 'react'; +import type PropertyStringFilter from './types/dev/hilla/crud/filter/PropertyStringFilter'; +import type { PropertyInfo } from './utils'; + +export type HeaderColumnContext = { + propertyInfo: PropertyInfo; + setPropertyFilter(propertyFilter: PropertyStringFilter): void; +}; + +export const HeaderColumnContext = createContext(null); diff --git a/packages/ts/react-grid/src/header-filter.module.css b/packages/ts/react-grid/src/header-filter.module.css new file mode 100644 index 0000000000..a2f98bc94f --- /dev/null +++ b/packages/ts/react-grid/src/header-filter.module.css @@ -0,0 +1,11 @@ +.filterWithLessGreaterEquals { + --vaadin-field-default-width: 2em; + margin-right: 3px; +} +.filterWithLessGreaterEquals > vaadin-select-value-button { + --_lumo-text-field-overflow-mask-image: none !important; +} + +.filterWithLessGreaterEquals::part(toggle-button) { + display: none; +} diff --git a/packages/ts/react-grid/src/header-filter.tsx b/packages/ts/react-grid/src/header-filter.tsx new file mode 100644 index 0000000000..84a647a2ee --- /dev/null +++ b/packages/ts/react-grid/src/header-filter.tsx @@ -0,0 +1,86 @@ +import { Item } from '@hilla/react-components/Item.js'; +import { ListBox } from '@hilla/react-components/ListBox.js'; +import { NumberField } from '@hilla/react-components/NumberField.js'; +import { Select, type SelectElement } from '@hilla/react-components/Select.js'; +import { TextField, type TextFieldElement } from '@hilla/react-components/TextField.js'; +import { useContext, useEffect, useRef, useState, type ReactElement } from 'react'; +import { HeaderColumnContext } from './header-column-context.js'; +import css from './header-filter.module.css'; +import Matcher from './types/dev/hilla/crud/filter/PropertyStringFilter/Matcher'; + +export function HeaderFilter(): ReactElement { + const context = useContext(HeaderColumnContext)!; + const [matcher, setMatcher] = useState(Matcher.GREATER_THAN); + const [filterValue, setFilterValue] = useState(''); + const select = useRef(null); + + function updateFilter(newMatcher: Matcher, newFilterValue: string) { + setFilterValue(newFilterValue); + setMatcher(newMatcher); + + const filter = { + propertyId: context.propertyInfo.name, + filterValue: newFilterValue, + matcher: newMatcher, + }; + // eslint-disable-next-line + (filter as any).t = 'propertyString'; + context.setPropertyFilter(filter); + } + + useEffect(() => { + // Workaround for https://github.com/vaadin/react-components/issues/148 + setTimeout(() => { + if (select.current) { + select.current.requestContentUpdate(); + } + }, 1); + }, []); + + if (context.propertyInfo.modelType === 'string') { + return ( + { + const fieldValue = ((e as InputEvent).target as TextFieldElement).value; + updateFilter(Matcher.CONTAINS, fieldValue); + }} + > + ); + } else if (context.propertyInfo.modelType === 'number') { + return ( + <> +