Compare commits
No commits in common. "0fc5b07f5edffef49426d3f8b7d1792a0e49cfbc" and "751aa257b14332c3715f673e93734ad67201cc3b" have entirely different histories.
0fc5b07f5e
...
751aa257b1
7 changed files with 35 additions and 82 deletions
|
@ -61,16 +61,15 @@ Munera is a companion for managing expenses efficiently and effortlessly, whethe
|
|||
|
||||
6. **Misc**
|
||||
- PeriodUnit and Interval need to be implemented with a scheduler
|
||||
- Graphs could use more work, maybe some filtering?
|
||||
- Graphs could use more work
|
||||
- More validation in form
|
||||
- ~~Login page~~
|
||||
- email setup
|
||||
- Login page
|
||||
- Migration tool for DB changes in prod
|
||||
|
||||
## Known Issues
|
||||
|
||||
- PeopleVIew dosent refresh after an edit operation anymore
|
||||
- Form still needs more validation when empty, some entities can be created with all null values, even the ones that have constraints throw SQL errors, they need to be gracefully handled.
|
||||
- Errors need to be caught and handled
|
||||
- Graphs still need ~~a lot of~~ **some** improvements
|
||||
- PeriodUnit and Interval need to be implemented with a scheduler
|
||||
- ~~ExpenseView dosent refresh after an edit operation anymore~~
|
|
@ -1,10 +0,0 @@
|
|||
package com.application.munera.data;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class AbstractEntity {
|
||||
private Long id;
|
||||
}
|
|
@ -13,7 +13,7 @@ import java.util.Set;
|
|||
@Getter
|
||||
@Setter
|
||||
@Table(name = "expenses")
|
||||
public class Expense extends AbstractEntity {
|
||||
public class Expense {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
|
|
@ -14,7 +14,7 @@ import java.util.Set;
|
|||
@Getter
|
||||
@Setter
|
||||
@Table(name = "people")
|
||||
public class Person extends AbstractEntity {
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
|
|
@ -11,7 +11,6 @@ import org.springframework.stereotype.Service;
|
|||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
public class ExpenseService {
|
||||
|
@ -41,13 +40,6 @@ public class ExpenseService {
|
|||
public Collection<Expense> findUnpaidCreditByUser(final Person person) {
|
||||
return repository.findUnpaidCreditorsExpensesByPersonId(person.getId());
|
||||
}
|
||||
|
||||
public List<Expense> findExpenseByUser(final Person person) {
|
||||
final var credits = this.findCreditByUser(person);
|
||||
final var debits = this.findDebtByUser(person);
|
||||
return Stream.concat(credits.stream(), debits.stream()).toList();
|
||||
}
|
||||
|
||||
public List<Expense> findAll() {return repository.findAll();}
|
||||
|
||||
public void update(Expense entity) {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package com.application.munera.views.people;
|
||||
|
||||
import com.application.munera.data.Expense;
|
||||
import com.application.munera.data.Person;
|
||||
import com.application.munera.services.ExpenseService;
|
||||
import com.application.munera.services.PersonService;
|
||||
import com.application.munera.views.MainLayout;
|
||||
import com.vaadin.flow.component.UI;
|
||||
|
@ -10,6 +8,7 @@ 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.html.Span;
|
||||
|
@ -21,7 +20,6 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
|||
import com.vaadin.flow.component.splitlayout.SplitLayout;
|
||||
import com.vaadin.flow.component.textfield.EmailField;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.component.treegrid.TreeGrid;
|
||||
import com.vaadin.flow.data.binder.BeanValidationBinder;
|
||||
import com.vaadin.flow.data.binder.ValidationException;
|
||||
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
||||
|
@ -29,11 +27,12 @@ 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 com.vaadin.flow.spring.data.VaadinSpringDataHelpers;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@PageTitle("People")
|
||||
|
@ -45,7 +44,7 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
private static final String PERSON_ID = "personID";
|
||||
private static final String PERSON_EDIT_ROUTE_TEMPLATE = "people/%s/edit";
|
||||
|
||||
private final TreeGrid<Object> grid = new TreeGrid<>();
|
||||
private final Grid<Person> grid = new Grid<>(Person.class, false);
|
||||
|
||||
private final Button cancel = new Button("Cancel");
|
||||
private final Button save = new Button("Save");
|
||||
|
@ -55,14 +54,12 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
|
||||
private Person person;
|
||||
private final PersonService personService;
|
||||
private final ExpenseService expenseService;
|
||||
private TextField firstName;
|
||||
private TextField lastName;
|
||||
private EmailField email;
|
||||
|
||||
public PeopleView(PersonService personService, ExpenseService expenseService) {
|
||||
public PeopleView(PersonService personService) {
|
||||
this.personService = personService;
|
||||
this.expenseService = expenseService;
|
||||
addClassNames("expenses-view");
|
||||
|
||||
// Create UI
|
||||
|
@ -73,22 +70,33 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
|
||||
add(splitLayout);
|
||||
|
||||
grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
|
||||
grid.addHierarchyColumn(this::getNodeName).setHeader("Name");
|
||||
grid.addColumn(this::getNodeCost).setHeader("Total Expenses Value").setSortable(true);
|
||||
// Configure Grid
|
||||
grid.addColumn(Person::getFirstName).setHeader("First Name").setSortable(true);
|
||||
grid.addColumn(Person::getLastName).setHeader("Last Name").setSortable(true);
|
||||
grid.addColumn(Person::getEmail).setHeader("Email").setSortable(true);
|
||||
grid.addColumn(personService::calculateDebt).setHeader("Debt").setSortable(true);
|
||||
grid.addColumn(personService::calculateCredit).setHeader("Credit").setSortable(true);
|
||||
grid.addColumn(personService::calculateNetBalance).setHeader("Total Expenses value").setSortable(true);
|
||||
grid.addColumn(new ComponentRenderer<>(persona -> {
|
||||
if (persona instanceof Person) return createPersonBadge(personService.calculateNetBalance((Person) persona));
|
||||
else return createExpenseBadge(((Expense) persona).getIsResolved());
|
||||
})).setHeader("Balance Status");
|
||||
final var netBalance = personService.calculateNetBalance(persona);
|
||||
return createBadge(netBalance);
|
||||
})).setHeader("Balance Status").setSortable(true);
|
||||
|
||||
List<Person> people = (List<Person>) personService.findAll();
|
||||
|
||||
this.setGridData(people);
|
||||
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 -> {
|
||||
Object selectedItem = event.getValue();
|
||||
if (selectedItem instanceof Person selectedPerson) UI.getCurrent().navigate(String.format(PERSON_EDIT_ROUTE_TEMPLATE, selectedPerson.getId()));
|
||||
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
|
||||
|
@ -141,25 +149,14 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
});
|
||||
}
|
||||
|
||||
private String getNodeName(Object node) {
|
||||
if (node instanceof Person) return ((Person) node).getFirstName() + " " + ((Person) node).getLastName();
|
||||
else if (node instanceof Expense) return ((Expense) node).getName();
|
||||
return "";
|
||||
}
|
||||
|
||||
private String getNodeCost(Object node) {
|
||||
if (node instanceof Person) return this.personService.calculateNetBalance((Person) node).toString() + " €";
|
||||
else if (node instanceof Expense) return ((Expense) node).getCost().toString() + " €";
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeEnter(BeforeEnterEvent event) {
|
||||
Optional<Long> personId = event.getRouteParameters().get(PERSON_ID).map(Long::parseLong);
|
||||
if (personId.isPresent()) {
|
||||
Optional<Person> personFromBackend = personService.get(personId.get());
|
||||
if (personFromBackend.isPresent()) populateForm(personFromBackend.get());
|
||||
else {
|
||||
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);
|
||||
|
@ -226,7 +223,7 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
|
||||
}
|
||||
|
||||
private Span createPersonBadge(BigDecimal netBalance) {
|
||||
private Span createBadge(BigDecimal netBalance) {
|
||||
Span badge = new Span();
|
||||
if (netBalance.compareTo(BigDecimal.ZERO) < 0) {
|
||||
badge.setText("Credit");
|
||||
|
@ -240,29 +237,4 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
}
|
||||
return badge;
|
||||
}
|
||||
|
||||
private Span createExpenseBadge(Boolean isExpenseResolved) {
|
||||
Span badge = new Span();
|
||||
if (Boolean.TRUE.equals(isExpenseResolved)) {
|
||||
badge.setText("Resolved");
|
||||
badge.getElement().getThemeList().add("badge success");
|
||||
} else {
|
||||
badge.setText("To be Resolved");
|
||||
badge.getElement().getThemeList().add("badge error");
|
||||
}
|
||||
return badge;
|
||||
}
|
||||
|
||||
public void setGridData(List<Person> people) {
|
||||
for (Person person : people) {
|
||||
// Add the person as a root item
|
||||
grid.getTreeData().addItem(null, person);
|
||||
|
||||
// Fetch expenses for the current person
|
||||
List<Expense> expenses = expenseService.findExpenseByUser(person);
|
||||
|
||||
// Add each expense as a child item under the person
|
||||
for (Expense expense : expenses) grid.getTreeData().addItem(person, expense);
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 77 KiB |
Loading…
Reference in a new issue