Erweiterungen

This commit is contained in:
2026-02-18 17:51:06 +01:00
parent 19ac94e0b8
commit 8b6412cb0e
5 changed files with 401 additions and 59 deletions

View File

@@ -239,6 +239,9 @@ window.initProfileInvoiceGenerator = function() {
ctx.textBaseline = 'middle';
ctx.fillText('Bild', x + w / 2, y + h / 2);
}
} else if (el.variable === 'services.list') {
// Draw services list as a table
drawServicesTable(el, x, y, w, h, fontSize);
} else {
// Text elements
var lines = (el.text || '').split('\n');
@@ -274,6 +277,169 @@ window.initProfileInvoiceGenerator = function() {
ctx.restore();
}
// Draw services list as a table with columns: Name, Steuersatz, Nettobetrag
// Plus summary section below: Nettosumme, USt, Gesamtsumme
function drawServicesTable(el, x, y, w, h, fontSize) {
var lineHeight = fontSize * 1.4;
var padding = 4 * zoomFactor;
var rowHeight = lineHeight + padding * 2;
var summaryRowHeight = fontSize * 1.6;
var summaryGap = fontSize * 0.5;
// Column widths (percentages of total width)
var colNameWidth = w * 0.55; // 55% for Name (left-aligned)
var colVatWidth = w * 0.20; // 20% for Steuersatz (right-aligned)
var colNetWidth = w * 0.25; // 25% for Nettobetrag (right-aligned)
var colNameX = x;
var colVatX = x + colNameWidth;
var colNetX = colVatX + colVatWidth;
// Calculate actual content height based on table + summary
var tableOnlyHeight = rowHeight * 4; // Header + 3 data rows
var summaryOnlyHeight = summaryGap + (summaryRowHeight * 3) + summaryGap + summaryRowHeight + summaryGap;
var calculatedContentHeight = tableOnlyHeight + summaryOnlyHeight;
// Ensure background covers at least the element height or the calculated content
var bgHeight = Math.max(h, calculatedContentHeight * zoomFactor);
// Draw orange background highlight for entire service variable element
var bgPadding = 3 * zoomFactor;
ctx.fillStyle = 'rgba(255, 152, 0, 0.15)';
ctx.fillRect(x - bgPadding, y - (2 * zoomFactor), w + (2 * bgPadding), bgHeight + (4 * zoomFactor));
// Draw table header (overlays the background)
ctx.fillStyle = '#f5f5f5';
ctx.fillRect(x, y, w, rowHeight);
// Header border
ctx.strokeStyle = '#cccccc';
ctx.lineWidth = Math.max(0.5, zoomFactor);
ctx.beginPath();
ctx.moveTo(x, y + rowHeight);
ctx.lineTo(x + w, y + rowHeight);
ctx.stroke();
// Header text
ctx.fillStyle = '#333333';
ctx.font = 'bold ' + fontSize + 'px Arial';
ctx.textBaseline = 'middle';
// Name column header (left-aligned)
ctx.textAlign = 'left';
ctx.fillText('Name', colNameX + padding, y + rowHeight / 2);
// Steuersatz column header (right-aligned)
ctx.textAlign = 'right';
ctx.fillText('Steuersatz', colVatX + colVatWidth - padding, y + rowHeight / 2);
// Nettobetrag column header (right-aligned)
ctx.fillText('Nettobetrag', colNetX + colNetWidth - padding, y + rowHeight / 2);
// Sample data rows (placeholder)
var sampleData = [
{ name: 'Umzugsleistung inkl. Verpackung', vat: '19%', net: '450,00 €' },
{ name: 'Entsorgung Möbel', vat: '19%', net: '85,00 €' },
{ name: 'Montage/De-Montage', vat: '19%', net: '120,00 €' }
];
var currentY = y + rowHeight;
// Draw sample rows
ctx.font = fontSize + 'px Arial';
sampleData.forEach(function(row, index) {
// Draw row background (alternating)
if (index % 2 === 1) {
ctx.fillStyle = 'rgba(0,0,0,0.02)';
ctx.fillRect(x, currentY, w, rowHeight);
}
// Row bottom border
ctx.strokeStyle = '#eeeeee';
ctx.lineWidth = Math.max(0.5, zoomFactor * 0.5);
ctx.beginPath();
ctx.moveTo(x, currentY + rowHeight);
ctx.lineTo(x + w, currentY + rowHeight);
ctx.stroke();
// Draw cell text
ctx.fillStyle = '#333333';
// Name (left-aligned)
ctx.textAlign = 'left';
ctx.fillText(row.name, colNameX + padding, currentY + rowHeight / 2);
// Steuersatz (right-aligned)
ctx.textAlign = 'right';
ctx.fillText(row.vat, colVatX + colVatWidth - padding, currentY + rowHeight / 2);
// Nettobetrag (right-aligned)
ctx.fillText(row.net, colNetX + colNetWidth - padding, currentY + rowHeight / 2);
currentY += rowHeight;
});
// Draw column separator lines
ctx.strokeStyle = '#e0e0e0';
ctx.lineWidth = Math.max(0.5, zoomFactor * 0.5);
ctx.beginPath();
// Line between Name and Steuersatz
ctx.moveTo(colVatX, y);
ctx.lineTo(colVatX, currentY);
// Line between Steuersatz and Nettobetrag
ctx.moveTo(colNetX, y);
ctx.lineTo(colNetX, currentY);
ctx.stroke();
// Draw outer border around table
ctx.strokeStyle = '#cccccc';
ctx.lineWidth = Math.max(0.5, zoomFactor);
ctx.strokeRect(x, y, w, currentY - y);
// Draw summary section below the table
var summaryY = currentY + summaryGap;
// Column positions for summary section
var labelX = x + colNameWidth + colVatWidth * 0.3; // Label column (left-aligned)
var valueX = x + w - padding; // Value column (right-aligned)
// Calculate totals from sample data
var netTotal = 655.00; // 450 + 85 + 120
var vatRate = 0.19;
var vatTotal = 124.45; // 655 * 0.19
var grossTotal = 779.45; // 655 + 124.45
// Draw summary lines
ctx.textBaseline = 'middle';
// Nettosumme - label left, value right
ctx.fillStyle = '#333333';
ctx.font = fontSize + 'px Arial';
ctx.textAlign = 'left';
ctx.fillText('Nettosumme:', labelX, summaryY + summaryRowHeight / 2);
ctx.font = 'bold ' + fontSize + 'px Arial';
ctx.textAlign = 'right';
ctx.fillText(netTotal.toFixed(2).replace('.', ',') + ' €', valueX, summaryY + summaryRowHeight / 2);
summaryY += summaryRowHeight;
// Umsatzsteuer - label left, value right
ctx.font = fontSize + 'px Arial';
ctx.textAlign = 'left';
ctx.fillText('zzgl. 19% USt:', labelX, summaryY + summaryRowHeight / 2);
ctx.font = 'bold ' + fontSize + 'px Arial';
ctx.textAlign = 'right';
ctx.fillText(vatTotal.toFixed(2).replace('.', ',') + ' €', valueX, summaryY + summaryRowHeight / 2);
summaryY += summaryRowHeight;
// Gesamtsumme - label left, value right
summaryY += summaryGap; // Extra gap before total
ctx.fillStyle = '#000000';
ctx.font = 'bold ' + (fontSize * 1.1) + 'px Arial';
ctx.textAlign = 'left';
ctx.fillText('Gesamtsumme:', labelX, summaryY + summaryRowHeight / 2);
ctx.textAlign = 'right';
ctx.fillText(grossTotal.toFixed(2).replace('.', ',') + ' €', valueX, summaryY + summaryRowHeight / 2);
}
function drawSelection(el) {
var x = pageX + (el.x * zoomFactor);
var y = pageY + (el.y * zoomFactor);
@@ -285,20 +451,33 @@ window.initProfileInvoiceGenerator = function() {
var fontSize = (el.fontSize || 14) * zoomFactor;
ctx.font = (el.fontStyle || '') + ' ' + fontSize + 'px Arial';
var lines = (el.text || '').split('\n');
var maxLineWidth = 0;
lines.forEach(function(line) {
var lineWidth = ctx.measureText(line).width;
maxLineWidth = Math.max(maxLineWidth, lineWidth);
});
// Use the larger of defined width or actual text width
w = Math.max(w, maxLineWidth + (10 * zoomFactor));
// Calculate actual height based on number of lines
var lineHeight = fontSize * 1.2;
var textHeight = lines.length * lineHeight;
h = Math.max(h, textHeight + (6 * zoomFactor));
// For services.list, calculate table height including summary section
if (el.variable === 'services.list') {
var lineHeight = fontSize * 1.4;
var padding = 4 * zoomFactor;
var rowHeight = lineHeight + padding * 2;
var tableHeight = rowHeight + 3 * rowHeight; // Header + 3 rows
var summaryRowHeight = fontSize * 1.6;
var summaryGap = fontSize * 0.5;
var summaryHeight = summaryGap + (summaryRowHeight * 3) + summaryGap + summaryRowHeight + summaryGap;
var totalHeight = tableHeight + summaryHeight;
h = Math.max(h, totalHeight);
} else {
var lines = (el.text || '').split('\n');
var maxLineWidth = 0;
lines.forEach(function(line) {
var lineWidth = ctx.measureText(line).width;
maxLineWidth = Math.max(maxLineWidth, lineWidth);
});
// Use the larger of defined width or actual text width
w = Math.max(w, maxLineWidth + (10 * zoomFactor));
// Calculate actual height based on number of lines
var lineHeight = fontSize * 1.2;
var textHeight = lines.length * lineHeight;
h = Math.max(h, textHeight + (6 * zoomFactor));
}
}
ctx.strokeStyle = '#1976d2';
@@ -307,8 +486,8 @@ window.initProfileInvoiceGenerator = function() {
ctx.strokeRect(x, y, w, h);
ctx.setLineDash([]);
// Don't show resize handles for static elements
if (el.isStatic) {
// Don't show resize handles for static elements (except services.list)
if (el.isStatic && el.variable !== 'services.list') {
return;
}
@@ -346,18 +525,30 @@ window.initProfileInvoiceGenerator = function() {
var fontSize = (el.fontSize || 14) * zoomFactor;
ctx.font = (el.fontStyle || '') + ' ' + fontSize + 'px Arial';
var lines = (el.text || '').split('\n');
var maxLineWidth = 0;
lines.forEach(function(line) {
var lineWidth = ctx.measureText(line).width;
maxLineWidth = Math.max(maxLineWidth, lineWidth);
});
ew = Math.max(ew, maxLineWidth + (10 * zoomFactor));
var lineHeight = fontSize * 1.2;
var textHeight = lines.length * lineHeight;
eh = Math.max(eh, textHeight + (6 * zoomFactor));
// For services.list, calculate table height
if (el.variable === 'services.list') {
var lineHeight = fontSize * 1.4;
var padding = 4 * zoomFactor;
var rowHeight = lineHeight + padding * 2;
var tableHeight = rowHeight + 3 * rowHeight; // Header + 3 rows
var summaryRowHeight = fontSize * 1.6;
var summaryGap = fontSize * 0.5;
var summaryHeight = summaryGap + (summaryRowHeight * 3) + summaryGap + summaryRowHeight + summaryGap;
eh = Math.max(eh, tableHeight + summaryHeight);
} else {
var lines = (el.text || '').split('\n');
var maxLineWidth = 0;
lines.forEach(function(line) {
var lineWidth = ctx.measureText(line).width;
maxLineWidth = Math.max(maxLineWidth, lineWidth);
});
ew = Math.max(ew, maxLineWidth + (10 * zoomFactor));
var lineHeight = fontSize * 1.2;
var textHeight = lines.length * lineHeight;
eh = Math.max(eh, textHeight + (6 * zoomFactor));
}
}
return x >= ex && x <= ex + ew && y >= ey && y <= ey + eh;
@@ -370,12 +561,28 @@ window.initProfileInvoiceGenerator = function() {
// Helper function to check if a point is on a resize handle
function getResizeHandle(x, y, el) {
if (!el || el.isStatic) return -1;
if (!el) return -1;
// Allow resizing for services.list even though it's static
if (el.isStatic && el.variable !== 'services.list') return -1;
var ex = pageX + (el.x * zoomFactor);
var ey = pageY + (el.y * zoomFactor);
var ew = (el.width || 100) * zoomFactor;
var eh = (el.height || 30) * zoomFactor;
// For services.list, calculate table height including summary
if (el.variable === 'services.list') {
var fontSize = (el.fontSize || 14) * zoomFactor;
var lineHeight = fontSize * 1.4;
var padding = 4 * zoomFactor;
var rowHeight = lineHeight + padding * 2;
var tableHeight = rowHeight + 3 * rowHeight; // Header + 3 rows
var summaryRowHeight = fontSize * 1.6;
var summaryGap = fontSize * 0.5;
var summaryHeight = summaryGap + (summaryRowHeight * 3) + summaryGap + summaryRowHeight + summaryGap;
eh = Math.max(eh, tableHeight + summaryHeight);
}
var hs = Math.max(6, 8 * zoomFactor);
var halfHs = hs / 2;
@@ -657,7 +864,21 @@ window.initProfileInvoiceGenerator = function() {
el.text = staticText;
el.color = '#000000'; // Black text for static elements
el.fontStyle = 'bold';
el.height = calculateHeight(el.fontSize, 1);
// For services.list, set larger dimensions for the table with summary section
if (variable === 'services.list') {
el.width = 450;
var lineHeight = el.fontSize * 1.4;
var padding = 4;
var rowHeight = lineHeight + padding * 2;
var tableHeight = rowHeight + 3 * rowHeight; // Header + 3 sample rows
var summaryRowHeight = el.fontSize * 1.6;
var summaryGap = el.fontSize * 0.5;
var summaryHeight = summaryGap + (summaryRowHeight * 3) + summaryGap + summaryRowHeight + summaryGap;
var totalHeight = tableHeight + summaryHeight;
el.height = Math.round(totalHeight);
} else {
el.height = calculateHeight(el.fontSize, 1);
}
} else {
switch (type) {
case 'text':