Erweiterungen

This commit is contained in:
2026-02-13 12:11:49 +01:00
parent 09bd168c74
commit 2af9c9a99a
4 changed files with 205 additions and 90 deletions

Binary file not shown.

View File

@@ -76,7 +76,8 @@ window.initProfileInvoiceGenerator = function() {
el.fontSize || 14, el.fontSize || 14,
el.color || '#333333', el.color || '#333333',
el.width || 100, el.width || 100,
el.height || 30 el.height || 30,
el.isStatic || false
); );
} }
} }
@@ -140,36 +141,51 @@ window.initProfileInvoiceGenerator = function() {
function drawElement(el) { function drawElement(el) {
ctx.save(); 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') { if (el.type === 'line') {
ctx.strokeStyle = el.color || '#333333'; ctx.strokeStyle = el.color || '#333333';
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(pageX + el.x, pageY + el.y); ctx.moveTo(x, y);
ctx.lineTo(pageX + el.x + el.width, pageY + el.y); ctx.lineTo(x + w, y);
ctx.stroke(); ctx.stroke();
} else if (el.type === 'image') { } else if (el.type === 'image') {
ctx.fillStyle = '#f0f0f0'; 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.strokeStyle = '#999999';
ctx.strokeRect(pageX + el.x, pageY + el.y, el.width, el.height); ctx.strokeRect(x, y, w, h);
ctx.fillStyle = '#666666'; ctx.fillStyle = '#666666';
ctx.font = '12px Arial'; ctx.font = '12px Arial';
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; 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 { } else {
// Text elements // Text elements
ctx.font = (el.fontStyle || '') + ' ' + (el.fontSize || 14) + 'px Arial'; ctx.font = (el.fontStyle || '') + ' ' + (el.fontSize || 14) + 'px Arial';
ctx.fillStyle = el.color || '#333333'; ctx.fillStyle = el.color || '#333333';
ctx.textBaseline = 'top'; 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 lines = (el.text || '').split('\n');
var lineHeight = (el.fontSize || 14) * 1.2; var lineHeight = (el.fontSize || 14) * 1.2;
var y = pageY + el.y; var ty = y;
lines.forEach(function(line) { lines.forEach(function(line) {
ctx.fillText(line, pageX + el.x, y); ctx.fillText(line, x, ty);
y += lineHeight; ty += lineHeight;
}); });
} }
@@ -342,7 +358,7 @@ window.initProfileInvoiceGenerator = function() {
}); });
// Add element function // Add element function
window.addProfileElement = function(type, label, dropX, dropY) { window.addProfileElement = function(type, label, dropX, dropY, isStatic, staticText) {
elementCounter++; elementCounter++;
var id = 'element-' + elementCounter; var id = 'element-' + elementCounter;
@@ -362,57 +378,66 @@ window.initProfileInvoiceGenerator = function() {
width: 150, width: 150,
height: 30, height: 30,
fontSize: 14, fontSize: 14,
color: '#333333' color: '#333333',
isStatic: isStatic || false
}; };
switch (type) { // Handle static elements (user data)
case 'text': if (isStatic && staticText) {
el.text = 'Text eingeben...'; el.text = staticText;
el.height = 20; el.color = '#1976d2'; // Blue color for static elements
break; el.fontStyle = 'bold';
case 'header': el.height = 20;
el.text = 'Überschrift'; } else {
el.fontSize = 24; switch (type) {
el.fontStyle = 'bold'; case 'text':
el.color = '#000000'; el.text = 'Text eingeben...';
el.width = 200; el.height = 20;
el.height = 30; break;
break; case 'header':
case 'date': el.text = 'Überschrift';
el.text = 'Datum: ' + new Date().toLocaleDateString('de-DE'); el.fontSize = 24;
el.fontSize = 12; el.fontStyle = 'bold';
el.color = '#666666'; el.color = '#000000';
el.height = 16; el.width = 200;
break; el.height = 30;
case 'customer': break;
el.text = 'Kundenname\nStraße Nr.\nPLZ Ort'; case 'date':
el.height = 50; el.text = 'Datum: ' + new Date().toLocaleDateString('de-DE');
el.fontSize = 12; el.fontSize = 12;
break; el.color = '#666666';
case 'company': el.height = 16;
el.text = 'Ihr Unternehmen\nIhre Straße\nIhre PLZ Ort'; break;
el.height = 50; case 'customer':
el.fontSize = 12; el.text = 'Kundenname\nStraße Nr.\nPLZ Ort';
break; el.height = 50;
case 'amount': el.fontSize = 12;
el.text = 'Gesamtbetrag: 0,00 €'; break;
el.fontStyle = 'bold'; case 'company':
el.width = 180; el.text = 'Ihr Unternehmen\nIhre Straße\nIhre PLZ Ort';
el.height = 20; el.height = 50;
break; el.fontSize = 12;
case 'line': break;
el.text = ''; case 'amount':
el.width = 200; el.text = 'Gesamtbetrag: 0,00 €';
el.height = 2; el.fontStyle = 'bold';
break; el.width = 180;
case 'image': el.height = 20;
el.text = 'Bild'; break;
el.width = 100; case 'line':
el.height = 100; el.text = '';
break; el.width = 200;
default: el.height = 2;
el.text = label || 'Neues Element'; break;
el.height = 20; case 'image':
el.text = 'Bild';
el.width = 100;
el.height = 100;
break;
default:
el.text = label || 'Neues Element';
el.height = 20;
}
} }
elements.push(el); elements.push(el);
@@ -538,12 +563,14 @@ window.initProfileInvoiceGenerator = function() {
var templateType = e.dataTransfer.getData('template-type'); var templateType = e.dataTransfer.getData('template-type');
var templateLabel = e.dataTransfer.getData('template-label'); 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) { if (templateType && window.addProfileElement) {
var rect = container.getBoundingClientRect(); var rect = container.getBoundingClientRect();
var x = e.clientX - rect.left; var x = e.clientX - rect.left;
var y = e.clientY - rect.top; var y = e.clientY - rect.top;
window.addProfileElement(templateType, templateLabel, x, y); window.addProfileElement(templateType, templateLabel, x, y, isStatic, staticText);
} }
}); });
} }

