Erweiterungen
This commit is contained in:
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user