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.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@ -24,4 +26,17 @@ public class Category {
|
||||||
|
|
||||||
@Column(name = "userId", nullable = false)
|
@Column(name = "userId", nullable = false)
|
||||||
private Long userId;
|
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;
|
package com.application.munera.services;
|
||||||
|
|
||||||
import com.application.munera.data.BadgeMessage;
|
import com.application.munera.data.BadgeMessage;
|
||||||
|
import com.application.munera.data.Category;
|
||||||
import com.application.munera.data.Expense;
|
import com.application.munera.data.Expense;
|
||||||
import com.application.munera.data.ExpenseType;
|
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.html.Span;
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
@ -47,8 +49,8 @@ public class ViewsService {
|
||||||
return badge;
|
return badge;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyFilter(TextField nameFilter, Long userId, PaginatedGrid<Expense, Objects> grid) {
|
public void applyNameFilter(TextField nameFilter, Long userId, PaginatedGrid<Expense, Objects> grid) {
|
||||||
String filterValue = nameFilter.getValue().trim();
|
final var filterValue = nameFilter.getValue().trim();
|
||||||
List<Expense> filteredExpenses;
|
List<Expense> filteredExpenses;
|
||||||
if (filterValue.isEmpty()) filteredExpenses = expenseService.findAllOrderByDateDescending(userId); // If the filter is empty, return all expenses
|
if (filterValue.isEmpty()) filteredExpenses = expenseService.findAllOrderByDateDescending(userId); // If the filter is empty, return all expenses
|
||||||
else {
|
else {
|
||||||
|
@ -60,6 +62,20 @@ public class ViewsService {
|
||||||
grid.setItems(filteredExpenses);
|
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) {
|
private BadgeMessage determineBadgeMessage(ExpenseType type, boolean isPaid) {
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
case CREDIT -> isPaid ? BadgeMessage.PAID_TO_ME : BadgeMessage.OWED_TO_ME;
|
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.button.ButtonVariant;
|
||||||
import com.vaadin.flow.component.checkbox.Checkbox;
|
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||||
import com.vaadin.flow.component.combobox.ComboBox;
|
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.datepicker.DatePicker;
|
||||||
import com.vaadin.flow.component.dependency.Uses;
|
import com.vaadin.flow.component.dependency.Uses;
|
||||||
import com.vaadin.flow.component.formlayout.FormLayout;
|
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;
|
||||||
import com.vaadin.flow.component.notification.Notification.Position;
|
import com.vaadin.flow.component.notification.Notification.Position;
|
||||||
import com.vaadin.flow.component.notification.NotificationVariant;
|
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.HorizontalLayout;
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.component.splitlayout.SplitLayout;
|
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 PaginatedGrid<Expense, Objects> grid = new PaginatedGrid<>();
|
||||||
private final TextField nameFilter = new TextField();
|
private final TextField nameFilter = new TextField();
|
||||||
|
private final MultiSelectComboBox<Category> categoryFilter = new MultiSelectComboBox<>();
|
||||||
private final Button cancel = new Button("Cancel");
|
private final Button cancel = new Button("Cancel");
|
||||||
private final Button save = new Button("Save");
|
private final Button save = new Button("Save");
|
||||||
private final Button delete = new Button("Delete");
|
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.setPageSize(22); // setting page size
|
||||||
grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
|
grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
|
||||||
|
|
||||||
// Filtering setup
|
// Filtering setup - Name
|
||||||
nameFilter.setPlaceholder("Filter by Name...");
|
nameFilter.setPlaceholder("Filter by Name...");
|
||||||
nameFilter.setClearButtonVisible(true);
|
nameFilter.setClearButtonVisible(true);
|
||||||
nameFilter.setValueChangeMode(ValueChangeMode.LAZY);
|
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();
|
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);
|
splitLayout.addToPrimary(layout);
|
||||||
|
|
||||||
// when a row is selected or deselected, populate form
|
// when a row is selected or deselected, populate form
|
||||||
grid.asSingleSelect().addValueChangeListener(event -> {
|
grid.asSingleSelect().addValueChangeListener(event -> {
|
||||||
if (event.getValue() != null) UI.getCurrent().navigate(String.format(EXPENSE_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
|
if (event.getValue() != null) {
|
||||||
else {
|
UI.getCurrent().navigate(String.format(EXPENSE_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
|
||||||
|
} else {
|
||||||
clearForm();
|
clearForm();
|
||||||
UI.getCurrent().navigate(ExpensesView.class);
|
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 = new BeanValidationBinder<>(Expense.class);
|
||||||
binder.bindInstanceFields(this);
|
binder.bindInstanceFields(this);
|
||||||
binder.forField(name)
|
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 -> {
|
cancel.addClickListener(e -> {
|
||||||
clearForm();
|
clearForm();
|
||||||
refreshGrid();
|
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();
|
initializeComboBoxes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,8 +239,6 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
|
||||||
Notification.show(
|
Notification.show(
|
||||||
String.format("The requested expense was not found, ID = %s", expenseId.get()), 3000,
|
String.format("The requested expense was not found, ID = %s", expenseId.get()), 3000,
|
||||||
Notification.Position.BOTTOM_START);
|
Notification.Position.BOTTOM_START);
|
||||||
// when a row is selected but the data is no longer available,
|
|
||||||
// refresh grid
|
|
||||||
refreshGrid();
|
refreshGrid();
|
||||||
event.forwardTo(ExpensesView.class);
|
event.forwardTo(ExpensesView.class);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue