feat: expense category filtering

This commit is contained in:
effe 2024-09-15 16:55:28 -04:00
parent c70ea1c76c
commit 56e5928e4b
3 changed files with 55 additions and 29 deletions

View file

@ -5,6 +5,8 @@ import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import java.util.Objects;
@Entity
@Getter
@Setter
@ -24,4 +26,17 @@ public class Category {
@Column(name = "userId", nullable = false)
private Long userId;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Category category = (Category) o;
return id != null && id.equals(category.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View file

@ -1,8 +1,10 @@
package com.application.munera.services;
import com.application.munera.data.BadgeMessage;
import com.application.munera.data.Category;
import com.application.munera.data.Expense;
import com.application.munera.data.ExpenseType;
import com.vaadin.flow.component.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.textfield.TextField;
import org.springframework.stereotype.Service;
@ -47,8 +49,8 @@ public class ViewsService {
return badge;
}
public void applyFilter(TextField nameFilter, Long userId, PaginatedGrid<Expense, Objects> grid) {
String filterValue = nameFilter.getValue().trim();
public void applyNameFilter(TextField nameFilter, Long userId, PaginatedGrid<Expense, Objects> grid) {
final var filterValue = nameFilter.getValue().trim();
List<Expense> filteredExpenses;
if (filterValue.isEmpty()) filteredExpenses = expenseService.findAllOrderByDateDescending(userId); // If the filter is empty, return all expenses
else {
@ -60,6 +62,20 @@ public class ViewsService {
grid.setItems(filteredExpenses);
}
public void applyCategoryFilter(MultiSelectComboBox<Category> categoryFilter, Long userId, PaginatedGrid<Expense, Objects> grid) {
final var selectedCategories = categoryFilter.getValue();
List<Expense> filteredExpenses;
if (selectedCategories.isEmpty()) filteredExpenses = expenseService.findAllOrderByDateDescending(userId); // If no categories are selected, return all expenses
else {
// Apply the filter by selected categories
filteredExpenses = expenseService.findAllOrderByDateDescending(userId)
.stream()
.filter(expense1 -> selectedCategories.contains(expense1.getCategory()))
.toList();
}
grid.setItems(filteredExpenses);
}
private BadgeMessage determineBadgeMessage(ExpenseType type, boolean isPaid) {
return switch (type) {
case CREDIT -> isPaid ? BadgeMessage.PAID_TO_ME : BadgeMessage.OWED_TO_ME;

View file

@ -15,6 +15,7 @@ import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
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.dependency.Uses;
import com.vaadin.flow.component.formlayout.FormLayout;
@ -24,6 +25,7 @@ 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.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
@ -56,6 +58,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
private final PaginatedGrid<Expense, Objects> grid = new PaginatedGrid<>();
private final TextField nameFilter = new TextField();
private final MultiSelectComboBox<Category> categoryFilter = new MultiSelectComboBox<>();
private final Button cancel = new Button("Cancel");
private final Button save = new Button("Save");
private final Button delete = new Button("Delete");
@ -114,27 +117,38 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
grid.setPageSize(22); // setting page size
grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
// Filtering setup
// Filtering setup - Name
nameFilter.setPlaceholder("Filter by Name...");
nameFilter.setClearButtonVisible(true);
nameFilter.setValueChangeMode(ValueChangeMode.LAZY);
nameFilter.addValueChangeListener(e -> this.viewsService.applyFilter(nameFilter, userId, grid));
nameFilter.addValueChangeListener(e -> this.viewsService.applyNameFilter(nameFilter, userId, grid));
// Add nameFilter field to layout (above the grid)
// Filtering setup - Category
categoryFilter.setPlaceholder("Filter by Category...");
categoryFilter.setClearButtonVisible(true);
categoryFilter.setItems(categoryService.findAllByUserId(userId));
categoryFilter.setItemLabelGenerator(Category::getName);
categoryFilter.addValueChangeListener(e -> this.viewsService.applyCategoryFilter(categoryFilter, userId, grid));
// Add filter fields to layout (above the grid)
VerticalLayout layout = new VerticalLayout();
layout.add(nameFilter, grid);
HorizontalLayout filterLayout = new HorizontalLayout(nameFilter, categoryFilter);
filterLayout.setSpacing(true);
filterLayout.setAlignItems(FlexComponent.Alignment.BASELINE);
layout.add(filterLayout, grid);
splitLayout.addToPrimary(layout);
// 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 {
if (event.getValue() != null) {
UI.getCurrent().navigate(String.format(EXPENSE_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
} else {
clearForm();
UI.getCurrent().navigate(ExpensesView.class);
}
});
// Bind fields. This is where you'd define e.g. validation rules
// Configure Form
binder = new BeanValidationBinder<>(Expense.class);
binder.bindInstanceFields(this);
binder.forField(name)
@ -171,22 +185,6 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
}
});
// TODO:// Event listeners that will remove the selected creditors from the debtors list and vice versa
// // Done so that the user cant create an expense with the same person as creditor and debtor
// payer.addValueChangeListener(event -> {
// Person selectedDebtors = event.getValue();
// final var creditorsSet = new HashSet<>(personService.findAllWithoutUser());
// creditorsSet.removeIf(creditorsSet::contains);
// payer.setItems(creditorsSet);
// });
//
// beneficiary.addValueChangeListener(event -> {
// Person selectedCreditors = event.getValue();
// final var debtorsSet = new HashSet<>(personService.findAllWithoutUser());
// debtorsSet.removeIf(debtorsSet::contains);
// beneficiary.setItems(debtorsSet);
// });
cancel.addClickListener(e -> {
clearForm();
refreshGrid();
@ -227,7 +225,6 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
}
});
// Initialize ComboBox with the logged-in user's Person entity as default
initializeComboBoxes();
}
@ -242,8 +239,6 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
Notification.show(
String.format("The requested expense was not found, ID = %s", expenseId.get()), 3000,
Notification.Position.BOTTOM_START);
// when a row is selected but the data is no longer available,
// refresh grid
refreshGrid();
event.forwardTo(ExpensesView.class);
}