From 69986a9027d320bfdf57ff19de474a3826797afd Mon Sep 17 00:00:00 2001 From: filippo-ferrari Date: Tue, 9 Jul 2024 22:17:30 +0200 Subject: [PATCH] feat: 1st try to have filters view in split layout --- .../munera/services/PersonService.java | 7 +- .../munera/views/expenses/ExpensesView.java | 64 +++---- .../munera/views/expenses/Filters.java | 163 ++++++++++++++++++ 3 files changed, 202 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/application/munera/views/expenses/Filters.java diff --git a/src/main/java/com/application/munera/services/PersonService.java b/src/main/java/com/application/munera/services/PersonService.java index 3de2629..78e490c 100644 --- a/src/main/java/com/application/munera/services/PersonService.java +++ b/src/main/java/com/application/munera/services/PersonService.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.Collection; +import java.util.List; import java.util.Optional; @Service @@ -30,7 +31,11 @@ public class PersonService { public Collection findAll() { return this.personRepository.findAll(); } - + + public List findAllAsList() { + return this.personRepository.findAll(); + } + public void update(Person person) { this.personRepository.save(person); } diff --git a/src/main/java/com/application/munera/views/expenses/ExpensesView.java b/src/main/java/com/application/munera/views/expenses/ExpensesView.java index 26a3a7c..66a2ee9 100644 --- a/src/main/java/com/application/munera/views/expenses/ExpensesView.java +++ b/src/main/java/com/application/munera/views/expenses/ExpensesView.java @@ -49,7 +49,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver { private static final String EXPENSE_ID = "expenseID"; private static final String EXPENSE_EDIT_ROUTE_TEMPLATE = "/%s/edit"; - private final Grid grid = new Grid<>(Expense.class, false); + private Grid grid; private final Button cancel = new Button("Cancel"); private final Button save = new Button("Save"); @@ -74,6 +74,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver { private MultiSelectComboBox creditors; private MultiSelectComboBox debtors; private ComboBox event; + public ExpensesView(ExpenseService expenseService, CategoryService categoryService, PersonService personService, EventService eventService) { this.expenseService = expenseService; this.categoryService = categoryService; @@ -81,41 +82,15 @@ public class ExpensesView extends Div implements BeforeEnterObserver { this.eventService = eventService; addClassNames("expenses-view"); + Filters filters = new Filters(this::refreshGrid); // Create UI SplitLayout splitLayout = new SplitLayout(); - createGridLayout(splitLayout); + createGridLayout(splitLayout, filters); createEditorLayout(splitLayout); add(splitLayout); - // Configure Grid - grid.addColumn(Expense::getName).setHeader("Name").setSortable(true).setSortProperty("name"); - grid.addColumn(Expense::getCost).setHeader("Amount").setSortable(true).setSortProperty("cost"); - grid.addColumn(expenseCategory -> expenseCategory.getCategory().getName()).setHeader("Category").setSortable(true).setSortProperty("category"); - grid.addColumn(Expense::getPeriodInterval).setHeader("Period Interval").setSortable(true); - grid.addColumn(Expense::getPeriodUnit).setHeader("Period Unit").setSortable(true); - grid.addColumn(Expense::getDate).setHeader("Date").setSortable(true).setSortProperty("date"); - // grid.addColumn(expenseEvent -> expenseEvent.getEvent().getName()).setHeader("Event").setSortable(true); - - grid.addColumn(new ComponentRenderer<>(expense1 -> createBadge(expenseService.isExpenseResolved(expense1)))).setHeader("Status").setSortable(true); - grid.getColumns().forEach(col -> col.setAutoWidth(true)); - - grid.setItems(query -> expenseService.list( - PageRequest.of(query.getPage(), query.getPageSize(), VaadinSpringDataHelpers.toSpringDataSort(query))) - .stream()); - grid.addThemeVariants(GridVariant.LUMO_NO_BORDER); - - // when a row is selected or deselected, populate form - grid.asSingleSelect().addValueChangeListener(event -> { - if (event.getValue() != null) { - UI.getCurrent().navigate(String.format(EXPENSE_EDIT_ROUTE_TEMPLATE, event.getValue().getId())); - } else { - clearForm(); - UI.getCurrent().navigate(ExpensesView.class); - } - }); - // Configure Form binder = new BeanValidationBinder<>(Expense.class); @@ -270,11 +245,38 @@ public class ExpensesView extends Div implements BeforeEnterObserver { editorLayoutDiv.add(buttonLayout); } - private void createGridLayout(SplitLayout splitLayout) { + private void createGridLayout(SplitLayout splitLayout, Filters filters) { + grid = new Grid<>(Expense.class, false); Div wrapper = new Div(); wrapper.setClassName("grid-wrapper"); splitLayout.addToPrimary(wrapper); - wrapper.add(grid); + wrapper.add(filters, grid); + + // Configure Grid + grid.addColumn(Expense::getName).setHeader("Name").setSortable(true).setSortProperty("name"); + grid.addColumn(Expense::getCost).setHeader("Amount").setSortable(true).setSortProperty("cost"); + grid.addColumn(expenseCategory -> expenseCategory.getCategory().getName()).setHeader("Category").setSortable(true).setSortProperty("category"); + grid.addColumn(Expense::getPeriodInterval).setHeader("Period Interval").setSortable(true); + grid.addColumn(Expense::getPeriodUnit).setHeader("Period Unit").setSortable(true); + grid.addColumn(Expense::getDate).setHeader("Date").setSortable(true).setSortProperty("date"); + + grid.addColumn(new ComponentRenderer<>(expense1 -> createBadge(expenseService.isExpenseResolved(expense1)))).setHeader("Status").setSortable(true); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + + grid.setItems(query -> expenseService.list( + PageRequest.of(query.getPage(), query.getPageSize(), VaadinSpringDataHelpers.toSpringDataSort(query))) + .stream()); + grid.addThemeVariants(GridVariant.LUMO_NO_BORDER); + + // when a row is selected or deselected, populate form + grid.asSingleSelect().addValueChangeListener(event -> { + if (event.getValue() != null) { + UI.getCurrent().navigate(String.format(EXPENSE_EDIT_ROUTE_TEMPLATE, event.getValue().getId())); + } else { + clearForm(); + UI.getCurrent().navigate(ExpensesView.class); + } + }); } private void refreshGrid() { diff --git a/src/main/java/com/application/munera/views/expenses/Filters.java b/src/main/java/com/application/munera/views/expenses/Filters.java new file mode 100644 index 0000000..57d5974 --- /dev/null +++ b/src/main/java/com/application/munera/views/expenses/Filters.java @@ -0,0 +1,163 @@ +package com.application.munera.views.expenses; + +import com.application.munera.data.Category; +import com.application.munera.data.Expense; +import com.application.munera.data.Person; +import com.application.munera.services.CategoryService; +import com.application.munera.services.PersonService; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.Text; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.checkbox.CheckboxGroup; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.combobox.MultiSelectComboBox; +import com.vaadin.flow.component.datepicker.DatePicker; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.FlexLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.theme.lumo.LumoUtility; +import jakarta.persistence.criteria.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.domain.Specification; + +import java.util.ArrayList; +import java.util.List; + +public class Filters extends Div implements Specification { + + private final TextField name = new TextField("Expense's name"); + private final ComboBox category = new ComboBox<>("Category"); + private final DatePicker startDate = new DatePicker("Date of Birth"); + private final DatePicker endDate = new DatePicker(); + private final MultiSelectComboBox creditors = new MultiSelectComboBox<>("Creditors"); + private final CheckboxGroup isResolved = new CheckboxGroup<>("Role"); + + @Autowired + private PersonService personService; + + @Autowired + private CategoryService categoryService; + + public Filters(Runnable onSearch) { + setWidthFull(); + addClassName("filter-layout"); + addClassNames(LumoUtility.Padding.Horizontal.LARGE, LumoUtility.Padding.Vertical.MEDIUM, + LumoUtility.BoxSizing.BORDER); + name.setPlaceholder("Expense's name"); + + creditors.setItems(this.personService.findAllAsList()); + category.setItems(this.categoryService.findAll()); + isResolved.setItems("Worker", "Supervisor", "Manager", "External"); + isResolved.addClassName("double-width"); + + // Action buttons + Button resetBtn = new Button("Reset"); + resetBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + resetBtn.addClickListener(e -> { + name.clear(); + category.clear(); + startDate.clear(); + endDate.clear(); + creditors.clear(); + isResolved.clear(); + onSearch.run(); + }); + Button searchBtn = new Button("Search"); + searchBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + searchBtn.addClickListener(e -> onSearch.run()); + + Div actions = new Div(resetBtn, searchBtn); + actions.addClassName(LumoUtility.Gap.SMALL); + actions.addClassName("actions"); + + add(name, category, creditors, isResolved, actions); + } + + private Component createDateRangeFilter() { + startDate.setPlaceholder("From"); + + endDate.setPlaceholder("To"); + + // For screen readers + startDate.setAriaLabel("From date"); + endDate.setAriaLabel("To date"); + + FlexLayout dateRangeComponent = new FlexLayout(startDate, new Text(" – "), endDate); + dateRangeComponent.setAlignItems(FlexComponent.Alignment.BASELINE); + dateRangeComponent.addClassName(LumoUtility.Gap.XSMALL); + + return dateRangeComponent; + } + + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) { + List predicates = new ArrayList<>(); + + if (!name.isEmpty()) { + String lowerCaseFilter = name.getValue().toLowerCase(); + Predicate firstNameMatch = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), + lowerCaseFilter + "%"); + predicates.add(firstNameMatch); + } + +// if (!category.isEmpty()) { +// String databaseColumn = "phone"; +// String ignore = "- ()"; +// +// String lowerCaseFilter = ignoreCharacters(ignore, category.getValue().toLowerCase()); +// Predicate phoneMatch = criteriaBuilder.like( +// ignoreCharacters(ignore, criteriaBuilder, criteriaBuilder.lower(root.get(databaseColumn))), +// "%" + lowerCaseFilter + "%"); +// predicates.add(phoneMatch); +// +// } + if (startDate.getValue() != null) { + String databaseColumn = "dateOfBirth"; + predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(databaseColumn), + criteriaBuilder.literal(startDate.getValue()))); + } + if (endDate.getValue() != null) { + String databaseColumn = "dateOfBirth"; + predicates.add(criteriaBuilder.greaterThanOrEqualTo(criteriaBuilder.literal(endDate.getValue()), + root.get(databaseColumn))); + } +// if (!creditors.isEmpty()) { +// String databaseColumn = "occupation"; +// List occupationPredicates = new ArrayList<>(); +// for (String occupation : creditors.getValue()) { +// occupationPredicates +// .add(criteriaBuilder.equal(criteriaBuilder.literal(occupation), root.get(databaseColumn))); +// } +// predicates.add(criteriaBuilder.or(occupationPredicates.toArray(Predicate[]::new))); +// } + if (!isResolved.isEmpty()) { + String databaseColumn = "role"; + List rolePredicates = new ArrayList<>(); + for (String role : isResolved.getValue()) { + rolePredicates.add(criteriaBuilder.equal(criteriaBuilder.literal(role), root.get(databaseColumn))); + } + predicates.add(criteriaBuilder.or(rolePredicates.toArray(Predicate[]::new))); + } + return criteriaBuilder.and(predicates.toArray(Predicate[]::new)); + } + + private String ignoreCharacters(String characters, String in) { + String result = in; + for (int i = 0; i < characters.length(); i++) { + result = result.replace("" + characters.charAt(i), ""); + } + return result; + } + + private Expression ignoreCharacters(String characters, CriteriaBuilder criteriaBuilder, + Expression inExpression) { + Expression expression = inExpression; + for (int i = 0; i < characters.length(); i++) { + expression = criteriaBuilder.function("replace", String.class, expression, + criteriaBuilder.literal(characters.charAt(i)), criteriaBuilder.literal("")); + } + return expression; + } +}