Erweiterungen

This commit is contained in:
2025-08-15 10:12:20 +02:00
parent 98cde1e762
commit 5826bf33db
5 changed files with 440 additions and 2 deletions

View File

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

View File

@@ -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);

View File

@@ -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<Invoice> 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<Invoice> 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);
}
}
}

View File

@@ -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 = "<canvas id='monthlyOrdersCanvas' style='width: 100%; height: 100%;'></canvas>";
Html canvas = new Html(canvasHtml);
chartContainer.add(canvas);
String script = """
<script>
setTimeout(function() {
const ctx = document.getElementById('monthlyOrdersCanvas');
if (ctx) {
new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
datasets: [{
label: '2024',
data: [15, 18, 22, 28, 32, 35, 42, 38, 41, 35, 28, 25],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.1
}, {
label: '2023',
data: [12, 15, 18, 25, 28, 30, 35, 32, 36, 30, 25, 22],
borderColor: 'rgb(135, 206, 235)',
backgroundColor: 'rgba(135, 206, 235, 0.2)',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Aufträge pro Monat'
}
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Anzahl Aufträge'
}
}
}
}
});
}
}, 100);
</script>
""";
Html scriptElement = new Html(script);
chartContainer.add(scriptElement);
return chartContainer;
}
private Div createStatusPieChart() {
Div chartContainer = new Div();
chartContainer.setId("statusPieChart");
String canvasHtml = "<canvas id='statusPieCanvas' style='width: 100%; height: 100%;'></canvas>";
Html canvas = new Html(canvasHtml);
chartContainer.add(canvas);
String script = """
<script>
setTimeout(function() {
const ctx = document.getElementById('statusPieCanvas');
if (ctx) {
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Abgeschlossen', 'In Bearbeitung', 'Geplant', 'Storniert'],
datasets: [{
data: [156, 34, 28, 12],
backgroundColor: [
'rgba(54, 162, 235, 0.8)',
'rgba(255, 206, 86, 0.8)',
'rgba(75, 192, 192, 0.8)',
'rgba(255, 99, 132, 0.8)'
],
borderColor: [
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(255, 99, 132, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Aufträge nach Status'
},
legend: {
position: 'right'
}
}
}
});
}
}, 100);
</script>
""";
Html scriptElement = new Html(script);
chartContainer.add(scriptElement);
return chartContainer;
}
private Div createRevenueByCustomerChart() {
Div chartContainer = new Div();
chartContainer.setId("revenueByCustomerChart");
String canvasHtml = "<canvas id='revenueByCustomerCanvas' style='width: 100%; height: 100%;'></canvas>";
Html canvas = new Html(canvasHtml);
chartContainer.add(canvas);
String script = """
<script>
setTimeout(function() {
const ctx = document.getElementById('revenueByCustomerCanvas');
if (ctx) {
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Firma A GmbH', 'Logistics B', 'Transport C', 'Spediteur D', 'Handel E',
'Industrie F', 'Service G', 'Vertrieb H', 'Export I', 'Import J'],
datasets: [{
label: 'Umsatz (€)',
data: [8500, 7200, 6800, 5900, 5400, 4800, 4200, 3900, 3500, 3100],
backgroundColor: 'rgba(75, 192, 192, 0.8)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Top 10 Kunden nach Umsatz'
},
legend: {
display: false
}
},
scales: {
x: {
ticks: {
maxRotation: 45,
minRotation: 45
}
},
y: {
beginAtZero: true,
title: {
display: true,
text: 'Umsatz (€)'
}
}
}
}
});
}
}, 100);
</script>
""";
Html scriptElement = new Html(script);
chartContainer.add(scriptElement);
return chartContainer;
}
}