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;
+ }
+}