Erweiterungen

This commit is contained in:
2025-08-22 11:03:50 +02:00
parent 545fce0a50
commit 9d8bf9487a

View File

@@ -7,6 +7,8 @@ import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.IFrame;
import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.notification.Notification;
@@ -14,8 +16,14 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.EmailField; import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.textfield.TextField;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.tabs.TabSheet;
import com.vaadin.flow.data.binder.Binder; import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.validator.EmailValidator; import com.vaadin.flow.data.validator.EmailValidator;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import de.assecutor.votianlt.model.User; import de.assecutor.votianlt.model.User;
@@ -27,14 +35,20 @@ import jakarta.annotation.security.RolesAllowed;
@Route(value = "edit-profile", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class) @Route(value = "edit-profile", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@RolesAllowed({"USER","ADMIN"}) @RolesAllowed({"USER","ADMIN"})
public class EditProfileView extends HorizontalLayout { public class EditProfileView extends HorizontalLayout {
private final UserService userService; private final TextField prefixField;
private final SecurityService securityService; private final TextField ustIdField;
private final TextField taxNumberField;
private final TextField bankNameField;
private final TextField ibanField;
private final TextField taxRateField;
private final TextArea introTextArea;
private final TextArea termsTextArea;
private final IFrame pdfFrame;
private final Binder<User> binder = new Binder<>(User.class); private final Binder<User> binder = new Binder<>(User.class);
private final User currentUser; private final User currentUser;
public EditProfileView(UserService userService, SecurityService securityService) { public EditProfileView(UserService userService, SecurityService securityService) {
this.userService = userService;
this.securityService = securityService;
this.currentUser = securityService.getCurrentDatabaseUser(); this.currentUser = securityService.getCurrentDatabaseUser();
setSizeFull(); setSizeFull();
setPadding(true); setPadding(true);
@@ -49,9 +63,13 @@ public class EditProfileView extends HorizontalLayout {
// Linke Spalte: Formular // Linke Spalte: Formular
VerticalLayout formColumn = new VerticalLayout(); VerticalLayout formColumn = new VerticalLayout();
formColumn.setWidth("48%"); formColumn.setWidth("68%");
formColumn.setPadding(false); formColumn.setPadding(false);
formColumn.setSpacing(false); formColumn.setSpacing(false);
// TabSheet
TabSheet tabSheet = new TabSheet();
tabSheet.setSizeFull();
FormLayout form = new FormLayout(); FormLayout form = new FormLayout();
form.setWidthFull(); form.setWidthFull();
@@ -72,10 +90,6 @@ public class EditProfileView extends HorizontalLayout {
TextField zipField = new TextField("Postleitzahl"); TextField zipField = new TextField("Postleitzahl");
TextField cityField = new TextField("Stadt"); TextField cityField = new TextField("Stadt");
// Pflichtfeldhinweis
Paragraph pflichtHinweis = new Paragraph("Die mit (*) gekennzeichneten Felder sind Pflichtfelder.");
pflichtHinweis.getStyle().set("color", "#d32f2f").set("fontWeight", "bold");
// Abweichende Rechnungsadresse // Abweichende Rechnungsadresse
Checkbox diffInvoiceAddress = new Checkbox("Abweichende Rechnungsadresse"); Checkbox diffInvoiceAddress = new Checkbox("Abweichende Rechnungsadresse");
diffInvoiceAddress.getStyle().set("marginTop", "1em"); diffInvoiceAddress.getStyle().set("marginTop", "1em");
@@ -194,39 +208,126 @@ public class EditProfileView extends HorizontalLayout {
form.add(diffInvoiceAddress, 2); form.add(diffInvoiceAddress, 2);
form.add(invCompanyField, 2); form.add(invCompanyField, 2);
form.add(invCompanyAddField, 2); form.add(invCompanyAddField, 2);
// Tabs hinzufügen
form.add(invFirstnameField, invLastnameField); form.add(invFirstnameField, invLastnameField);
form.add(invStreetField, invHouseNumberField); form.add(invStreetField, invHouseNumberField);
form.add(invAddressAddField, 2); form.add(invAddressAddField, 2);
form.add(invZipField, invCityField); form.add(invZipField, invCityField);
formColumn.add(form, pflichtHinweis); tabSheet.add("Stammdaten", form);
// Schalter // Karte (2. Tab)
Checkbox locateAppUser = new Checkbox("App-Nutzer orten");
Checkbox digitalProcess = new Checkbox("Digitale Abwicklung");
Checkbox invoiceViaVotianlt = new Checkbox("Rechnungslegung über votianlt");
formColumn.add(locateAppUser, digitalProcess, invoiceViaVotianlt);
// Rechte Spalte: Karte und Aktionen
VerticalLayout rightColumn = new VerticalLayout();
rightColumn.setWidth("48%");
rightColumn.setAlignItems(Alignment.CENTER);
rightColumn.setSpacing(true);
rightColumn.setPadding(false);
// Koordinatenanzeige
Span coordsLabel = new Span("53°36'25.1\"N 10°06'46.9\"E"); Span coordsLabel = new Span("53°36'25.1\"N 10°06'46.9\"E");
coordsLabel.getStyle().set("fontWeight", "bold").set("marginTop", "1em"); coordsLabel.getStyle().set("fontWeight", "bold").set("marginTop", "1em");
rightColumn.add(coordsLabel);
// Google Maps Platzhalter (iFrame)
Div mapDiv = new Div(); Div mapDiv = new Div();
mapDiv.setWidth("400px"); mapDiv.setWidth("100%");
mapDiv.setHeight("400px"); mapDiv.setHeight("400px");
mapDiv.getElement().setProperty("innerHTML", mapDiv.getElement().setProperty("innerHTML",
"<iframe width='100%' height='100%' frameborder='0' style='border:0' " + "<iframe width='100%' height='100%' frameborder='0' style='border:0' " +
"src='https://www.google.com/maps/embed/v1/place?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&q=53.6070,10.1125' allowfullscreen></iframe>"); "src='https://www.google.com/maps/embed/v1/place?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&q=53.6070,10.1125' allowfullscreen></iframe>");
rightColumn.add(mapDiv); VerticalLayout mapTab = new VerticalLayout();
mapTab.setPadding(false);
mapTab.setSpacing(true);
mapTab.add(coordsLabel, mapDiv);
tabSheet.add("Karte", mapTab);
// Dritter Tab: Rechnungsstellung
HorizontalLayout billingTab = new HorizontalLayout();
billingTab.setWidthFull();
billingTab.setSpacing(true);
billingTab.setPadding(false);
// Linke Spalte: Rechnungsbestandteile
VerticalLayout billingLeft = new VerticalLayout();
billingLeft.setWidth("45%");
billingLeft.setPadding(false);
billingLeft.setSpacing(true);
H3 partsTitle = new H3("Rechnungsbestandteile");
partsTitle.getStyle().set("margin", "0 0 var(--lumo-space-s) 0");
// Felder für Rechnungsstellung (für Live-Update)
Checkbox billingEnabled = new Checkbox("Rechnungslegung über votianLT");
billingEnabled.setValue(false);
billingEnabled.addValueChangeListener(e -> toggleBilling(e.getValue()));
prefixField = new TextField("Rechnungs Präfix");
ustIdField = new TextField("USt-IdNr.");
taxNumberField = new TextField("Steuernummer");
bankNameField = new TextField("Bankname");
ibanField = new TextField("IBAN");
taxRateField = new TextField("Steuersatz");
introTextArea = new TextArea("Einleitungstext");
termsTextArea = new TextArea("Zahlungsbedingungen");
introTextArea.setWidthFull();
termsTextArea.setWidthFull();
// Anfangszustand: Felder deaktiviert bis Checkbox aktiv
setBillingFieldsEnabled(false);
// Live-Update: EAGER und Listener
prefixField.setValueChangeMode(ValueChangeMode.EAGER);
ustIdField.setValueChangeMode(ValueChangeMode.EAGER);
taxNumberField.setValueChangeMode(ValueChangeMode.EAGER);
bankNameField.setValueChangeMode(ValueChangeMode.EAGER);
ibanField.setValueChangeMode(ValueChangeMode.EAGER);
taxRateField.setValueChangeMode(ValueChangeMode.EAGER);
introTextArea.setValueChangeMode(ValueChangeMode.EAGER);
termsTextArea.setValueChangeMode(ValueChangeMode.EAGER);
prefixField.addValueChangeListener(e -> refreshPdf());
ustIdField.addValueChangeListener(e -> refreshPdf());
taxNumberField.addValueChangeListener(e -> refreshPdf());
bankNameField.addValueChangeListener(e -> refreshPdf());
ibanField.addValueChangeListener(e -> refreshPdf());
taxRateField.addValueChangeListener(e -> refreshPdf());
introTextArea.addValueChangeListener(e -> refreshPdf());
termsTextArea.addValueChangeListener(e -> refreshPdf());
billingLeft.add(partsTitle, billingEnabled, prefixField, ustIdField, taxNumberField, bankNameField,
ibanField, taxRateField, introTextArea, termsTextArea);
// Rechte Spalte: Vorschau
VerticalLayout billingRight = new VerticalLayout();
billingRight.setWidth("55%");
billingRight.setPadding(false);
billingRight.setSpacing(false);
billingRight.setHeight("70vh");
H3 previewTitle = new H3("Rechnungsvorschau");
previewTitle.getStyle().set("margin", "0 0 var(--lumo-space-s) 0");
// Echte PDF-Vorschau mittels StreamResource und iframe
Div previewWrapper = new Div();
previewWrapper.setWidth("100%");
previewWrapper.setHeight("100%");
previewWrapper.getStyle()
.set("overflow", "auto");
// Initial noch keine PDF laden (erst bei aktiver Checkbox)
pdfFrame = new IFrame();
pdfFrame.setWidth("100%");
pdfFrame.setHeight("100%");
previewWrapper.removeAll();
previewWrapper.add(pdfFrame);
billingRight.add(previewTitle, previewWrapper);
billingTab.add(billingLeft, billingRight);
tabSheet.add("Rechnungsstellung", billingTab);
// Zweiter Tab: Einstellungen (Beispiel mit Schaltern)
VerticalLayout switches = new VerticalLayout();
switches.setPadding(false);
switches.setSpacing(true);
Checkbox locateAppUser = new Checkbox("App-Nutzer orten");
Checkbox digitalProcess = new Checkbox("Digitale Abwicklung");
Checkbox invoiceViaVotianlt = new Checkbox("Rechnungslegung über votianlt");
switches.add(locateAppUser, digitalProcess, invoiceViaVotianlt);
tabSheet.add("Einstellungen", switches);
// Sicherheit-Tab (2FA, Passwort, Konto)
VerticalLayout securityTab = new VerticalLayout();
securityTab.setPadding(false);
securityTab.setSpacing(true);
// 2-Faktor Auth // 2-Faktor Auth
Checkbox twoFactor = new Checkbox("2-Faktor-Authentifizierung"); Checkbox twoFactor = new Checkbox("2-Faktor-Authentifizierung");
@@ -234,17 +335,19 @@ public class EditProfileView extends HorizontalLayout {
twoFactorInfo.getStyle().set("marginLeft", "0.3em"); twoFactorInfo.getStyle().set("marginLeft", "0.3em");
HorizontalLayout twoFactorLayout = new HorizontalLayout(twoFactor, twoFactorInfo); HorizontalLayout twoFactorLayout = new HorizontalLayout(twoFactor, twoFactorInfo);
twoFactorLayout.setAlignItems(Alignment.CENTER); twoFactorLayout.setAlignItems(Alignment.CENTER);
rightColumn.add(twoFactorLayout); securityTab.add(twoFactorLayout);
// Passwort ändern Button // Passwort ändern Button
Button changePassword = new Button("Passwort ändern"); Button changePassword = new Button("Passwort ändern");
changePassword.addThemeVariants(ButtonVariant.LUMO_PRIMARY); changePassword.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
rightColumn.add(changePassword); securityTab.add(changePassword);
// Benutzerkonto löschen Button // Benutzerkonto löschen Button
Button deleteAccount = new Button("Benutzerkonto löschen"); Button deleteAccount = new Button("Benutzerkonto löschen");
deleteAccount.addThemeVariants(ButtonVariant.LUMO_ERROR); deleteAccount.addThemeVariants(ButtonVariant.LUMO_ERROR);
rightColumn.add(deleteAccount); securityTab.add(deleteAccount);
tabSheet.add("Sicherheit", securityTab);
// Profil speichern Button (unten rechts) // Profil speichern Button (unten rechts)
Button saveProfile = new Button("Profiländerungen speichern"); Button saveProfile = new Button("Profiländerungen speichern");
@@ -260,8 +363,91 @@ public class EditProfileView extends HorizontalLayout {
} }
} }
}); });
// Tabs dem linken Bereich hinzufügen
formColumn.add(tabSheet);
saveProfile.getStyle().set("position", "absolute").set("right", "2em").set("bottom", "2em");
add(formColumn, rightColumn, saveProfile); // Button horizontal zentrieren unterhalb der Tabs
HorizontalLayout buttonBar = new HorizontalLayout(saveProfile);
buttonBar.setWidthFull();
buttonBar.setJustifyContentMode(JustifyContentMode.CENTER);
buttonBar.setPadding(false);
buttonBar.setSpacing(false);
// 20px Abstand nach oben
buttonBar.getStyle().set("margin-top", "20px");
// Fixieren am unteren Rand
buttonBar.getStyle().set("position", "sticky");
buttonBar.getStyle().set("bottom", "0");
buttonBar.getStyle().set("background", "var(--lumo-base-color)");
buttonBar.getStyle().set("padding", "var(--lumo-space-s) 0");
buttonBar.getStyle().set("z-index", "1");
formColumn.add(buttonBar);
add(formColumn);
} }
// PDF neu rendern und iframe aktualisieren
// Felder im Billing-Tab aktivieren/deaktivieren
private void setBillingFieldsEnabled(boolean enabled) {
if (prefixField != null) prefixField.setEnabled(enabled);
if (ustIdField != null) ustIdField.setEnabled(enabled);
if (taxNumberField != null) taxNumberField.setEnabled(enabled);
if (bankNameField != null) bankNameField.setEnabled(enabled);
if (ibanField != null) ibanField.setEnabled(enabled);
if (taxRateField != null) taxRateField.setEnabled(enabled);
if (introTextArea != null) introTextArea.setEnabled(enabled);
if (termsTextArea != null) termsTextArea.setEnabled(enabled);
}
// Checkbox steuert Aktivierung und PDF
private void toggleBilling(boolean enabled) {
setBillingFieldsEnabled(enabled);
if (enabled) {
refreshPdf();
} else {
if (pdfFrame != null) {
pdfFrame.setSrc((String) null);
}
}
}
private void refreshPdf() {
byte[] bytes = generatePreviewPdf();
String dataUrl = "data:application/pdf;base64," + Base64.getEncoder().encodeToString(bytes) + "#toolbar=0&navpanes=0&zoom=page-width&view=FitH";
if (pdfFrame != null) {
pdfFrame.setSrc(dataUrl);
}
}
// Einfache PDF-Vorschau generieren (kann später durch echte Logik ersetzt werden)
private byte[] generatePreviewPdf() {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
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("Rechnungsvorschau"));
document.add(new com.lowagie.text.Paragraph(" "));
document.add(new com.lowagie.text.Paragraph("Rechnungs Präfix: " + safe(prefixField)));
document.add(new com.lowagie.text.Paragraph("USt-IdNr.: " + safe(ustIdField)));
document.add(new com.lowagie.text.Paragraph("Steuernummer: " + safe(taxNumberField)));
document.add(new com.lowagie.text.Paragraph("Bankname: " + safe(bankNameField)));
document.add(new com.lowagie.text.Paragraph("IBAN: " + safe(ibanField)));
document.add(new com.lowagie.text.Paragraph("Steuersatz: " + safe(taxRateField)));
document.add(new com.lowagie.text.Paragraph(" "));
document.add(new com.lowagie.text.Paragraph("Einleitungstext:"));
document.add(new com.lowagie.text.Paragraph(safe(introTextArea)));
document.add(new com.lowagie.text.Paragraph(" "));
document.add(new com.lowagie.text.Paragraph("Zahlungsbedingungen:"));
document.add(new com.lowagie.text.Paragraph(safe(termsTextArea)));
document.close();
return out.toByteArray();
} catch (Exception e) {
throw new RuntimeException("PDF-Generierung fehlgeschlagen: " + e.getMessage(), e);
}
}
// Utility: safe getter für TextField/TextArea
private String safe(TextField f) { return f != null && f.getValue() != null ? f.getValue() : ""; }
private String safe(TextArea f) { return f != null && f.getValue() != null ? f.getValue() : ""; }
} }