feat: expense category filtering
This commit is contained in:
parent
c70ea1c76c
commit
56e5928e4b
3 changed files with 55 additions and 29 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
@ -86,7 +89,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
|
|||
this.expenseService = expenseService;
|
||||
this.categoryService = categoryService;
|
||||
this.viewsService = viewsService;
|
||||
this.userService = userService;
|
||||
this.userService = userService;
|
||||
this.personFacade = personFacade;
|
||||
this.userId = this.userService.getLoggedInUser().getId();
|
||||
addClassNames("expenses-view");
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue