feat: PeopleView

This commit is contained in:
filippo-ferrari 2024-05-24 19:15:30 +02:00
parent fb2d026141
commit 9d127b722f
3 changed files with 196 additions and 0 deletions

View file

@ -35,6 +35,10 @@ public class PersonService {
this.personRepository.deleteById(id); this.personRepository.deleteById(id);
} }
public Page<Person> list(Pageable pageable){
return personRepository.findAll(pageable);
}
public Page<Person> list(Pageable pageable, Specification<Person> filter) { public Page<Person> list(Pageable pageable, Specification<Person> filter) {
return this.personRepository.findAll(filter, pageable); return this.personRepository.findAll(filter, pageable);
} }

View file

@ -2,6 +2,7 @@ package com.application.munera.views;
import com.application.munera.views.expenses.CategoriesView; import com.application.munera.views.expenses.CategoriesView;
import com.application.munera.views.expenses.ExpensesView; import com.application.munera.views.expenses.ExpensesView;
import com.application.munera.views.expenses.PeopleView;
import com.vaadin.flow.component.applayout.AppLayout; import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle; import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.html.Footer; import com.vaadin.flow.component.html.Footer;
@ -53,6 +54,7 @@ public class MainLayout extends AppLayout {
nav.addItem(new SideNavItem("Expenses", ExpensesView.class, LineAwesomeIcon.COLUMNS_SOLID.create())); nav.addItem(new SideNavItem("Expenses", ExpensesView.class, LineAwesomeIcon.COLUMNS_SOLID.create()));
nav.addItem(new SideNavItem("Categories", CategoriesView.class, LineAwesomeIcon.COLUMNS_SOLID.create())); nav.addItem(new SideNavItem("Categories", CategoriesView.class, LineAwesomeIcon.COLUMNS_SOLID.create()));
nav.addItem(new SideNavItem("People", PeopleView.class, LineAwesomeIcon.COLUMNS_SOLID.create()));
return nav; return nav;
} }

View file

@ -0,0 +1,190 @@
package com.application.munera.views.expenses;
import com.application.munera.data.Person;
import com.application.munera.services.CategoryService;
import com.application.munera.services.ExpenseService;
import com.application.munera.services.PersonService;
import com.application.munera.views.MainLayout;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.dependency.Uses;
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.html.Div;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.Notification.Position;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.data.VaadinSpringDataHelpers;
import org.springframework.data.domain.PageRequest;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import java.util.Optional;
@PageTitle("People")
@Route(value = "people/:personID?/:action?(edit)", layout = MainLayout.class)
@Uses(Icon.class)
public class PeopleView extends Div implements BeforeEnterObserver {
private static final String PERSON_ID = "personID";
private static final String PERSON_EDIT_ROUTE_TEMPLATE = "/%s/edit";
private final Grid<Person> grid = new Grid<>(Person.class, false);
private final Button cancel = new Button("Cancel");
private final Button save = new Button("Save");
private final BeanValidationBinder<Person> binder;
private Person person;
private final ExpenseService expenseService;
private final CategoryService categoryService;
private final PersonService personService;
private TextField firstName;
private TextField lastName;
public PeopleView(ExpenseService expenseService, CategoryService categoryService, PersonService personService) {
this.expenseService = expenseService;
this.categoryService = categoryService;
this.personService = personService;
addClassNames("people-view");
// Create UI
SplitLayout splitLayout = new SplitLayout();
createGridLayout(splitLayout);
createEditorLayout(splitLayout);
add(splitLayout);
// Configure Grid
grid.addColumn(Person::getFirstName).setHeader("First Name").setSortable(true);
grid.addColumn(Person::getLastName).setHeader("Last Name").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);
// when a row is selected or deselected, populate form
grid.asSingleSelect().addValueChangeListener(event -> {
if (event.getValue() != null) {
UI.getCurrent().navigate(String.format(PERSON_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
} else {
clearForm();
UI.getCurrent().navigate(PeopleView.class);
}
});
// Configure Form
binder = new BeanValidationBinder<>(Person.class);
// Bind fields. This is where you'd define e.g. validation rules
binder.bindInstanceFields(this);
cancel.addClickListener(e -> {
clearForm();
refreshGrid();
});
save.addClickListener(e -> {
try {
if (this.person == null) {
this.person = new Person();
}
binder.writeBean(this.person);
personService.update(this.person);
clearForm();
refreshGrid();
Notification.show("Data updated");
UI.getCurrent().navigate(PeopleView.class);
} catch (ObjectOptimisticLockingFailureException exception) {
Notification n = Notification.show(
"Error updating the data. Somebody else has updated the record while you were making changes.");
n.setPosition(Position.MIDDLE);
n.addThemeVariants(NotificationVariant.LUMO_ERROR);
} catch (ValidationException validationException) {
Notification.show("Failed to update the data. Check again that all values are valid");
}
});
}
@Override
public void beforeEnter(BeforeEnterEvent event) {
Optional<Long> personId = event.getRouteParameters().get(PERSON_ID).map(Long::parseLong);
if (personId.isPresent()) {
Optional<Person> personFromBackend = personService.get(personId.get());
if (personFromBackend.isPresent()) {
populateForm(personFromBackend.get());
} else {
Notification.show(
String.format("The requested person was not found, ID = %s", personId.get()), 3000,
Position.BOTTOM_START);
// when a row is selected but the data is no longer available,
// refresh grid
refreshGrid();
event.forwardTo(PeopleView.class);
}
}
}
private void createEditorLayout(SplitLayout splitLayout) {
Div editorLayoutDiv = new Div();
editorLayoutDiv.setClassName("editor-layout");
Div editorDiv = new Div();
editorDiv.setClassName("editor");
editorLayoutDiv.add(editorDiv);
FormLayout formLayout = new FormLayout();
firstName = new TextField("Name");
lastName = new TextField("Cost");
formLayout.add(firstName, lastName);
editorDiv.add(formLayout);
createButtonLayout(editorLayoutDiv);
splitLayout.addToSecondary(editorLayoutDiv);
}
private void createButtonLayout(Div editorLayoutDiv) {
HorizontalLayout buttonLayout = new HorizontalLayout();
buttonLayout.setClassName("button-layout");
cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
buttonLayout.add(save, cancel);
editorLayoutDiv.add(buttonLayout);
}
private void createGridLayout(SplitLayout splitLayout) {
Div wrapper = new Div();
wrapper.setClassName("grid-wrapper");
splitLayout.addToPrimary(wrapper);
wrapper.add(grid);
}
private void refreshGrid() {
grid.select(null);
grid.getDataProvider().refreshAll();
}
private void clearForm() {
populateForm(null);
}
private void populateForm(Person value) {
this.person = value;
binder.readBean(this.person);
}
}