diff --git a/src/main/bundles/dev.bundle b/src/main/bundles/dev.bundle index 75936dd..1b08a7c 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 7baa42d..122d525 100644 --- a/src/main/frontend/invoice-generator/profile-invoice-generator.js +++ b/src/main/frontend/invoice-generator/profile-invoice-generator.js @@ -48,14 +48,23 @@ window.initProfileInvoiceGenerator = function() { // Page dimensions var padding = 10; var pageX, pageY, pageWidth, pageHeight; + var zoomFactor = 1; + var basePageWidth = 595; // A4 width in pixels at 96 DPI + var basePageHeight = 842; // A4 height in pixels at 96 DPI function updatePageDimensions() { var w = canvas.width; var h = canvas.height; var availableWidth = w - padding * 2; var availableHeight = h - padding * 2; - pageWidth = Math.min(availableWidth, availableHeight * 0.707); - pageHeight = pageWidth / 0.707; + + // Calculate zoom factor based on available space + var zoomX = availableWidth / basePageWidth; + var zoomY = availableHeight / basePageHeight; + zoomFactor = Math.min(zoomX, zoomY); + + pageWidth = basePageWidth * zoomFactor; + pageHeight = basePageHeight * zoomFactor; pageX = (w - pageWidth) / 2; pageY = (h - pageHeight) / 2; } @@ -111,16 +120,17 @@ window.initProfileInvoiceGenerator = function() { ctx.lineWidth = 1; ctx.strokeRect(pageX, pageY, pageWidth, pageHeight); - // Grid + // Grid (scaled by zoom factor) ctx.strokeStyle = 'rgba(200, 200, 200, 0.3)'; ctx.lineWidth = 0.5; - for (var x = pageX; x <= pageX + pageWidth; x += gridSize) { + var scaledGridSize = gridSize * zoomFactor; + for (var x = pageX; x <= pageX + pageWidth; x += scaledGridSize) { ctx.beginPath(); ctx.moveTo(x, pageY); ctx.lineTo(x, pageY + pageHeight); ctx.stroke(); } - for (var y = pageY; y <= pageY + pageHeight; y += gridSize) { + for (var y = pageY; y <= pageY + pageHeight; y += scaledGridSize) { ctx.beginPath(); ctx.moveTo(pageX, y); ctx.lineTo(pageX + pageWidth, y); @@ -141,14 +151,16 @@ 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; + // Apply zoom factor to position and size + var x = pageX + (el.x * zoomFactor); + var y = pageY + (el.y * zoomFactor); + var w = (el.width || 100) * zoomFactor; + var h = (el.height || 30) * zoomFactor; + var fontSize = (el.fontSize || 14) * zoomFactor; if (el.type === 'line') { ctx.strokeStyle = el.color || '#333333'; - ctx.lineWidth = 1; + ctx.lineWidth = Math.max(1, zoomFactor); ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + w, y); @@ -159,29 +171,32 @@ window.initProfileInvoiceGenerator = function() { ctx.strokeStyle = '#999999'; ctx.strokeRect(x, y, w, h); ctx.fillStyle = '#666666'; - ctx.font = '12px Arial'; + ctx.font = Math.max(8, 12 * zoomFactor) + 'px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('Bild', x + w / 2, y + h / 2); } else { // Text elements - ctx.font = (el.fontStyle || '') + ' ' + (el.fontSize || 14) + 'px Arial'; + ctx.font = (el.fontStyle || '') + ' ' + fontSize + 'px Arial'; ctx.fillStyle = el.color || '#333333'; - ctx.textBaseline = 'top'; + + var lines = (el.text || '').split('\n'); + var lineHeight = fontSize * 1.2; + var totalTextHeight = lines.length * lineHeight; + + // Vertically center the text in the element + var ty = y + (h - totalTextHeight) / 2; // Draw background highlight for static elements if (el.isStatic) { - var textWidth = ctx.measureText(el.text || '').width + 10; + var textWidth = ctx.measureText(el.text || '').width + (10 * zoomFactor); ctx.fillStyle = 'rgba(25, 118, 210, 0.1)'; // Light blue background - ctx.fillRect(x - 3, y - 2, Math.max(w, textWidth), h); + ctx.fillRect(x - (3 * zoomFactor), y - (2 * zoomFactor), 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 ty = y; + ctx.font = (el.fontStyle || '') + ' ' + fontSize + 'px Arial'; + ctx.textBaseline = 'top'; lines.forEach(function(line) { ctx.fillText(line, x, ty); @@ -193,22 +208,27 @@ window.initProfileInvoiceGenerator = function() { } function drawSelection(el) { - var x = pageX + el.x; - var y = pageY + el.y; - var w = el.width || 100; - var h = el.height || 30; - var hs = 8; // handle size + var x = pageX + (el.x * zoomFactor); + var y = pageY + (el.y * zoomFactor); + var w = (el.width || 100) * zoomFactor; + var h = (el.height || 30) * zoomFactor; ctx.strokeStyle = '#1976d2'; - ctx.lineWidth = 2; + ctx.lineWidth = Math.max(1, 2 * zoomFactor); ctx.setLineDash([5, 3]); ctx.strokeRect(x, y, w, h); ctx.setLineDash([]); + // Don't show resize handles for static elements + if (el.isStatic) { + return; + } + // Handles + var hs = Math.max(6, 8 * zoomFactor); // handle size scaled ctx.fillStyle = '#ffffff'; ctx.strokeStyle = '#1976d2'; - ctx.lineWidth = 2; + ctx.lineWidth = Math.max(1, 2 * zoomFactor); var positions = [ [x - hs/2, y - hs/2], @@ -228,10 +248,10 @@ window.initProfileInvoiceGenerator = function() { } function hitTest(x, y, el) { - var ex = pageX + el.x; - var ey = pageY + el.y; - var ew = el.width || 100; - var eh = el.height || 30; + var ex = pageX + (el.x * zoomFactor); + var ey = pageY + (el.y * zoomFactor); + var ew = (el.width || 100) * zoomFactor; + var eh = (el.height || 30) * zoomFactor; return x >= ex && x <= ex + ew && y >= ey && y <= ey + eh; } @@ -271,15 +291,16 @@ window.initProfileInvoiceGenerator = function() { var y = e.clientY - rect.top; if (isDragging && selectedElement) { - var dx = x - dragStart.x; - var dy = y - dragStart.y; + // Adjust mouse movement by zoom factor to get stored coordinates + var dx = (x - dragStart.x) / zoomFactor; + var dy = (y - dragStart.y) / zoomFactor; var newX = elementStart.x + dx; var newY = elementStart.y + dy; - // Constrain to page - newX = Math.max(0, Math.min(newX, pageWidth - (selectedElement.width || 100))); - newY = Math.max(0, Math.min(newY, pageHeight - (selectedElement.height || 30))); + // Constrain to page (using base coordinates, not zoomed) + newX = Math.max(0, Math.min(newX, basePageWidth - (selectedElement.width || 100))); + newY = Math.max(0, Math.min(newY, basePageHeight - (selectedElement.height || 30))); // Snap to grid selectedElement.x = snapToGrid(newX); @@ -325,7 +346,7 @@ window.initProfileInvoiceGenerator = function() { e.preventDefault(); break; case 'ArrowDown': - selectedElement.y = Math.min(pageHeight - (selectedElement.height || 30), selectedElement.y + step); + selectedElement.y = Math.min(basePageHeight - (selectedElement.height || 30), selectedElement.y + step); moved = true; e.preventDefault(); break; @@ -335,7 +356,7 @@ window.initProfileInvoiceGenerator = function() { e.preventDefault(); break; case 'ArrowRight': - selectedElement.x = Math.min(pageWidth - (selectedElement.width || 100), selectedElement.x + step); + selectedElement.x = Math.min(basePageWidth - (selectedElement.width || 100), selectedElement.x + step); moved = true; e.preventDefault(); break; @@ -362,9 +383,9 @@ window.initProfileInvoiceGenerator = function() { elementCounter++; var id = 'element-' + elementCounter; - // Convert to page coordinates - var x = dropX - pageX - 50; - var y = dropY - pageY - 15; + // Convert to page coordinates (adjusting for zoom factor) + var x = (dropX - pageX - 50 * zoomFactor) / zoomFactor; + var y = (dropY - pageY - 15 * zoomFactor) / zoomFactor; x = Math.max(10, x); y = Math.max(10, y); x = snapToGrid(x); 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 3c72f54..0ec816d 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java @@ -389,7 +389,13 @@ public class EditProfileView extends HorizontalLayout { return; } try { - String templateData = result.toString(); + String templateData; + if (result instanceof elemental.json.JsonValue) { + elemental.json.JsonValue jsonValue = (elemental.json.JsonValue) result; + templateData = jsonValue.toJson(); + } else { + templateData = result.toString(); + } invoiceTemplateService.saveTemplate(currentUser.getId().toString(), templateData); Notification.show("Template erfolgreich gespeichert", 3000, Notification.Position.BOTTOM_CENTER); } catch (Exception ex) { diff --git a/src/main/java/de/assecutor/votianlt/service/CustomerInvoiceService.java b/src/main/java/de/assecutor/votianlt/service/CustomerInvoiceService.java index c993064..c942e59 100644 --- a/src/main/java/de/assecutor/votianlt/service/CustomerInvoiceService.java +++ b/src/main/java/de/assecutor/votianlt/service/CustomerInvoiceService.java @@ -261,8 +261,8 @@ public class CustomerInvoiceService { htmlBuilder.append(""); @@ -293,10 +293,13 @@ public class CustomerInvoiceService { htmlBuilder.append("width:").append(String.format(java.util.Locale.US, "%.2f", mmWidth)).append("mm;"); htmlBuilder.append("height:").append(String.format(java.util.Locale.US, "%.2f", mmHeight)).append("mm;"); htmlBuilder.append("font-size:").append(fontSize).append("pt;"); + htmlBuilder.append("line-height:").append(String.format(java.util.Locale.US, "%.2f", fontSize * 1.2)).append("pt;"); htmlBuilder.append("color:").append(color).append(";"); if (!fontStyle.isEmpty()) { if (fontStyle.contains("bold")) htmlBuilder.append("font-weight:bold;"); } + // Vertically center content + htmlBuilder.append("display:flex;align-items:center;"); htmlBuilder.append("'"); htmlBuilder.append(">"); @@ -308,7 +311,7 @@ public class CustomerInvoiceService { .replace("'", "'"); if ("line".equals(type)) { - htmlBuilder.append("
"); + htmlBuilder.append("
"); } else if ("image".equals(type)) { if (element.has("imageData")) { String imageData = element.get("imageData").asText(); @@ -317,7 +320,8 @@ public class CustomerInvoiceService { htmlBuilder.append("[Bild]"); } } else { - htmlBuilder.append(text); + // Wrap text in a span to prevent flexbox issues + htmlBuilder.append("").append(text).append(""); } htmlBuilder.append("");