View File

@@ -295,14 +295,11 @@ public class EditProfileView extends HorizontalLayout {
// Nur die Checkbox "Rechnungslegung über votianLT" // Nur die Checkbox "Rechnungslegung über votianLT"
billingEnabled = new Checkbox("Rechnungslegung über votianLT"); billingEnabled = new Checkbox("Rechnungslegung über votianLT");
billingEnabled.setValue(false); billingEnabled.setValue(true); // Standardmäßig aktiviert
billingEnabled.addValueChangeListener(e -> {
// Nur noch für das Speichern des Status, keine PDF-Vorschau mehr
});
billingTab.add(billingEnabled); billingTab.add(billingEnabled);
// Hauptlayout: Links (Templates) | Mitte (Canvas) | Rechts (Eigenschaften) // Hauptlayout: Links (Templates) | Mitte (Canvas) | Rechts (Eigenschaften)
HorizontalLayout mainLayout = new HorizontalLayout(); final HorizontalLayout mainLayout = new HorizontalLayout();
mainLayout.setWidthFull(); mainLayout.setWidthFull();
mainLayout.setHeight("500px"); mainLayout.setHeight("500px");
mainLayout.setSpacing(true); mainLayout.setSpacing(true);
@@ -360,8 +357,11 @@ public class EditProfileView extends HorizontalLayout {
billingTab.add(mainLayout); billingTab.add(mainLayout);
billingTab.expand(mainLayout); billingTab.expand(mainLayout);
// Initialen Zustand setzen (sichtbar da checkbox standardmäßig true)
mainLayout.setVisible(true);
// Action Buttons für den Rechnungsgenerator // Action Buttons für den Rechnungsgenerator
HorizontalLayout actionLayout = new HorizontalLayout(); final HorizontalLayout actionLayout = new HorizontalLayout();
actionLayout.setWidthFull(); actionLayout.setWidthFull();
actionLayout.setJustifyContentMode(JustifyContentMode.END); actionLayout.setJustifyContentMode(JustifyContentMode.END);
actionLayout.setSpacing(true); actionLayout.setSpacing(true);
@@ -401,7 +401,17 @@ public class EditProfileView extends HorizontalLayout {
actionLayout.add(clearButton, previewPdfButton, saveTemplateButton); actionLayout.add(clearButton, previewPdfButton, saveTemplateButton);
billingTab.add(actionLayout); billingTab.add(actionLayout);
// Initialen Zustand setzen (sichtbar da checkbox standardmäßig true)
actionLayout.setVisible(true);
tabSheet.add("Rechnungserstellung", billingTab); 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) // Bestehende Rechnungsdaten laden (nur für die Checkbox)
loadInvoiceData(); loadInvoiceData();
@@ -416,7 +426,9 @@ public class EditProfileView extends HorizontalLayout {
" if (window.initProfileInvoiceGenerator) { " + " if (window.initProfileInvoiceGenerator) { " +
" window.initProfileInvoiceGenerator(); " + " window.initProfileInvoiceGenerator(); " +
" console.log('Canvas initialized, now loading template...'); " + " console.log('Canvas initialized, now loading template...'); " +
" $0.$server.onCanvasReady(); " + " setTimeout(function() { " +
" $0.$server.onCanvasReady(); " +
" }, 200); " +
" } else { " + " } else { " +
" console.error('initProfileInvoiceGenerator not found'); " + " console.error('initProfileInvoiceGenerator not found'); " +
" } " + " } " +
@@ -848,27 +860,101 @@ public class EditProfileView extends HorizontalLayout {
panel.setSpacing(true); panel.setSpacing(true);
panel.setHeightFull(); panel.setHeightFull();
Span header = new Span("Textbausteine"); // Bereich 1: Rechnungselemente (Stammdaten)
header.getStyle() Span invoiceHeader = new Span("Rechnungselemente");
invoiceHeader.getStyle()
.set("font-weight", "bold") .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 // Draggable Templates
Div textBlock = createDraggableTemplate("Textfeld", VaadinIcon.TEXT_LABEL, "text"); Div textBlock = createDraggableTemplate("Textfeld", VaadinIcon.TEXT_LABEL, "text");
Div headerBlock = createDraggableTemplate("Überschrift", VaadinIcon.HEADER, "header"); Div headerBlock = createDraggableTemplate("Überschrift", VaadinIcon.HEADER, "header");
Div dateBlock = createDraggableTemplate("Datum", VaadinIcon.CALENDAR, "date"); Div dateBlock = createDraggableTemplate("Datum", VaadinIcon.CALENDAR, "date");
Div customerBlock = createDraggableTemplate("Kundeninfo", VaadinIcon.USER, "customer"); 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 amountBlock = createDraggableTemplate("Betrag", VaadinIcon.COIN_PILES, "amount");
Div lineBlock = createDraggableTemplate("Linie", VaadinIcon.LINE_V, "line"); Div lineBlock = createDraggableTemplate("Linie", VaadinIcon.LINE_V, "line");
Div imageBlock = createDraggableTemplate("Bild", VaadinIcon.PICTURE, "image"); Div imageBlock = createDraggableTemplate("Bild", VaadinIcon.PICTURE, "image");
panel.add(header, textBlock, headerBlock, dateBlock, customerBlock, companyBlock, amountBlock, lineBlock, panel.add(
imageBlock); invoiceHeader,
senderCompany, senderName, senderAddress, senderCity, senderEmail, senderPhone,
freeHeader,
textBlock, headerBlock, dateBlock, customerBlock, companyBlock, amountBlock, lineBlock, imageBlock
);
return panel; 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) { private Div createDraggableTemplate(String label, VaadinIcon icon, String type) {
Div template = new Div(); Div template = new Div();
template.setText(label); template.setText(label);
@@ -896,6 +982,7 @@ public class EditProfileView extends HorizontalLayout {
"this.addEventListener('dragstart', function(e) {" + "this.addEventListener('dragstart', function(e) {" +
" e.dataTransfer.setData('template-type', this.getAttribute('data-template-type'));" + " e.dataTransfer.setData('template-type', this.getAttribute('data-template-type'));" +
" e.dataTransfer.setData('template-label', this.getAttribute('data-template-label'));" + " e.dataTransfer.setData('template-label', this.getAttribute('data-template-label'));" +
" e.dataTransfer.setData('is-static', 'false');" +
" this.style.opacity = '0.5';" + " this.style.opacity = '0.5';" +
"});" + "});" +
"this.addEventListener('dragend', function(e) {" + "this.addEventListener('dragend', function(e) {" +
@@ -929,7 +1016,7 @@ public class EditProfileView extends HorizontalLayout {
@ClientCallable @ClientCallable
public void updatePropertiesPanel(String elementId, String elementType, String text, Double x, Double y, 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(() -> { getUI().ifPresent(ui -> ui.access(() -> {
propertiesPanelProfile.removeAll(); propertiesPanelProfile.removeAll();
@@ -939,7 +1026,7 @@ public class EditProfileView extends HorizontalLayout {
.set("font-size", "var(--lumo-font-size-l)"); .set("font-size", "var(--lumo-font-size-l)");
// Element Typ Anzeige // 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)"); typeLabel.getStyle().set("font-size", "var(--lumo-font-size-s)");
propertiesPanelProfile.add(header, typeLabel); propertiesPanelProfile.add(header, typeLabel);
@@ -949,12 +1036,18 @@ public class EditProfileView extends HorizontalLayout {
TextField textField = new TextField("Text"); TextField textField = new TextField("Text");
textField.setValue(text != null ? text : ""); textField.setValue(text != null ? text : "");
textField.setWidthFull(); textField.setWidthFull();
textField.addValueChangeListener(e -> { // Statische Elemente können nicht editiert werden
getElement().executeJs( if (Boolean.TRUE.equals(isStatic)) {
"if (window.updateProfileElementText) { window.updateProfileElementText('" + elementId textField.setReadOnly(true);
+ "', $0); }", textField.setHelperText("Text kommt aus Ihren Stammdaten");
e.getValue()); } else {
}); textField.addValueChangeListener(e -> {
getElement().executeJs(
"if (window.updateProfileElementText) { window.updateProfileElementText('" + elementId
+ "', $0); }",
e.getValue());
});
}
propertiesPanelProfile.add(textField); propertiesPanelProfile.add(textField);
} }
@@ -1113,21 +1206,16 @@ public class EditProfileView extends HorizontalLayout {
if (optionalTemplate.isPresent()) { if (optionalTemplate.isPresent()) {
String templateData = optionalTemplate.get().getTemplateData(); String templateData = optionalTemplate.get().getTemplateData();
if (templateData != null && !templateData.isEmpty()) { if (templateData != null && !templateData.isEmpty()) {
// Escape single quotes and newlines for JavaScript System.out.println("Loading template data: " + templateData.substring(0, Math.min(100, templateData.length())) + "...");
String escapedData = templateData
.replace("\\", "\\\\")
.replace("'", "\\'")
.replace("\n", "\\n")
.replace("\r", "");
getElement().executeJs( getElement().executeJs(
"setTimeout(function() { " + "setTimeout(function() { " +
" if (window.loadProfileTemplate && document.getElementById('invoice-canvas-container-profile')) { " + " if (window.loadProfileTemplate && document.getElementById('invoice-canvas-container-profile')) { " +
" console.log('Loading template into canvas...'); " + " console.log('Loading template into canvas...'); " +
" window.loadProfileTemplate('" + escapedData + "'); " + " window.loadProfileTemplate($0); " +
" } else { " + " } else { " +
" console.error('loadProfileTemplate or canvas not available'); " + " console.error('loadProfileTemplate or canvas not available'); " +
" } " + " } " +
"}, 100);"); "}, 300);", templateData);
} }
} else { } else {
System.out.println("No template found for user: " + currentUser.getId()); System.out.println("No template found for user: " + currentUser.getId());

View File

@@ -262,7 +262,7 @@ public class CustomerInvoiceService {
htmlBuilder.append("@page { size: A4; margin: 0; }"); 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("body { margin: 0; padding: 0; width: 210mm; height: 297mm; position: relative; font-family: Arial, sans-serif; }");
htmlBuilder.append(".element { position: absolute; }"); 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(".line { border-top: 1px solid #333; }");
htmlBuilder.append(".image { max-width: 100%; max-height: 100%; }"); htmlBuilder.append(".image { max-width: 100%; max-height: 100%; }");
htmlBuilder.append("</style>"); htmlBuilder.append("</style>");