Erweiterungen
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -82,6 +82,12 @@
|
|||||||
<version>1.18.38</version>
|
<version>1.18.38</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.librepdf</groupId>
|
||||||
|
<artifactId>openpdf</artifactId>
|
||||||
|
<version>1.3.30</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
19
src/main/java/de/assecutor/votianlt/model/Invoice.java
Normal file
19
src/main/java/de/assecutor/votianlt/model/Invoice.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -81,8 +81,8 @@ public final class MainLayout extends AppLayout {
|
|||||||
SideNavItem customers = new SideNavItem("Kunden", "customers", new Icon(VaadinIcon.USERS));
|
SideNavItem customers = new SideNavItem("Kunden", "customers", new Icon(VaadinIcon.USERS));
|
||||||
SideNavItem appUsers = new SideNavItem("App-Nutzer", "app-user", 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 devices = new SideNavItem("Endgeräte", "app-devices", new Icon(VaadinIcon.MOBILE));
|
||||||
SideNavItem invoices = new SideNavItem("Rechnungen", "5", new Icon(VaadinIcon.COG));
|
SideNavItem invoices = new SideNavItem("Rechnungen", "invoices", new Icon(VaadinIcon.FILE_TEXT));
|
||||||
SideNavItem statistics = new SideNavItem("Statistik", "6", new Icon(VaadinIcon.COG));
|
SideNavItem statistics = new SideNavItem("Statistiken", "statistics", new Icon(VaadinIcon.BAR_CHART));
|
||||||
|
|
||||||
verwaltungContent.add(jobs, customers, appUsers, devices, invoices, statistics);
|
verwaltungContent.add(jobs, customers, appUsers, devices, invoices, statistics);
|
||||||
verwaltungDetails.add(verwaltungContent);
|
verwaltungDetails.add(verwaltungContent);
|
||||||
|
|||||||
104
src/main/java/de/assecutor/votianlt/pages/view/InvoicesView.java
Normal file
104
src/main/java/de/assecutor/votianlt/pages/view/InvoicesView.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user