feat: TreeGrid first iteration

work in progress
This commit is contained in:
filippo-ferrari 2024-07-18 17:59:08 +02:00
parent 2415858d52
commit b7a6a2fdb3
5 changed files with 67 additions and 30 deletions

View file

@ -0,0 +1,10 @@
package com.application.munera.data;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public abstract class AbstractEntity {
private Long id;
}

View file

@ -13,7 +13,7 @@ import java.util.Set;
@Getter @Getter
@Setter @Setter
@Table(name = "expenses") @Table(name = "expenses")
public class Expense { public class Expense extends AbstractEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

View file

@ -14,7 +14,7 @@ import java.util.Set;
@Getter @Getter
@Setter @Setter
@Table(name = "people") @Table(name = "people")
public class Person { public class Person extends AbstractEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

View file

@ -11,6 +11,7 @@ import org.springframework.stereotype.Service;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream;
@Service @Service
public class ExpenseService { public class ExpenseService {
@ -40,6 +41,13 @@ public class ExpenseService {
public Collection<Expense> findUnpaidCreditByUser(final Person person) { public Collection<Expense> findUnpaidCreditByUser(final Person person) {
return repository.findUnpaidCreditorsExpensesByPersonId(person.getId()); return repository.findUnpaidCreditorsExpensesByPersonId(person.getId());
} }
public List<Expense> findExpenseByUser(final Person person) {
final var credits = this.findCreditByUser(person);
final var debits = this.findDebtByUser(person);
return Stream.concat(credits.stream(), debits.stream()).toList();
}
public List<Expense> findAll() {return repository.findAll();} public List<Expense> findAll() {return repository.findAll();}
public void update(Expense entity) { public void update(Expense entity) {

View file

@ -1,6 +1,8 @@
package com.application.munera.views.people; package com.application.munera.views.people;
import com.application.munera.data.Expense;
import com.application.munera.data.Person; import com.application.munera.data.Person;
import com.application.munera.services.ExpenseService;
import com.application.munera.services.PersonService; import com.application.munera.services.PersonService;
import com.application.munera.views.MainLayout; import com.application.munera.views.MainLayout;
import com.vaadin.flow.component.UI; import com.vaadin.flow.component.UI;
@ -8,7 +10,6 @@ import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.dependency.Uses; import com.vaadin.flow.component.dependency.Uses;
import com.vaadin.flow.component.formlayout.FormLayout; import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.html.Span;
@ -20,19 +21,18 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout; import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.textfield.EmailField; import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.treegrid.TreeGrid;
import com.vaadin.flow.data.binder.BeanValidationBinder; import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException; import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.BeforeEnterEvent; import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.data.VaadinSpringDataHelpers;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import org.springframework.data.domain.PageRequest;
import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.orm.ObjectOptimisticLockingFailureException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@PageTitle("People") @PageTitle("People")
@ -44,7 +44,7 @@ public class PeopleView extends Div implements BeforeEnterObserver {
private static final String PERSON_ID = "personID"; private static final String PERSON_ID = "personID";
private static final String PERSON_EDIT_ROUTE_TEMPLATE = "people/%s/edit"; private static final String PERSON_EDIT_ROUTE_TEMPLATE = "people/%s/edit";
private final Grid<Person> grid = new Grid<>(Person.class, false); private final TreeGrid<Object> grid = new TreeGrid<>();
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");
@ -54,12 +54,14 @@ public class PeopleView extends Div implements BeforeEnterObserver {
private Person person; private Person person;
private final PersonService personService; private final PersonService personService;
private final ExpenseService expenseService;
private TextField firstName; private TextField firstName;
private TextField lastName; private TextField lastName;
private EmailField email; private EmailField email;
public PeopleView(PersonService personService) { public PeopleView(PersonService personService, ExpenseService expenseService) {
this.personService = personService; this.personService = personService;
this.expenseService = expenseService;
addClassNames("expenses-view"); addClassNames("expenses-view");
// Create UI // Create UI
@ -70,32 +72,32 @@ public class PeopleView extends Div implements BeforeEnterObserver {
add(splitLayout); add(splitLayout);
// Configure Grid
grid.addColumn(Person::getFirstName).setHeader("First Name").setSortable(true);
grid.addColumn(Person::getLastName).setHeader("Last Name").setSortable(true);
grid.addColumn(Person::getEmail).setHeader("Email").setSortable(true);
grid.addColumn(personService::calculateDebt).setHeader("Debt").setSortable(true);
grid.addColumn(personService::calculateCredit).setHeader("Credit").setSortable(true);
grid.addColumn(personService::calculateNetBalance).setHeader("Total Expenses value").setSortable(true);
grid.addColumn(new ComponentRenderer<>(persona -> {
final var netBalance = personService.calculateNetBalance(persona);
return createBadge(netBalance);
})).setHeader("Balance Status").setSortable(true);
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.setItems(query -> personService.list(
PageRequest.of(query.getPage(), query.getPageSize(), VaadinSpringDataHelpers.toSpringDataSort(query)))
.stream());
grid.addThemeVariants(GridVariant.LUMO_NO_BORDER); grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
grid.addHierarchyColumn(this::getNodeName).setHeader("Name");
grid.addColumn(this::getNodeType).setHeader("Type");
List<Person> people = (List<Person>) personService.findAll();
for (Person person : people) {
// Add the person as a root item
grid.getTreeData().addItem(null, person);
// Fetch expenses for the current person
List<Expense> expenses = expenseService.findExpenseByUser(person);
// Add each expense as a child item under the person
for (Expense expense : expenses) {
grid.getTreeData().addItem(person, expense);
}
}
// when a row is selected or deselected, populate form // when a row is selected or deselected, populate form
grid.asSingleSelect().addValueChangeListener(event -> { grid.asSingleSelect().addValueChangeListener(event -> {
if (event.getValue() != null) { Object selectedItem = event.getValue();
UI.getCurrent().navigate(String.format(PERSON_EDIT_ROUTE_TEMPLATE, event.getValue().getId())); if (selectedItem instanceof Person) {
} else { Person selectedPerson = (Person) selectedItem;
clearForm(); UI.getCurrent().navigate(String.format("people/%d/edit", selectedPerson.getId()));
UI.getCurrent().navigate(PeopleView.class);
} }
}); });
@ -149,6 +151,23 @@ public class PeopleView extends Div implements BeforeEnterObserver {
}); });
} }
private String getNodeName(Object node) {
if (node instanceof Person) {
return ((Person) node).getFirstName() + " " + ((Person) node).getLastName();
} else if (node instanceof Expense) {
return ((Expense) node).getName() + " - $" + ((Expense) node).getCost();
}
return "";
}
private String getNodeType(Object node) {
if (node instanceof Person) {
return "Person";
} else if (node instanceof Expense) {
return "Expense";
}
return "";
}
@Override @Override
public void beforeEnter(BeforeEnterEvent event) { public void beforeEnter(BeforeEnterEvent event) {
Optional<Long> personId = event.getRouteParameters().get(PERSON_ID).map(Long::parseLong); Optional<Long> personId = event.getRouteParameters().get(PERSON_ID).map(Long::parseLong);