diff --git a/src/main/java/com/application/munera/views/expenses/CategoriesView.java b/src/main/java/com/application/munera/views/expenses/CategoriesView.java index 51f0a93..76638f2 100644 --- a/src/main/java/com/application/munera/views/expenses/CategoriesView.java +++ b/src/main/java/com/application/munera/views/expenses/CategoriesView.java @@ -5,62 +5,109 @@ import com.application.munera.services.CategoryService; 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.orderedlayout.VerticalLayout; +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.router.BeforeEnterEvent; -import com.vaadin.flow.router.BeforeEnterObserver; -import com.vaadin.flow.router.PageTitle; -import com.vaadin.flow.router.Route; -import org.springframework.beans.factory.annotation.Autowired; +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("Categories") @Route(value = "categories/:categoryID?/:action?(edit)", layout = MainLayout.class) -public class CategoriesView extends VerticalLayout implements BeforeEnterObserver { +@Uses(Icon.class) +public class CategoriesView extends Div implements BeforeEnterObserver { private final String CATEGORY_ID = "categoryID"; - private final TextField categoryNameField = new TextField("Category Name"); - private final Button addButton = new Button("Add Category"); - private final Grid categoryGrid = new Grid<>(Category.class); - private final Button removeButton = new Button("Remove Category"); + private final String CATEGORY_EDIT_ROUTE_TEMPLATE = "categories/%s/edit"; - @Autowired - private final CategoryService categoryService; + private final Grid grid = new Grid<>(Category.class, false); + + private final Button cancel = new Button("Cancel"); + private final Button save = new Button("Save"); private final BeanValidationBinder binder; + private Category category; + private final CategoryService categoryService; + private TextField name; + public CategoriesView(CategoryService categoryService) { this.categoryService = categoryService; + addClassNames("categories-view"); - FormLayout formLayout = new FormLayout(); - formLayout.add(categoryNameField, addButton); + // Create UI + SplitLayout splitLayout = new SplitLayout(); - categoryGrid.setColumns("id", "name"); - categoryGrid.setItems(categoryService.findAll()); - categoryGrid.asSingleSelect().addValueChangeListener(event -> { - if (event.getValue() != null) { - UI.getCurrent().navigate(String.format("/manage-categories/%d/edit", event.getValue().getId())); - } else { + createGridLayout(splitLayout); + createEditorLayout(splitLayout); + + add(splitLayout); + + // Configure Grid + grid.addColumn(Category::getName).setHeader("Name").setSortable(true); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + + grid.setItems(query -> categoryService.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(CATEGORY_EDIT_ROUTE_TEMPLATE, event.getValue().getId())); + else { clearForm(); UI.getCurrent().navigate(CategoriesView.class); } }); - removeButton.setEnabled(false); - removeButton.addClickListener(event -> removeCategory()); - - addButton.addClickListener(event -> addCategory()); - + // Configure Form binder = new BeanValidationBinder<>(Category.class); + + // Bind fields. This is where you'd define e.g. validation rules + binder.bindInstanceFields(this); - add(formLayout, categoryGrid, removeButton); + cancel.addClickListener(e -> { + clearForm(); + refreshGrid(); + }); + + save.addClickListener(e -> { + try { + if (this.category == null) { + this.category = new Category(); + } + binder.writeBean(this.category); + categoryService.update(this.category); + clearForm(); + refreshGrid(); + Notification.show("Data updated"); + UI.getCurrent().navigate(CategoriesView.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(Notification.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 @@ -77,27 +124,47 @@ public class CategoriesView extends VerticalLayout implements BeforeEnterObserve // when a row is selected but the data is no longer available, // refresh grid refreshGrid(); - event.forwardTo(ExpensesView.class); + event.forwardTo(CategoriesView.class); } } } - private void addCategory() { - Category newCategory = new Category(); - binder.writeBeanIfValid(newCategory); - categoryService.update(newCategory); - refreshGrid(); - Notification.show("Category added successfully"); + 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(); + name = new TextField("Name"); + formLayout.add(name); + editorDiv.add(formLayout); + createButtonLayout(editorLayoutDiv); + + splitLayout.addToSecondary(editorLayoutDiv); } - private void removeCategory() { - if (category != null) { - categoryService.delete(category); - clearForm(); - refreshGrid(); - Notification.show("Category removed successfully"); - UI.getCurrent().navigate(CategoriesView.class); - } + 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() { @@ -107,9 +174,6 @@ public class CategoriesView extends VerticalLayout implements BeforeEnterObserve private void populateForm(Category value) { this.category = value; binder.readBean(this.category); - } - private void refreshGrid() { - categoryGrid.setItems(categoryService.findAll()); } }