diff --git a/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java b/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java index a6a246d..341e352 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java @@ -7,6 +7,8 @@ import com.vaadin.flow.component.formlayout.FormLayout; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Paragraph; 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.VaadinIcon; 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.textfield.EmailField; 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.validator.EmailValidator; +import com.vaadin.flow.data.value.ValueChangeMode; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; 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) @RolesAllowed({"USER","ADMIN"}) public class EditProfileView extends HorizontalLayout { - private final UserService userService; - private final SecurityService securityService; + private final TextField prefixField; + 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 binder = new Binder<>(User.class); private final User currentUser; public EditProfileView(UserService userService, SecurityService securityService) { - this.userService = userService; - this.securityService = securityService; this.currentUser = securityService.getCurrentDatabaseUser(); setSizeFull(); setPadding(true); @@ -49,9 +63,13 @@ public class EditProfileView extends HorizontalLayout { // Linke Spalte: Formular VerticalLayout formColumn = new VerticalLayout(); - formColumn.setWidth("48%"); + formColumn.setWidth("68%"); formColumn.setPadding(false); formColumn.setSpacing(false); + // TabSheet + TabSheet tabSheet = new TabSheet(); + tabSheet.setSizeFull(); + FormLayout form = new FormLayout(); form.setWidthFull(); @@ -72,10 +90,6 @@ public class EditProfileView extends HorizontalLayout { TextField zipField = new TextField("Postleitzahl"); 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 Checkbox diffInvoiceAddress = new Checkbox("Abweichende Rechnungsadresse"); diffInvoiceAddress.getStyle().set("marginTop", "1em"); @@ -194,39 +208,126 @@ public class EditProfileView extends HorizontalLayout { form.add(diffInvoiceAddress, 2); form.add(invCompanyField, 2); form.add(invCompanyAddField, 2); + // Tabs hinzufügen form.add(invFirstnameField, invLastnameField); form.add(invStreetField, invHouseNumberField); form.add(invAddressAddField, 2); form.add(invZipField, invCityField); - formColumn.add(form, pflichtHinweis); + tabSheet.add("Stammdaten", form); - // Schalter - 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 + // Karte (2. Tab) Span coordsLabel = new Span("53°36'25.1\"N 10°06'46.9\"E"); coordsLabel.getStyle().set("fontWeight", "bold").set("marginTop", "1em"); - rightColumn.add(coordsLabel); - - // Google Maps Platzhalter (iFrame) Div mapDiv = new Div(); - mapDiv.setWidth("400px"); + mapDiv.setWidth("100%"); mapDiv.setHeight("400px"); mapDiv.getElement().setProperty("innerHTML", ""); - 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 Checkbox twoFactor = new Checkbox("2-Faktor-Authentifizierung"); @@ -234,17 +335,19 @@ public class EditProfileView extends HorizontalLayout { twoFactorInfo.getStyle().set("marginLeft", "0.3em"); HorizontalLayout twoFactorLayout = new HorizontalLayout(twoFactor, twoFactorInfo); twoFactorLayout.setAlignItems(Alignment.CENTER); - rightColumn.add(twoFactorLayout); + securityTab.add(twoFactorLayout); // Passwort ändern Button Button changePassword = new Button("Passwort ändern"); changePassword.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - rightColumn.add(changePassword); + securityTab.add(changePassword); // Benutzerkonto löschen Button Button deleteAccount = new Button("Benutzerkonto löschen"); deleteAccount.addThemeVariants(ButtonVariant.LUMO_ERROR); - rightColumn.add(deleteAccount); + securityTab.add(deleteAccount); + + tabSheet.add("Sicherheit", securityTab); // Profil speichern Button (unten rechts) 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() : ""; } + }