diff --git a/src/main/bundles/dev.bundle b/src/main/bundles/dev.bundle index ccaa944..75936dd 100644 Binary files a/src/main/bundles/dev.bundle and b/src/main/bundles/dev.bundle differ diff --git a/src/main/frontend/invoice-generator/profile-invoice-generator.js b/src/main/frontend/invoice-generator/profile-invoice-generator.js index f9a353b..7baa42d 100644 --- a/src/main/frontend/invoice-generator/profile-invoice-generator.js +++ b/src/main/frontend/invoice-generator/profile-invoice-generator.js @@ -76,7 +76,8 @@ window.initProfileInvoiceGenerator = function() { el.fontSize || 14, el.color || '#333333', el.width || 100, - el.height || 30 + el.height || 30, + el.isStatic || false ); } } @@ -140,36 +141,51 @@ window.initProfileInvoiceGenerator = function() { function drawElement(el) { ctx.save(); + var x = pageX + el.x; + var y = pageY + el.y; + var w = el.width || 100; + var h = el.height || 30; + if (el.type === 'line') { ctx.strokeStyle = el.color || '#333333'; ctx.lineWidth = 1; ctx.beginPath(); - ctx.moveTo(pageX + el.x, pageY + el.y); - ctx.lineTo(pageX + el.x + el.width, pageY + el.y); + ctx.moveTo(x, y); + ctx.lineTo(x + w, y); ctx.stroke(); } else if (el.type === 'image') { ctx.fillStyle = '#f0f0f0'; - ctx.fillRect(pageX + el.x, pageY + el.y, el.width, el.height); + ctx.fillRect(x, y, w, h); ctx.strokeStyle = '#999999'; - ctx.strokeRect(pageX + el.x, pageY + el.y, el.width, el.height); + ctx.strokeRect(x, y, w, h); ctx.fillStyle = '#666666'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.fillText('Bild', pageX + el.x + el.width / 2, pageY + el.y + el.height / 2); + ctx.fillText('Bild', x + w / 2, y + h / 2); } else { // Text elements ctx.font = (el.fontStyle || '') + ' ' + (el.fontSize || 14) + 'px Arial'; ctx.fillStyle = el.color || '#333333'; ctx.textBaseline = 'top'; + // Draw background highlight for static elements + if (el.isStatic) { + var textWidth = ctx.measureText(el.text || '').width + 10; + ctx.fillStyle = 'rgba(25, 118, 210, 0.1)'; // Light blue background + ctx.fillRect(x - 3, y - 2, Math.max(w, textWidth), h); + } + + ctx.fillStyle = el.color || '#333333'; + ctx.font = (el.fontStyle || '') + ' ' + (el.fontSize || 14) + 'px Arial'; + var lines = (el.text || '').split('\n'); var lineHeight = (el.fontSize || 14) * 1.2; - var y = pageY + el.y; + var ty = y; lines.forEach(function(line) { - ctx.fillText(line, pageX + el.x, y); - y += lineHeight; + ctx.fillText(line, x, ty); + ty += lineHeight; }); } @@ -342,7 +358,7 @@ window.initProfileInvoiceGenerator = function() { }); // Add element function - window.addProfileElement = function(type, label, dropX, dropY) { + window.addProfileElement = function(type, label, dropX, dropY, isStatic, staticText) { elementCounter++; var id = 'element-' + elementCounter; @@ -362,57 +378,66 @@ window.initProfileInvoiceGenerator = function() { width: 150, height: 30, fontSize: 14, - color: '#333333' + color: '#333333', + isStatic: isStatic || false }; - switch (type) { - case 'text': - el.text = 'Text eingeben...'; - el.height = 20; - break; - case 'header': - el.text = 'Überschrift'; - el.fontSize = 24; - el.fontStyle = 'bold'; - el.color = '#000000'; - el.width = 200; - el.height = 30; - break; - case 'date': - el.text = 'Datum: ' + new Date().toLocaleDateString('de-DE'); - el.fontSize = 12; - el.color = '#666666'; - el.height = 16; - break; - case 'customer': - el.text = 'Kundenname\nStraße Nr.\nPLZ Ort'; - el.height = 50; - el.fontSize = 12; - break; - case 'company': - el.text = 'Ihr Unternehmen\nIhre Straße\nIhre PLZ Ort'; - el.height = 50; - el.fontSize = 12; - break; - case 'amount': - el.text = 'Gesamtbetrag: 0,00 €'; - el.fontStyle = 'bold'; - el.width = 180; - el.height = 20; - break; - case 'line': - el.text = ''; - el.width = 200; - el.height = 2; - break; - case 'image': - el.text = 'Bild'; - el.width = 100; - el.height = 100; - break; - default: - el.text = label || 'Neues Element'; - el.height = 20; + // Handle static elements (user data) + if (isStatic && staticText) { + el.text = staticText; + el.color = '#1976d2'; // Blue color for static elements + el.fontStyle = 'bold'; + el.height = 20; + } else { + switch (type) { + case 'text': + el.text = 'Text eingeben...'; + el.height = 20; + break; + case 'header': + el.text = 'Überschrift'; + el.fontSize = 24; + el.fontStyle = 'bold'; + el.color = '#000000'; + el.width = 200; + el.height = 30; + break; + case 'date': + el.text = 'Datum: ' + new Date().toLocaleDateString('de-DE'); + el.fontSize = 12; + el.color = '#666666'; + el.height = 16; + break; + case 'customer': + el.text = 'Kundenname\nStraße Nr.\nPLZ Ort'; + el.height = 50; + el.fontSize = 12; + break; + case 'company': + el.text = 'Ihr Unternehmen\nIhre Straße\nIhre PLZ Ort'; + el.height = 50; + el.fontSize = 12; + break; + case 'amount': + el.text = 'Gesamtbetrag: 0,00 €'; + el.fontStyle = 'bold'; + el.width = 180; + el.height = 20; + break; + case 'line': + el.text = ''; + el.width = 200; + el.height = 2; + break; + case 'image': + el.text = 'Bild'; + el.width = 100; + el.height = 100; + break; + default: + el.text = label || 'Neues Element'; + el.height = 20; + } } elements.push(el); @@ -538,12 +563,14 @@ window.initProfileInvoiceGenerator = function() { var templateType = e.dataTransfer.getData('template-type'); var templateLabel = e.dataTransfer.getData('template-label'); + var isStatic = e.dataTransfer.getData('is-static') === 'true'; + var staticText = e.dataTransfer.getData('static-text'); if (templateType && window.addProfileElement) { var rect = container.getBoundingClientRect(); var x = e.clientX - rect.left; var y = e.clientY - rect.top; - window.addProfileElement(templateType, templateLabel, x, y); + window.addProfileElement(templateType, templateLabel, x, y, isStatic, staticText); } }); } 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 c689439..3c72f54 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java @@ -295,14 +295,11 @@ public class EditProfileView extends HorizontalLayout { // Nur die Checkbox "Rechnungslegung über votianLT" billingEnabled = new Checkbox("Rechnungslegung über votianLT"); - billingEnabled.setValue(false); - billingEnabled.addValueChangeListener(e -> { - // Nur noch für das Speichern des Status, keine PDF-Vorschau mehr - }); + billingEnabled.setValue(true); // Standardmäßig aktiviert billingTab.add(billingEnabled); // Hauptlayout: Links (Templates) | Mitte (Canvas) | Rechts (Eigenschaften) - HorizontalLayout mainLayout = new HorizontalLayout(); + final HorizontalLayout mainLayout = new HorizontalLayout(); mainLayout.setWidthFull(); mainLayout.setHeight("500px"); mainLayout.setSpacing(true); @@ -360,8 +357,11 @@ public class EditProfileView extends HorizontalLayout { billingTab.add(mainLayout); billingTab.expand(mainLayout); + // Initialen Zustand setzen (sichtbar da checkbox standardmäßig true) + mainLayout.setVisible(true); + // Action Buttons für den Rechnungsgenerator - HorizontalLayout actionLayout = new HorizontalLayout(); + final HorizontalLayout actionLayout = new HorizontalLayout(); actionLayout.setWidthFull(); actionLayout.setJustifyContentMode(JustifyContentMode.END); actionLayout.setSpacing(true); @@ -401,7 +401,17 @@ public class EditProfileView extends HorizontalLayout { actionLayout.add(clearButton, previewPdfButton, saveTemplateButton); billingTab.add(actionLayout); + // Initialen Zustand setzen (sichtbar da checkbox standardmäßig true) + actionLayout.setVisible(true); + tabSheet.add("Rechnungserstellung", billingTab); + + // Sichtbarkeit des Invoice Generators an Checkbox binden + billingEnabled.addValueChangeListener(e -> { + boolean visible = e.getValue(); + mainLayout.setVisible(visible); + actionLayout.setVisible(visible); + }); // Bestehende Rechnungsdaten laden (nur für die Checkbox) loadInvoiceData(); @@ -416,7 +426,9 @@ public class EditProfileView extends HorizontalLayout { " if (window.initProfileInvoiceGenerator) { " + " window.initProfileInvoiceGenerator(); " + " console.log('Canvas initialized, now loading template...'); " + - " $0.$server.onCanvasReady(); " + + " setTimeout(function() { " + + " $0.$server.onCanvasReady(); " + + " }, 200); " + " } else { " + " console.error('initProfileInvoiceGenerator not found'); " + " } " + @@ -848,27 +860,101 @@ public class EditProfileView extends HorizontalLayout { panel.setSpacing(true); panel.setHeightFull(); - Span header = new Span("Textbausteine"); - header.getStyle() + // Bereich 1: Rechnungselemente (Stammdaten) + Span invoiceHeader = new Span("Rechnungselemente"); + invoiceHeader.getStyle() .set("font-weight", "bold") - .set("font-size", "var(--lumo-font-size-l)"); + .set("font-size", "var(--lumo-font-size-m)") + .set("margin-top", "var(--lumo-space-s)"); + + // Stammdaten des Benutzers als unveränderbare Elemente + String company = safe(currentUser.getCompany()); + String fullName = safe(currentUser.getFirstname()) + " " + safe(currentUser.getName()); + String street = safe(currentUser.getStreet()) + " " + safe(currentUser.getHouseNumber()); + String city = safe(currentUser.getZip()) + " " + safe(currentUser.getCity()); + String email = safe(currentUser.getEmail()); + String phone = safe(currentUser.getPhone()); + + Div senderCompany = createStaticTemplate("Firma", VaadinIcon.OFFICE, "sender_company", + company.isEmpty() ? "Ihre Firma" : company); + Div senderName = createStaticTemplate("Name", VaadinIcon.USER, "sender_name", + fullName.trim().isEmpty() ? "Ihr Name" : fullName.trim()); + Div senderAddress = createStaticTemplate("Adresse", VaadinIcon.MAP_MARKER, "sender_address", + street.trim().isEmpty() ? "Ihre Straße" : street.trim()); + Div senderCity = createStaticTemplate("Ort", VaadinIcon.BUILDING, "sender_city", + city.trim().isEmpty() ? "PLZ Ort" : city.trim()); + Div senderEmail = createStaticTemplate("E-Mail", VaadinIcon.ENVELOPE, "sender_email", + email.isEmpty() ? "ihre@email.de" : email); + Div senderPhone = createStaticTemplate("Telefon", VaadinIcon.PHONE, "sender_phone", + phone.isEmpty() ? "Ihre Telefonnummer" : phone); + + // Bereich 2: Freie Elemente + Span freeHeader = new Span("Freie Elemente"); + freeHeader.getStyle() + .set("font-weight", "bold") + .set("font-size", "var(--lumo-font-size-m)") + .set("margin-top", "var(--lumo-space-m)"); // Draggable Templates Div textBlock = createDraggableTemplate("Textfeld", VaadinIcon.TEXT_LABEL, "text"); Div headerBlock = createDraggableTemplate("Überschrift", VaadinIcon.HEADER, "header"); Div dateBlock = createDraggableTemplate("Datum", VaadinIcon.CALENDAR, "date"); Div customerBlock = createDraggableTemplate("Kundeninfo", VaadinIcon.USER, "customer"); - Div companyBlock = createDraggableTemplate("Firmeninfo", VaadinIcon.OFFICE, "company"); + Div companyBlock = createDraggableTemplate("Firmeninfo", VaadinIcon.WORKPLACE, "company"); Div amountBlock = createDraggableTemplate("Betrag", VaadinIcon.COIN_PILES, "amount"); Div lineBlock = createDraggableTemplate("Linie", VaadinIcon.LINE_V, "line"); Div imageBlock = createDraggableTemplate("Bild", VaadinIcon.PICTURE, "image"); - panel.add(header, textBlock, headerBlock, dateBlock, customerBlock, companyBlock, amountBlock, lineBlock, - imageBlock); + panel.add( + invoiceHeader, + senderCompany, senderName, senderAddress, senderCity, senderEmail, senderPhone, + freeHeader, + textBlock, headerBlock, dateBlock, customerBlock, companyBlock, amountBlock, lineBlock, imageBlock + ); return panel; } + private Div createStaticTemplate(String label, VaadinIcon icon, String type, String defaultText) { + Div template = new Div(); + template.setText(label); + template.getStyle() + .set("padding", "var(--lumo-space-s)") + .set("margin", "var(--lumo-space-xs) 0") + .set("background-color", "var(--lumo-contrast-10pct)") + .set("border", "1px solid var(--lumo-contrast-30pct)") + .set("border-radius", "var(--lumo-border-radius-m)") + .set("cursor", "grab") + .set("display", "flex") + .set("align-items", "center") + .set("gap", "var(--lumo-space-s)") + .set("user-select", "none") + .set("font-size", "var(--lumo-font-size-s)"); + + Icon templateIcon = icon.create(); + templateIcon.setSize("var(--lumo-icon-size-s)"); + template.getElement().insertChild(0, templateIcon.getElement()); + + template.getElement().setAttribute("draggable", "true"); + template.getElement().setAttribute("data-template-type", type); + template.getElement().setAttribute("data-template-label", label); + template.getElement().setAttribute("data-static-text", defaultText); + + template.getElement().executeJs( + "this.addEventListener('dragstart', function(e) {" + + " e.dataTransfer.setData('template-type', this.getAttribute('data-template-type'));" + + " e.dataTransfer.setData('template-label', this.getAttribute('data-template-label'));" + + " e.dataTransfer.setData('static-text', this.getAttribute('data-static-text'));" + + " e.dataTransfer.setData('is-static', 'true');" + + " this.style.opacity = '0.5';" + + "});" + + "this.addEventListener('dragend', function(e) {" + + " this.style.opacity = '1';" + + "});"); + + return template; + } + private Div createDraggableTemplate(String label, VaadinIcon icon, String type) { Div template = new Div(); template.setText(label); @@ -896,6 +982,7 @@ public class EditProfileView extends HorizontalLayout { "this.addEventListener('dragstart', function(e) {" + " e.dataTransfer.setData('template-type', this.getAttribute('data-template-type'));" + " e.dataTransfer.setData('template-label', this.getAttribute('data-template-label'));" + + " e.dataTransfer.setData('is-static', 'false');" + " this.style.opacity = '0.5';" + "});" + "this.addEventListener('dragend', function(e) {" + @@ -929,7 +1016,7 @@ public class EditProfileView extends HorizontalLayout { @ClientCallable public void updatePropertiesPanel(String elementId, String elementType, String text, Double x, Double y, - Integer fontSize, String color, Double width, Double height) { + Integer fontSize, String color, Double width, Double height, Boolean isStatic) { getUI().ifPresent(ui -> ui.access(() -> { propertiesPanelProfile.removeAll(); @@ -939,7 +1026,7 @@ public class EditProfileView extends HorizontalLayout { .set("font-size", "var(--lumo-font-size-l)"); // Element Typ Anzeige - Span typeLabel = new Span("Typ: " + elementType); + Span typeLabel = new Span("Typ: " + elementType + (Boolean.TRUE.equals(isStatic) ? " (Stammdaten)" : "")); typeLabel.getStyle().set("font-size", "var(--lumo-font-size-s)"); propertiesPanelProfile.add(header, typeLabel); @@ -949,12 +1036,18 @@ public class EditProfileView extends HorizontalLayout { TextField textField = new TextField("Text"); textField.setValue(text != null ? text : ""); textField.setWidthFull(); - textField.addValueChangeListener(e -> { - getElement().executeJs( - "if (window.updateProfileElementText) { window.updateProfileElementText('" + elementId - + "', $0); }", - e.getValue()); - }); + // Statische Elemente können nicht editiert werden + if (Boolean.TRUE.equals(isStatic)) { + textField.setReadOnly(true); + textField.setHelperText("Text kommt aus Ihren Stammdaten"); + } else { + textField.addValueChangeListener(e -> { + getElement().executeJs( + "if (window.updateProfileElementText) { window.updateProfileElementText('" + elementId + + "', $0); }", + e.getValue()); + }); + } propertiesPanelProfile.add(textField); } @@ -1113,21 +1206,16 @@ public class EditProfileView extends HorizontalLayout { if (optionalTemplate.isPresent()) { String templateData = optionalTemplate.get().getTemplateData(); if (templateData != null && !templateData.isEmpty()) { - // Escape single quotes and newlines for JavaScript - String escapedData = templateData - .replace("\\", "\\\\") - .replace("'", "\\'") - .replace("\n", "\\n") - .replace("\r", ""); + System.out.println("Loading template data: " + templateData.substring(0, Math.min(100, templateData.length())) + "..."); getElement().executeJs( "setTimeout(function() { " + " if (window.loadProfileTemplate && document.getElementById('invoice-canvas-container-profile')) { " + " console.log('Loading template into canvas...'); " + - " window.loadProfileTemplate('" + escapedData + "'); " + + " window.loadProfileTemplate($0); " + " } else { " + " console.error('loadProfileTemplate or canvas not available'); " + " } " + - "}, 100);"); + "}, 300);", templateData); } } else { System.out.println("No template found for user: " + currentUser.getId()); diff --git a/src/main/java/de/assecutor/votianlt/service/CustomerInvoiceService.java b/src/main/java/de/assecutor/votianlt/service/CustomerInvoiceService.java index 693fbbd..c993064 100644 --- a/src/main/java/de/assecutor/votianlt/service/CustomerInvoiceService.java +++ b/src/main/java/de/assecutor/votianlt/service/CustomerInvoiceService.java @@ -262,7 +262,7 @@ public class CustomerInvoiceService { htmlBuilder.append("@page { size: A4; margin: 0; }"); htmlBuilder.append("body { margin: 0; padding: 0; width: 210mm; height: 297mm; position: relative; font-family: Arial, sans-serif; }"); htmlBuilder.append(".element { position: absolute; }"); - htmlBuilder.append(".text { white-space: pre-wrap; word-wrap: break-word; }"); + htmlBuilder.append(".text { white-space: pre; }"); htmlBuilder.append(".line { border-top: 1px solid #333; }"); htmlBuilder.append(".image { max-width: 100%; max-height: 100%; }"); htmlBuilder.append("");