feat: export to CSV

This commit is contained in:
filippo-ferrari 2024-09-12 11:32:47 +02:00
parent ac67892cae
commit 05ec0fc8da
3 changed files with 59 additions and 55 deletions

View file

@ -0,0 +1,39 @@
package com.application.munera.services;
import com.application.munera.data.Expense;
import com.vaadin.flow.server.StreamResource;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Service
public class CSVService {
public StreamResource createCSVResource(List<Expense> expenses) {
return new StreamResource("expenses.csv", () -> {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try (OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT.withHeader("Name", "Cost", "Category", "Date", "Payment date"))) {
for (Expense expense : expenses) {
csvPrinter.printRecord(
expense.getName(),
expense.getCost(),
expense.getCategory() != null ? expense.getCategory().getName() : "",
expense.getDate(),
expense.getPaymentDate() != null ? expense.getPaymentDate().toLocalDate() : "Unpaid"
);
}
} catch (Exception e) {
e.printStackTrace();
}
return new ByteArrayInputStream(stream.toByteArray());
});
}
}

View file

@ -1,5 +1,7 @@
package com.application.munera.views; package com.application.munera.views;
import com.application.munera.services.CSVService;
import com.application.munera.services.ExpenseService;
import com.application.munera.views.categories.CategoriesView; import com.application.munera.views.categories.CategoriesView;
import com.application.munera.views.dashboard.DashboardView; import com.application.munera.views.dashboard.DashboardView;
import com.application.munera.views.events.EventsView; import com.application.munera.views.events.EventsView;
@ -9,16 +11,14 @@ import com.application.munera.views.settings.SettingsView;
import com.vaadin.flow.component.applayout.AppLayout; import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle; import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Footer; import com.vaadin.flow.component.html.*;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Header;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.FlexComponent; 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.Scroller; import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.sidenav.SideNav; import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem; import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.server.StreamResource;
import com.vaadin.flow.spring.security.AuthenticationContext; import com.vaadin.flow.spring.security.AuthenticationContext;
import com.vaadin.flow.theme.lumo.LumoUtility; import com.vaadin.flow.theme.lumo.LumoUtility;
import org.vaadin.lineawesome.LineAwesomeIcon; import org.vaadin.lineawesome.LineAwesomeIcon;
@ -30,9 +30,13 @@ public class MainLayout extends AppLayout {
private H1 viewTitle; private H1 viewTitle;
private final transient AuthenticationContext authContext; private final transient AuthenticationContext authContext;
private final CSVService csvService;
private final ExpenseService expenseService;
public MainLayout(AuthenticationContext authContext) { public MainLayout(AuthenticationContext authContext, CSVService csvService, ExpenseService expenseService) {
this.authContext = authContext; this.authContext = authContext;
this.csvService = csvService;
this.expenseService = expenseService;
setPrimarySection(Section.DRAWER); setPrimarySection(Section.DRAWER);
addDrawerContent(); addDrawerContent();
addHeaderContent(); addHeaderContent();
@ -64,8 +68,18 @@ public class MainLayout extends AppLayout {
Button logout = new Button("Logout", click -> this.authContext.logout()); Button logout = new Button("Logout", click -> this.authContext.logout());
logout.getStyle().set("padding", "10px"); // add padding to the logout button logout.getStyle().set("padding", "10px"); // add padding to the logout button
// Create the Export to CSV button
Button exportToCSVButton = new Button("Export to CSV");
exportToCSVButton.addClickListener(event -> {
// Call a method in ExpensesView to handle CSV creation
StreamResource resource = this.csvService.createCSVResource(this.expenseService.findAll());
Anchor downloadLink = new Anchor(resource, "Download CSV");
downloadLink.getElement().setAttribute("download", true);
addToNavbar(downloadLink); // Add the download link to the navbar
});
// Create the header layout and add all elements // Create the header layout and add all elements
HorizontalLayout header = new HorizontalLayout(userInfoLayout, logout); HorizontalLayout header = new HorizontalLayout(exportToCSVButton, userInfoLayout, logout);
header.setWidthFull(); // Make the header take the full width header.setWidthFull(); // Make the header take the full width
header.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER); header.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
header.setJustifyContentMode(FlexComponent.JustifyContentMode.END); // Align items to the right header.setJustifyContentMode(FlexComponent.JustifyContentMode.END); // Align items to the right

View file

@ -13,7 +13,6 @@ 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;
import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.notification.Notification;
@ -28,22 +27,13 @@ import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.data.converter.StringToBigDecimalConverter; import com.vaadin.flow.data.converter.StringToBigDecimalConverter;
import com.vaadin.flow.data.renderer.ComponentRenderer; import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.*; import com.vaadin.flow.router.*;
import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVFormat;
import org.vaadin.klaudeta.PaginatedGrid; import org.vaadin.klaudeta.PaginatedGrid;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@ -62,7 +52,6 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
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");
private final Button exportToCSVButton = new Button("Export to CSV");
private final BeanValidationBinder<Expense> binder; private final BeanValidationBinder<Expense> binder;
private Expense expense; private Expense expense;
@ -120,22 +109,6 @@ 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);
// Export to CSV button
exportToCSVButton.addClickListener(CSVClickEvent -> {
StreamResource resource = createCSVResource(expenseService.findAll());
Anchor downloadLink = new Anchor(resource, "Download CSV");
downloadLink.getElement().setAttribute("download", true);
// Remove old download links if any
this.getChildren().filter(Anchor.class::isInstance)
.forEach(child -> remove((Anchor) child));
// Add the new download link
add(downloadLink);
});
add(exportToCSVButton);
// 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) UI.getCurrent().navigate(String.format(EXPENSE_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
@ -356,26 +329,4 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
} }
} }
} }
private StreamResource createCSVResource(List<Expense> expenses) {
return new StreamResource("expenses.csv", () -> {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try (OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT.withHeader("Name", "Cost", "Category", "Date", "Payment date"))) {
for (Expense expense : expenses) {
csvPrinter.printRecord(
expense.getName(),
expense.getCost(),
expense.getCategory() != null ? expense.getCategory().getName() : "",
expense.getDate(),
expense.getPaymentDate() != null ? expense.getPaymentDate().toLocalDate() : "Unpaid"
);
}
} catch (Exception e) {
e.printStackTrace();
}
return new ByteArrayInputStream(stream.toByteArray());
});
}
} }