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.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);
}
});
}

View File

@@ -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());

View File

@@ -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("</style>");