diff --git a/pom.xml b/pom.xml index d43a0b5..799c785 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,12 @@ 1.18.38 provided + + com.github.librepdf + openpdf + 1.3.30 + + diff --git a/src/main/java/de/assecutor/votianlt/model/Invoice.java b/src/main/java/de/assecutor/votianlt/model/Invoice.java new file mode 100644 index 0000000..6e493ba --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/model/Invoice.java @@ -0,0 +1,19 @@ +package de.assecutor.votianlt.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Invoice { + private String id; + private String kunde; + private LocalDate datum; + private double betrag; + private String beschreibung; +} + diff --git a/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java b/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java index eb92171..c7b5054 100644 --- a/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java +++ b/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java @@ -81,8 +81,8 @@ public final class MainLayout extends AppLayout { SideNavItem customers = new SideNavItem("Kunden", "customers", new Icon(VaadinIcon.USERS)); SideNavItem appUsers = new SideNavItem("App-Nutzer", "app-user", new Icon(VaadinIcon.USERS)); SideNavItem devices = new SideNavItem("Endgeräte", "app-devices", new Icon(VaadinIcon.MOBILE)); - SideNavItem invoices = new SideNavItem("Rechnungen", "5", new Icon(VaadinIcon.COG)); - SideNavItem statistics = new SideNavItem("Statistik", "6", new Icon(VaadinIcon.COG)); + SideNavItem invoices = new SideNavItem("Rechnungen", "invoices", new Icon(VaadinIcon.FILE_TEXT)); + SideNavItem statistics = new SideNavItem("Statistiken", "statistics", new Icon(VaadinIcon.BAR_CHART)); verwaltungContent.add(jobs, customers, appUsers, devices, invoices, statistics); verwaltungDetails.add(verwaltungContent); diff --git a/src/main/java/de/assecutor/votianlt/pages/view/InvoicesView.java b/src/main/java/de/assecutor/votianlt/pages/view/InvoicesView.java new file mode 100644 index 0000000..50d55b5 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/pages/view/InvoicesView.java @@ -0,0 +1,104 @@ +package de.assecutor.votianlt.pages.view; + +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.component.UI; +import de.assecutor.votianlt.model.Invoice; +import jakarta.annotation.security.RolesAllowed; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.time.LocalDate; +import java.util.List; + +import com.vaadin.flow.server.StreamResource; +import com.vaadin.flow.server.StreamRegistration; + +@PageTitle("Rechnungen") +@Route(value = "invoices", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class) +@RolesAllowed({"USER","ADMIN"}) +public class InvoicesView extends VerticalLayout { + + private final Grid invoiceGrid; + + public InvoicesView() { + setSizeFull(); + setPadding(true); + setSpacing(true); + setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); + setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER); + + H2 title = new H2("Rechnungen"); + add(title); + + invoiceGrid = new Grid<>(Invoice.class, false); + invoiceGrid.addColumn(Invoice::getId).setHeader("Rechnungsnummer").setAutoWidth(true); + invoiceGrid.addColumn(Invoice::getKunde).setHeader("Kunde").setAutoWidth(true); + invoiceGrid.addColumn(Invoice::getDatum).setHeader("Datum").setAutoWidth(true); + invoiceGrid.addColumn(Invoice::getBetrag).setHeader("Betrag").setAutoWidth(true); + invoiceGrid.addColumn(Invoice::getBeschreibung).setHeader("Beschreibung").setAutoWidth(true); + invoiceGrid.setSelectionMode(Grid.SelectionMode.SINGLE); + invoiceGrid.getStyle().set("cursor", "pointer"); + + // Testdaten + List testInvoices = List.of( + new Invoice("R-2024-001", "Max Mustermann", LocalDate.now().minusDays(2), 199.99, "Transport Hamburg-Berlin"), + new Invoice("R-2024-002", "Erika Musterfrau", LocalDate.now().minusDays(1), 299.49, "Express München-Köln"), + new Invoice("R-2024-003", "Hans Beispiel", LocalDate.now(), 149.00, "Standard Leipzig-Dresden") + ); + invoiceGrid.setItems(testInvoices); + + invoiceGrid.addItemClickListener(event -> { + Invoice invoice = event.getItem(); + if (invoice != null) { + downloadInvoicePdf(invoice); + } + }); + + add(invoiceGrid); + } + + private void downloadInvoicePdf(Invoice invoice) { + try { + // PDF generieren + byte[] pdfBytes = generatePdf(invoice); + StreamResource resource = new StreamResource( + invoice.getId() + ".pdf", + () -> new ByteArrayInputStream(pdfBytes) + ); + resource.setContentType("application/pdf"); + resource.setCacheTime(0); + + // Direkter Download über UI + StreamRegistration registration = UI.getCurrent().getSession().getResourceRegistry().registerResource(resource); + UI.getCurrent().getPage().open(registration.getResourceUri().toString()); + + } catch (Exception e) { + Notification.show("Fehler beim Erstellen der PDF: " + e.getMessage(), 5000, Notification.Position.MIDDLE); + } + } + + private byte[] generatePdf(Invoice invoice) { + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + // Einfache PDF mit iText (oder OpenPDF, falls iText nicht verfügbar) + com.lowagie.text.Document document = new com.lowagie.text.Document(); + com.lowagie.text.pdf.PdfWriter.getInstance(document, out); + document.open(); + document.add(new com.lowagie.text.Paragraph("Rechnung: " + invoice.getId())); + document.add(new com.lowagie.text.Paragraph("Kunde: " + invoice.getKunde())); + document.add(new com.lowagie.text.Paragraph("Datum: " + invoice.getDatum())); + document.add(new com.lowagie.text.Paragraph("Betrag: " + invoice.getBetrag() + " EUR")); + document.add(new com.lowagie.text.Paragraph("Beschreibung: " + invoice.getBeschreibung())); + document.close(); + return out.toByteArray(); + } catch (Exception e) { + throw new RuntimeException("PDF-Generierung fehlgeschlagen: " + e.getMessage(), e); + } + } +} + diff --git a/src/main/java/de/assecutor/votianlt/pages/view/StatisticsView.java b/src/main/java/de/assecutor/votianlt/pages/view/StatisticsView.java new file mode 100644 index 0000000..1e0de59 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/pages/view/StatisticsView.java @@ -0,0 +1,309 @@ +package de.assecutor.votianlt.pages.view; + +import com.vaadin.flow.component.Html; +import com.vaadin.flow.component.dependency.JavaScript; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.H3; +import com.vaadin.flow.component.html.Span; +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.router.PageTitle; +import com.vaadin.flow.router.Route; +import jakarta.annotation.security.RolesAllowed; + +@PageTitle("Statistiken") +@Route(value = "statistics", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class) +@RolesAllowed({"USER","ADMIN"}) +@JavaScript("https://cdn.jsdelivr.net/npm/chart.js") +public class StatisticsView extends VerticalLayout { + + public StatisticsView() { + setSizeFull(); + setPadding(true); + setSpacing(true); + + H2 title = new H2("Statistiken"); + add(title); + + // KPI Cards + HorizontalLayout kpiLayout = createKpiCards(); + add(kpiLayout); + + // Charts Layout + HorizontalLayout chartsLayout = new HorizontalLayout(); + chartsLayout.setWidthFull(); + chartsLayout.setHeight("400px"); + chartsLayout.setSpacing(true); + + // Aufträge pro Monat (Liniendiagramm) + Div monthlyOrdersChart = createMonthlyOrdersChart(); + monthlyOrdersChart.setWidth("50%"); + monthlyOrdersChart.setHeight("100%"); + chartsLayout.add(monthlyOrdersChart); + + // Aufträge nach Status (Kreisdiagramm) + Div statusChart = createStatusPieChart(); + statusChart.setWidth("50%"); + statusChart.setHeight("100%"); + chartsLayout.add(statusChart); + + add(chartsLayout); + + // Umsatz nach Kunden (Balkendiagramm) + VerticalLayout revenueContainer = new VerticalLayout(); + revenueContainer.setWidthFull(); + revenueContainer.setHeight("400px"); + revenueContainer.setPadding(false); + + Div revenueChart = createRevenueByCustomerChart(); + revenueChart.setSizeFull(); + revenueContainer.add(revenueChart); + + add(revenueContainer); + } + + private HorizontalLayout createKpiCards() { + HorizontalLayout kpiLayout = new HorizontalLayout(); + kpiLayout.setWidthFull(); + kpiLayout.setSpacing(true); + kpiLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.EVENLY); + + // Gesamtaufträge + Div totalOrdersCard = createKpiCard("Gesamtaufträge", "247", "success"); + + // Offene Aufträge + Div openOrdersCard = createKpiCard("Offene Aufträge", "34", "warning"); + + // Umsatz diesen Monat + Div revenueCard = createKpiCard("Umsatz (Monat)", "€ 24.500", "primary"); + + // Neue Kunden + Div newCustomersCard = createKpiCard("Neue Kunden", "12", "success"); + + kpiLayout.add(totalOrdersCard, openOrdersCard, revenueCard, newCustomersCard); + return kpiLayout; + } + + private Div createKpiCard(String title, String value, String theme) { + Div card = new Div(); + card.addClassName("kpi-card"); + card.getStyle() + .set("background", "var(--lumo-base-color)") + .set("border", "1px solid var(--lumo-contrast-10pct)") + .set("border-radius", "var(--lumo-border-radius-m)") + .set("padding", "var(--lumo-space-m)") + .set("text-align", "center") + .set("box-shadow", "var(--lumo-box-shadow-xs)") + .set("min-width", "150px"); + + H3 titleElement = new H3(title); + titleElement.getStyle().set("margin", "0 0 var(--lumo-space-s) 0").set("font-size", "var(--lumo-font-size-s)"); + + Span valueElement = new Span(value); + valueElement.getStyle() + .set("font-size", "var(--lumo-font-size-xl)") + .set("font-weight", "bold") + .set("color", getThemeColor(theme)); + + card.add(titleElement, valueElement); + return card; + } + + private String getThemeColor(String theme) { + return switch (theme) { + case "success" -> "var(--lumo-success-color)"; + case "warning" -> "var(--lumo-warning-color)"; + case "error" -> "var(--lumo-error-color)"; + default -> "var(--lumo-primary-color)"; + }; + } + + private Div createMonthlyOrdersChart() { + Div chartContainer = new Div(); + chartContainer.setId("monthlyOrdersChart"); + + String canvasHtml = ""; + Html canvas = new Html(canvasHtml); + chartContainer.add(canvas); + + String script = """ + + """; + + Html scriptElement = new Html(script); + chartContainer.add(scriptElement); + + return chartContainer; + } + + private Div createStatusPieChart() { + Div chartContainer = new Div(); + chartContainer.setId("statusPieChart"); + + String canvasHtml = ""; + Html canvas = new Html(canvasHtml); + chartContainer.add(canvas); + + String script = """ + + """; + + Html scriptElement = new Html(script); + chartContainer.add(scriptElement); + + return chartContainer; + } + + private Div createRevenueByCustomerChart() { + Div chartContainer = new Div(); + chartContainer.setId("revenueByCustomerChart"); + + String canvasHtml = ""; + Html canvas = new Html(canvasHtml); + chartContainer.add(canvas); + + String script = """ + + """; + + Html scriptElement = new Html(script); + chartContainer.add(scriptElement); + + return chartContainer; + } +}