diff --git a/src/main/java/com/application/munera/services/PersonService.java b/src/main/java/com/application/munera/services/PersonService.java index 24587c1..76031ea 100644 --- a/src/main/java/com/application/munera/services/PersonService.java +++ b/src/main/java/com/application/munera/services/PersonService.java @@ -35,6 +35,10 @@ public class PersonService { this.personRepository.deleteById(id); } + public Page list(Pageable pageable){ + return personRepository.findAll(pageable); + } + public Page list(Pageable pageable, Specification filter) { return this.personRepository.findAll(filter, pageable); } diff --git a/src/main/java/com/application/munera/views/MainLayout.java b/src/main/java/com/application/munera/views/MainLayout.java index 26d69ff..6c958cb 100644 --- a/src/main/java/com/application/munera/views/MainLayout.java +++ b/src/main/java/com/application/munera/views/MainLayout.java @@ -2,6 +2,7 @@ package com.application.munera.views; import com.application.munera.views.expenses.CategoriesView; 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.DrawerToggle; 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("Categories", CategoriesView.class, LineAwesomeIcon.COLUMNS_SOLID.create())); + nav.addItem(new SideNavItem("People", PeopleView.class, LineAwesomeIcon.COLUMNS_SOLID.create())); return nav; } diff --git a/src/main/java/com/application/munera/views/expenses/PeopleView.java b/src/main/java/com/application/munera/views/expenses/PeopleView.java new file mode 100644 index 0000000..7c78749 --- /dev/null +++ b/src/main/java/com/application/munera/views/expenses/PeopleView.java @@ -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 grid = new Grid<>(Person.class, false); + + private final Button cancel = new Button("Cancel"); + private final Button save = new Button("Save"); + + private final BeanValidationBinder 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 personId = event.getRouteParameters().get(PERSON_ID).map(Long::parseLong); + if (personId.isPresent()) { + Optional 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); + + } +} \ No newline at end of file