feat: 1st try to have filters view in split layout

This commit is contained in:
filippo-ferrari 2024-07-09 22:17:30 +02:00
parent 0b31beee7b
commit 69986a9027
3 changed files with 202 additions and 32 deletions

View file

@ -10,6 +10,7 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@Service @Service
@ -31,6 +32,10 @@ public class PersonService {
return this.personRepository.findAll(); return this.personRepository.findAll();
} }
public List<Person> findAllAsList() {
return this.personRepository.findAll();
}
public void update(Person person) { public void update(Person person) {
this.personRepository.save(person); this.personRepository.save(person);
} }

View file

@ -49,7 +49,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
private static final String EXPENSE_ID = "expenseID"; private static final String EXPENSE_ID = "expenseID";
private static final String EXPENSE_EDIT_ROUTE_TEMPLATE = "/%s/edit"; private static final String EXPENSE_EDIT_ROUTE_TEMPLATE = "/%s/edit";
private final Grid<Expense> grid = new Grid<>(Expense.class, false); private Grid<Expense> grid;
private final Button cancel = new Button("Cancel"); private final Button cancel = new Button("Cancel");
private final Button save = new Button("Save"); private final Button save = new Button("Save");
@ -74,6 +74,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
private MultiSelectComboBox<Person> creditors; private MultiSelectComboBox<Person> creditors;
private MultiSelectComboBox<Person> debtors; private MultiSelectComboBox<Person> debtors;
private ComboBox<Event> event; private ComboBox<Event> event;
public ExpensesView(ExpenseService expenseService, CategoryService categoryService, PersonService personService, EventService eventService) { public ExpensesView(ExpenseService expenseService, CategoryService categoryService, PersonService personService, EventService eventService) {
this.expenseService = expenseService; this.expenseService = expenseService;
this.categoryService = categoryService; this.categoryService = categoryService;
@ -81,41 +82,15 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
this.eventService = eventService; this.eventService = eventService;
addClassNames("expenses-view"); addClassNames("expenses-view");
Filters filters = new Filters(this::refreshGrid);
// Create UI // Create UI
SplitLayout splitLayout = new SplitLayout(); SplitLayout splitLayout = new SplitLayout();
createGridLayout(splitLayout); createGridLayout(splitLayout, filters);
createEditorLayout(splitLayout); createEditorLayout(splitLayout);
add(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 // Configure Form
binder = new BeanValidationBinder<>(Expense.class); binder = new BeanValidationBinder<>(Expense.class);
@ -270,11 +245,38 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
editorLayoutDiv.add(buttonLayout); 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(); Div wrapper = new Div();
wrapper.setClassName("grid-wrapper"); wrapper.setClassName("grid-wrapper");
splitLayout.addToPrimary(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() { private void refreshGrid() {

View file

@ -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<Expense> {
private final TextField name = new TextField("Expense's name");
private final ComboBox<Category> category = new ComboBox<>("Category");
private final DatePicker startDate = new DatePicker("Date of Birth");
private final DatePicker endDate = new DatePicker();
private final MultiSelectComboBox<Person> creditors = new MultiSelectComboBox<>("Creditors");
private final CheckboxGroup<String> 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<Expense> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> 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<Predicate> 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<Predicate> 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<String> ignoreCharacters(String characters, CriteriaBuilder criteriaBuilder,
Expression<String> inExpression) {
Expression<String> 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;
}
}