PDF Export Design an neues Design angepasst und PDF größe verringert
This commit is contained in:
1 parent
004003cd17
commit
c055091dc7
12 files changed
+93
-43
No files matched your search
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+89
-39
@@ -2246,9 +2246,9 @@ async function exportPDF(){
|
|||||||
container.style.width = '1200px';
|
container.style.width = '1200px';
|
||||||
container.style.background = '#fff';
|
container.style.background = '#fff';
|
||||||
container.style.padding = '20px';
|
container.style.padding = '20px';
|
||||||
container.style.fontFamily = 'Arial, sans-serif';
|
container.style.fontFamily = '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
||||||
container.style.fontSize = '10px';
|
container.style.fontSize = '10px';
|
||||||
container.style.color = '#1a1a1a';
|
container.style.color = 'var(--text)';
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
|
|
||||||
const grouped = peopleByRole(w);
|
const grouped = peopleByRole(w);
|
||||||
@@ -2283,16 +2283,16 @@ async function exportPDF(){
|
|||||||
cover.style.flexDirection = 'column';
|
cover.style.flexDirection = 'column';
|
||||||
cover.style.justifyContent = 'center';
|
cover.style.justifyContent = 'center';
|
||||||
cover.style.textAlign = 'center';
|
cover.style.textAlign = 'center';
|
||||||
cover.style.fontFamily = 'Arial, sans-serif';
|
cover.style.fontFamily = '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
||||||
const logoHtml = logoDataUrl
|
const logoHtml = logoDataUrl
|
||||||
? `<div style="margin-bottom:28px;"><img src="${logoDataUrl}" alt="Logo" style="width:200px; height:200px; object-fit:contain;"></div>`
|
? `<div style="margin-bottom:28px;"><img src="${logoDataUrl}" alt="Logo" style="width:200px; height:200px; object-fit:contain;"></div>`
|
||||||
: `<div style="font-size:80px; margin-bottom:28px;">⚡</div>`;
|
: `<div style="font-size:80px; margin-bottom:28px;">⚡</div>`;
|
||||||
cover.innerHTML = `
|
cover.innerHTML = `
|
||||||
${logoHtml}
|
${logoHtml}
|
||||||
<h1 style="font-size:32px; margin:0 0 16px 0; color:#1e3a5f;">Projektübersicht ${DEPARTMENTS[currentDepartment].label}</h1>
|
<h1 style="font-size:32px; margin:0 0 16px 0; color:#1d1d1f;">Projektübersicht ${DEPARTMENTS[currentDepartment].label}</h1>
|
||||||
<div style="font-size:18px; color:#4a5568; margin-bottom:48px;">${esc(w.label)}</div>
|
<div style="font-size:18px; color:#6e6e73; margin-bottom:48px;">${esc(w.label)}</div>
|
||||||
${w.date ? `<div style="font-size:14px; color:#718096; margin-bottom:12px;">Datum: ${esc(formatWeekDate(w.date))}</div>` : ''}
|
${w.date ? `<div style="font-size:14px; color:#6e6e73; margin-bottom:12px;">Datum: ${esc(formatWeekDate(w.date))}</div>` : ''}
|
||||||
<div style="font-size:12px; color:#a0aec0; margin-top:60px;">Erstellt am ${new Date().toLocaleDateString('de-DE')}</div>
|
<div style="font-size:12px; color:#6e6e73; margin-top:60px;">Erstellt am ${new Date().toLocaleDateString('de-DE')}</div>
|
||||||
`;
|
`;
|
||||||
container.appendChild(cover);
|
container.appendChild(cover);
|
||||||
|
|
||||||
@@ -2313,11 +2313,14 @@ async function exportPDF(){
|
|||||||
const landscapePageW = pageHeightPx - marginPx * 2;
|
const landscapePageW = pageHeightPx - marginPx * 2;
|
||||||
const landscapePageH = pageWidthPx - marginPx * 2;
|
const landscapePageH = pageWidthPx - marginPx * 2;
|
||||||
|
|
||||||
|
// JPEG quality (0.1 - 1.0) - lower = smaller file
|
||||||
|
const JPEG_QUALITY = 1;
|
||||||
|
|
||||||
async function addImageToLandscapePage(canvas){
|
async function addImageToLandscapePage(canvas){
|
||||||
const imgW = landscapePageW;
|
const imgW = landscapePageW;
|
||||||
const imgH = (canvas.height * imgW) / canvas.width;
|
const imgH = (canvas.height * imgW) / canvas.width;
|
||||||
pdf.addPage([pageHeightPx, pageWidthPx], 'landscape');
|
pdf.addPage([pageHeightPx, pageWidthPx], 'landscape');
|
||||||
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', marginPx, marginPx, imgW, imgH);
|
pdf.addImage(canvas.toDataURL('image/jpeg', JPEG_QUALITY), 'JPEG', marginPx, marginPx, imgW, imgH);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function drawBlockOnLandscapePage(blockHTML){
|
async function drawBlockOnLandscapePage(blockHTML){
|
||||||
@@ -2328,7 +2331,7 @@ async function exportPDF(){
|
|||||||
wrapper.style.background = '#fff';
|
wrapper.style.background = '#fff';
|
||||||
wrapper.innerHTML = blockHTML;
|
wrapper.innerHTML = blockHTML;
|
||||||
container.appendChild(wrapper);
|
container.appendChild(wrapper);
|
||||||
const canvas = await html2canvas(wrapper, { scale: 2, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
const canvas = await html2canvas(wrapper, { scale: 1.5, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
||||||
const imgH = (canvas.height * landscapePageW) / canvas.width;
|
const imgH = (canvas.height * landscapePageW) / canvas.width;
|
||||||
wrapper.remove();
|
wrapper.remove();
|
||||||
return { canvas, imgH };
|
return { canvas, imgH };
|
||||||
@@ -2336,12 +2339,12 @@ async function exportPDF(){
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Deckblatt (Hochkant) zeichnen
|
// Deckblatt (Hochkant) zeichnen
|
||||||
const coverCanvas = await html2canvas(cover, { scale: 2, useCORS: true, backgroundColor: '#ffffff', width: 595, height: 842 });
|
const coverCanvas = await html2canvas(cover, { scale: 1.5, useCORS: true, backgroundColor: '#ffffff', width: 595, height: 842 });
|
||||||
if(pdf.internal.getNumberOfPages() > 0) pdf.addPage();
|
if(pdf.internal.getNumberOfPages() > 0) pdf.addPage();
|
||||||
const coverW = pageWidthPx - marginPx * 2;
|
const coverW = pageWidthPx - marginPx * 2;
|
||||||
const coverH = (coverCanvas.height * coverW) / coverCanvas.width;
|
const coverH = (coverCanvas.height * coverW) / coverCanvas.width;
|
||||||
const coverX = marginPx + (coverW - (coverCanvas.width * coverW / coverCanvas.width)) / 2;
|
const coverX = marginPx + (coverW - (coverCanvas.width * coverW / coverCanvas.width)) / 2;
|
||||||
pdf.addImage(coverCanvas.toDataURL('image/png'), 'PNG', marginPx, marginPx + ((pageHeightPx - marginPx * 2 - coverH) / 2), coverW, coverH);
|
pdf.addImage(coverCanvas.toDataURL('image/jpeg', JPEG_QUALITY), 'JPEG', marginPx, marginPx + ((pageHeightPx - marginPx * 2 - coverH) / 2), coverW, coverH);
|
||||||
|
|
||||||
const headerRows = buildPdfTableHeaderRows(allPeople);
|
const headerRows = buildPdfTableHeaderRows(allPeople);
|
||||||
|
|
||||||
@@ -2356,7 +2359,7 @@ async function exportPDF(){
|
|||||||
// Hilfsfunktion: misst Höhe einer Tabelle mit gegebenen Zeilen
|
// Hilfsfunktion: misst Höhe einer Tabelle mit gegebenen Zeilen
|
||||||
async function measureBlockWithRows(titleRow, bodyRows){
|
async function measureBlockWithRows(titleRow, bodyRows){
|
||||||
const wrapper = renderSinglePdfBlock(container, headerRows, titleRow, bodyRows);
|
const wrapper = renderSinglePdfBlock(container, headerRows, titleRow, bodyRows);
|
||||||
const canvas = await html2canvas(wrapper, { scale: 2, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
const canvas = await html2canvas(wrapper, { scale: 1.5, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
||||||
const imgH = (canvas.height * landscapePageW) / canvas.width;
|
const imgH = (canvas.height * landscapePageW) / canvas.width;
|
||||||
wrapper.remove();
|
wrapper.remove();
|
||||||
return { canvas, imgH };
|
return { canvas, imgH };
|
||||||
@@ -2375,7 +2378,10 @@ async function exportPDF(){
|
|||||||
|
|
||||||
for(let i = 0; i < projects.length; i++){
|
for(let i = 0; i < projects.length; i++){
|
||||||
const rowHTML = buildPdfProjectRow(projects[i], allPeople);
|
const rowHTML = buildPdfProjectRow(projects[i], allPeople);
|
||||||
const testRows = currentRows + rowHTML;
|
// Alternating row background to match project board design
|
||||||
|
const rowBg = i % 2 === 0 ? ' style="background:#fafafc"' : '';
|
||||||
|
const rowHTMLWithBg = rowHTML.replace('<tr>', `<tr${rowBg}>`);
|
||||||
|
const testRows = currentRows + rowHTMLWithBg;
|
||||||
const { imgH: testImgH } = await measureBlockWithRows(titleRow, testRows);
|
const { imgH: testImgH } = await measureBlockWithRows(titleRow, testRows);
|
||||||
|
|
||||||
// Wenn die aktuelle Seite überschritten würde und wir bereits Zeilen haben,
|
// Wenn die aktuelle Seite überschritten würde und wir bereits Zeilen haben,
|
||||||
@@ -2396,7 +2402,7 @@ async function exportPDF(){
|
|||||||
// Projektboard-Blöcke zeichnen
|
// Projektboard-Blöcke zeichnen
|
||||||
for(const block of groupBlocks){
|
for(const block of groupBlocks){
|
||||||
const wrapper = renderSinglePdfBlock(container, headerRows, block.titleRow, block.bodyRows);
|
const wrapper = renderSinglePdfBlock(container, headerRows, block.titleRow, block.bodyRows);
|
||||||
const canvas = await html2canvas(wrapper, { scale: 2, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
const canvas = await html2canvas(wrapper, { scale: 1.5, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
||||||
await addImageToLandscapePage(canvas);
|
await addImageToLandscapePage(canvas);
|
||||||
wrapper.remove();
|
wrapper.remove();
|
||||||
}
|
}
|
||||||
@@ -2424,25 +2430,36 @@ async function exportPDF(){
|
|||||||
|
|
||||||
// buildPdfTableHeaderRows: baut die Kopfzeilen der Projektübersichts-Tabelle als HTML-String (zwei Zeilen).
|
// buildPdfTableHeaderRows: baut die Kopfzeilen der Projektübersichts-Tabelle als HTML-String (zwei Zeilen).
|
||||||
function buildPdfTableHeaderRows(allPeople){
|
function buildPdfTableHeaderRows(allPeople){
|
||||||
let h1 = `<tr><td rowspan="2" style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Pr.-Nr.</td><td rowspan="2" style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Bezeichnung</td><td rowspan="2" style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Status</td>`;
|
let h1 = `<tr><td rowspan="2" style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Pr.-Nr.</td><td rowspan="2" style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Bezeichnung</td><td rowspan="2" style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Status</td>`;
|
||||||
const grouped = peopleByRole(currentWeek());
|
const grouped = peopleByRole(currentWeek());
|
||||||
ROLES.forEach(r => {
|
ROLES.forEach(r => {
|
||||||
const count = Math.max(1, grouped[r].length);
|
const count = Math.max(1, grouped[r].length);
|
||||||
h1 += `<td class="role-header role-${r.toLowerCase()}" colspan="${count}" style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">${r}</td>`;
|
// Rollen-spezifische Hintergrundfarben aus dem Projektboard
|
||||||
|
let roleBg;
|
||||||
|
if(r === 'Planung') roleBg = 'rgb(212, 245, 224)';
|
||||||
|
else if(r === 'Projektleitung') roleBg = 'rgb(255, 219, 219)';
|
||||||
|
else if(r === 'Bauüberwachung') roleBg = 'rgb(203, 238, 255)';
|
||||||
|
else roleBg = '#f0f0f2';
|
||||||
|
h1 += `<td class="role-header role-${r.toLowerCase()}" colspan="${count}" style="border:1px solid #e8e8ed;background:${roleBg};color:#1d1d1f;padding:6px;font-weight:bold;">${r}</td>`;
|
||||||
});
|
});
|
||||||
h1 += `<td colspan="${LPHS.length}" style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Leistungsphasen (%)</td></tr>`;
|
h1 += `<td colspan="${LPHS.length}" style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Leistungsphasen (%)</td></tr>`;
|
||||||
|
|
||||||
let h2 = `<tr>`;
|
let h2 = `<tr>`;
|
||||||
ROLES.forEach(r => {
|
ROLES.forEach(r => {
|
||||||
|
let roleBg;
|
||||||
|
if(r === 'Planung') roleBg = 'rgb(212, 245, 224)';
|
||||||
|
else if(r === 'Projektleitung') roleBg = 'rgb(255, 219, 219)';
|
||||||
|
else if(r === 'Bauüberwachung') roleBg = 'rgb(203, 238, 255)';
|
||||||
|
else roleBg = '#fafafc';
|
||||||
if(grouped[r].length === 0){
|
if(grouped[r].length === 0){
|
||||||
h2 += `<td style="border:1px solid #4a5568;background:#cbd5e0;padding:4px;font-weight:bold;">–</td>`;
|
h2 += `<td style="border:1px solid #e8e8ed;background:${roleBg};padding:4px;font-weight:bold;">–</td>`;
|
||||||
} else {
|
} else {
|
||||||
grouped[r].forEach(entry => {
|
grouped[r].forEach(entry => {
|
||||||
h2 += `<td style="border:1px solid #4a5568;background:#cbd5e0;padding:4px;font-weight:bold;">${esc(entry.person.name)}</td>`;
|
h2 += `<td style="border:1px solid #e8e8ed;background:${roleBg};padding:4px;font-weight:bold;">${esc(entry.person.name)}</td>`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
LPHS.forEach(l => h2 += `<td style="border:1px solid #4a5568;background:#cbd5e0;padding:4px;font-weight:bold;">${l}</td>`);
|
LPHS.forEach(l => h2 += `<td style="border:1px solid #e8e8ed;background:#fafafc;padding:4px;font-weight:bold;">${l}</td>`);
|
||||||
h2 += `</tr>`;
|
h2 += `</tr>`;
|
||||||
return h1 + h2;
|
return h1 + h2;
|
||||||
}
|
}
|
||||||
@@ -2450,28 +2467,61 @@ function buildPdfTableHeaderRows(allPeople){
|
|||||||
// buildPdfProjectRow: baut eine einzelne Projektzeile als HTML-String.
|
// buildPdfProjectRow: baut eine einzelne Projektzeile als HTML-String.
|
||||||
function buildPdfProjectRow(p, allPeople){
|
function buildPdfProjectRow(p, allPeople){
|
||||||
let body = `<tr>`;
|
let body = `<tr>`;
|
||||||
body += `<td style="border:1px solid #a0aec0;padding:4px;white-space:nowrap;">${esc(p.nr)}</td>`;
|
body += `<td style="border:1px solid #e8e8ed;padding:4px;white-space:nowrap;">${esc(p.nr)}</td>`;
|
||||||
body += `<td style="border:1px solid #a0aec0;padding:4px;">${esc(p.name)}</td>`;
|
body += `<td style="border:1px solid #e8e8ed;padding:4px;">${esc(p.name)}</td>`;
|
||||||
const statusText = pdfStatusText(p.status);
|
const statusText = pdfStatusText(p.status);
|
||||||
body += `<td style="border:1px solid #a0aec0;padding:4px;">${statusText}</td>`;
|
body += `<td style="border:1px solid #e8e8ed;padding:4px;">${statusText}</td>`;
|
||||||
allPeople.forEach(entry => {
|
allPeople.forEach(entry => {
|
||||||
const s = p.assign[assignKey(entry.person.id, entry.role)] || 0;
|
const s = p.assign[assignKey(entry.person.id, entry.role)] || 0;
|
||||||
const txt = s === 1 ? 'X' : s === 2 ? '(X)' : '';
|
const txt = s === 1 ? 'X' : s === 2 ? '(X)' : '';
|
||||||
const bg = s === 1 ? '#9ae6b4' : s === 2 ? '#fbd38d' : '#fff';
|
let roleBg = '#fff';
|
||||||
body += `<td style="border:1px solid #a0aec0;padding:4px;text-align:center;background:${bg};">${txt}</td>`;
|
if(entry.role === 'Planung') roleBg = 'rgb(212, 245, 224)';
|
||||||
|
else if(entry.role === 'Projektleitung') roleBg = 'rgb(255, 219, 219)';
|
||||||
|
else if(entry.role === 'Bauüberwachung') roleBg = 'rgb(203, 238, 255)';
|
||||||
|
const textColor = (s === 1 || s === 2) ? '#fff' : '#1d1d1f';
|
||||||
|
const cellBg = s === 1 ? '#248a3d' : s === 2 ? '#d18b00' : roleBg;
|
||||||
|
body += `<td style="border:1px solid #e8e8ed;padding:4px;text-align:center;background:${cellBg};color:${textColor};">${txt}</td>`;
|
||||||
});
|
});
|
||||||
LPHS.forEach(l => {
|
LPHS.forEach(l => {
|
||||||
const v = (p.lph[l] !== undefined && p.lph[l] !== '') ? String(p.lph[l]).replace('%','') : '';
|
const v = (p.lph[l] !== undefined && p.lph[l] !== '') ? String(p.lph[l]).replace('%','') : '';
|
||||||
body += `<td style="border:1px solid #a0aec0;padding:4px;text-align:center;">${esc(v ? v + '%' : '')}</td>`;
|
body += `<td style="border:1px solid #e8e8ed;padding:4px;text-align:center;">${esc(v ? v + '%' : '')}</td>`;
|
||||||
});
|
});
|
||||||
body += `</tr>`;
|
body += `</tr>`;
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build PDF group body rows with alternating background colors
|
||||||
|
function buildPdfGroupBodyRows(projectRows, allPeople) {
|
||||||
|
let html = '';
|
||||||
|
projectRows.forEach((pr, i) => {
|
||||||
|
const s = pr.assign ? Object.values(pr.assign)[0] || 0 : 0;
|
||||||
|
const isEven = i % 2 === 0;
|
||||||
|
const rowBg = isEven ? '#fafafc' : '#fff';
|
||||||
|
html += `<tr style="background:${rowBg}">`;
|
||||||
|
html += `<td style="border:1px solid #e8e8ed;padding:4px;white-space:nowrap;">${esc(pr.nr)}</td>`;
|
||||||
|
html += `<td style="border:1px solid #e8e8ed;padding:4px;">${esc(pr.name)}</td>`;
|
||||||
|
const statusText = pdfStatusText(pr.status);
|
||||||
|
html += `<td style="border:1px solid #e8e8ed;padding:4px;">${statusText}</td>`;
|
||||||
|
allPeople.forEach(entry => {
|
||||||
|
const assignKey = `${entry.person.id}-${entry.role}`;
|
||||||
|
const assign = pr.assign ? pr.assign[assignKey] : 0;
|
||||||
|
const txt = assign === 1 ? 'X' : assign === 2 ? '(X)' : '';
|
||||||
|
const bg = assign === 1 ? '#248a3d' : assign === 2 ? '#d18b00' : '#fff';
|
||||||
|
html += `<td style="border:1px solid #e8e8ed;padding:4px;text-align:center;background:${bg};color:${assign===1||assign===2?'#fff':'#1d1d1f'}";">${txt}</td>`;
|
||||||
|
});
|
||||||
|
LPHS.forEach(l => {
|
||||||
|
const v = (pr.lph && pr.lph[l] !== undefined && pr.lph[l] !== '') ? String(pr.lph[l]).replace('%','') : '';
|
||||||
|
html += `<td style="border:1px solid #e8e8ed;padding:4px;text-align:center;">${esc(v ? v + '%' : '')}</td>`;
|
||||||
|
});
|
||||||
|
html += `</tr>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
// buildPdfGroupTitleRow: baut die Zeile mit dem Gruppentitel.
|
// buildPdfGroupTitleRow: baut die Zeile mit dem Gruppentitel.
|
||||||
function buildPdfGroupTitleRow(groupName, allPeople){
|
function buildPdfGroupTitleRow(groupName, allPeople){
|
||||||
const colCount = 3 + allPeople.length + LPHS.length;
|
const colCount = 3 + allPeople.length + LPHS.length;
|
||||||
return `<tr><td colspan="${colCount}" style="border:1px solid #4a5568;background:#edf2f7;font-weight:bold;padding:6px;">${esc(groupName)}</td></tr>`;
|
return `<tr><td colspan="${colCount}" style="border:1px solid #e8e8ed;background:#f0f0f2;font-weight:bold;padding:6px;color:#1d1d1f;">${esc(groupName)}</td></tr>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderSinglePdfBlock: erzeugt einen DOM-Wrapper mit einer einzelnen Tabelle
|
// renderSinglePdfBlock: erzeugt einen DOM-Wrapper mit einer einzelnen Tabelle
|
||||||
@@ -2490,7 +2540,7 @@ function renderSinglePdfBlock(container, headerRows, titleRow, bodyRows){
|
|||||||
// buildPdfAttendanceBlock: baut den Anwesenheits-Block als HTML-String.
|
// buildPdfAttendanceBlock: baut den Anwesenheits-Block als HTML-String.
|
||||||
function buildPdfAttendanceBlock(presentList, absentList){
|
function buildPdfAttendanceBlock(presentList, absentList){
|
||||||
return `<div class="pdf-block" style="margin-bottom:16px;">
|
return `<div class="pdf-block" style="margin-bottom:16px;">
|
||||||
<div style="margin-bottom:8px; font-weight:bold; color:#1e3a5f; font-size:12px;">Anwesenheit</div>
|
<div style="margin-bottom:8px; font-weight:bold; color:#1d1d1f; font-size:12px;">Anwesenheit</div>
|
||||||
<div style="margin-bottom:4px;"><b>Anwesend:</b> ${presentList || '—'}</div>
|
<div style="margin-bottom:4px;"><b>Anwesend:</b> ${presentList || '—'}</div>
|
||||||
<div><b>Abwesend:</b> ${absentList || '—'}</div>
|
<div><b>Abwesend:</b> ${absentList || '—'}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -2545,33 +2595,33 @@ function buildPdfLoadSection(allPeople){
|
|||||||
if(l > maxLoad) maxLoad = l;
|
if(l > maxLoad) maxLoad = l;
|
||||||
});
|
});
|
||||||
|
|
||||||
let html = `<div class="pdf-block"><div style="margin-top:24px; margin-bottom:8px; font-weight:bold; color:#1e3a5f; font-size:12px;">Auslastung der Projektbeteiligten (aktuelle Sitzung)</div>`;
|
let html = `<div class="pdf-block"><div style="margin-top:24px; margin-bottom:8px; font-weight:bold; color:#1d1d1f; font-size:12px;">Auslastung der Projektbeteiligten (aktuelle Sitzung)</div>`;
|
||||||
html += '<table style="border-collapse:collapse;width:100%;"><tbody><tr><td style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Rolle</td><td style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Kürzel</td><td style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Punkte</td><td style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Balken</td><td style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Projektdetails</td></tr>';
|
html += '<table style="border-collapse:collapse;width:100%;"><tbody><tr><td style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Rolle</td><td style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Kürzel</td><td style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Punkte</td><td style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Balken</td><td style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Projektdetails</td></tr>';
|
||||||
|
|
||||||
ROLES.forEach(r => {
|
ROLES.forEach(r => {
|
||||||
grouped[r].forEach(entry => {
|
grouped[r].forEach(entry => {
|
||||||
const s = stats[assignKey(entry.person.id, r)];
|
const s = stats[assignKey(entry.person.id, r)];
|
||||||
const load = s.main + s.opt * 0.5 + s.kleine * 0.3;
|
const load = s.main + s.opt * 0.5 + s.kleine * 0.3;
|
||||||
const pct = Math.round(load / maxLoad * 100);
|
const pct = Math.round(load / maxLoad * 100);
|
||||||
const color = pct > 75 ? '#c53030' : pct > 45 ? '#d69e2e' : '#2f855a';
|
const color = pct > 75 ? '#d70015' : pct > 45 ? '#b25000' : '#248a3d';
|
||||||
const detail = Object.keys(s.projects).map(k => `${esc(k)} (${s.projects[k]}×)`).join(', ') || '—';
|
const detail = Object.keys(s.projects).map(k => `${esc(k)} (${s.projects[k]}×)`).join(', ') || '—';
|
||||||
html += `<tr>
|
html += `<tr>
|
||||||
<td style="border:1px solid #a0aec0;padding:4px;">${r}</td>
|
<td style="border:1px solid #e8e8ed;padding:4px;">${r}</td>
|
||||||
<td style="border:1px solid #a0aec0;padding:4px;font-weight:bold;">${esc(entry.person.name)}</td>
|
<td style="border:1px solid #e8e8ed;padding:4px;font-weight:bold;">${esc(entry.person.name)}</td>
|
||||||
<td style="border:1px solid #a0aec0;padding:4px;text-align:center;">${load.toString().replace('.',',')}</td>
|
<td style="border:1px solid #e8e8ed;padding:4px;text-align:center;">${load.toString().replace('.',',')}</td>
|
||||||
<td style="border:1px solid #a0aec0;padding:4px;">
|
<td style="border:1px solid #e8e8ed;padding:4px;">
|
||||||
<div style="background:#e2e8f0;height:10px;border-radius:3px;overflow:hidden;">
|
<div style="background:#f0f0f0;height:10px;border-radius:3px;overflow:hidden;">
|
||||||
<div style="width:${pct}%;background:${color};height:100%;"></div>
|
<div style="width:${pct}%;background:${color};height:100%;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:9px;color:#666;text-align:right;">${pct}%</div>
|
<div style="font-size:9px;color:#6e6e73;text-align:right;">${pct}%</div>
|
||||||
</td>
|
</td>
|
||||||
<td style="border:1px solid #a0aec0;padding:4px;font-size:9px;">${detail}${s.anbahnung ? `<br><span style="color:#3182ce;">In Anbahnung: ${s.anbahnung}</span>` : ''}</td>
|
<td style="border:1px solid #e8e8ed;padding:4px;font-size:9px;">${detail}${s.anbahnung ? `<br><span style="color:#0071e3;">In Anbahnung: ${s.anbahnung}</span>` : ''}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
html += '</tbody></table>';
|
html += '</tbody></table>';
|
||||||
html += '<div style="margin-top:4px;font-size:9px;color:#666;">Haupt=1 · Optional=0,5 · Kleine Projekte=0,3 · In Anbahnung wird nicht in der Punktzahl gezählt.</div>';
|
html += '<div style="margin-top:4px;font-size:9px;color:#6e6e73;">Haupt=1 · Optional=0,5 · Kleine Projekte=0,3 · In Anbahnung wird nicht in der Punktzahl gezählt.</div>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
@@ -4,15 +4,15 @@ Pr.-Nr.;Bezeichnung;Status;AP;AK;ED;SK;SE;WS;ED;SP;THK;RR;MK;TW;SG;ED;SK;SP;THK;
|
|||||||
ANWESENHEIT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
ANWESENHEIT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
18-4007;Janusz-Korczak-Gesamtsch. Bottrop;- KW 35 NW Raum Planung <div>- KW 44 Möbel </div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;90%;;;;;Aktive Projekte;0;"{""start"":"""",""end"":"""",""milestones"":[],""people"":[],""progress"":0}"
|
18-4007;Janusz-Korczak-Gesamtsch. Bottrop;- KW 35 NW Raum Planung <div>- KW 44 Möbel </div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;90%;;;;;Aktive Projekte;0;"{""start"":"""",""end"":"""",""milestones"":[],""people"":[],""progress"":0}"
|
||||||
21-4030;NSHV Köln Immobilienprüfung in Hof;- Thorsten W. klärt und übernimmt Schriftverkehr <div>- LV liegt zur Sichtung bei T.W.</div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;;;;Aktive Projekte;2;
|
21-4030;NSHV Köln Immobilienprüfung in Hof;- Thorsten W. klärt und übernimmt Schriftverkehr <div>- LV liegt zur Sichtung bei T.W.</div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;;;;Aktive Projekte;2;
|
||||||
22-4066;DSW 21, Umbau E-Mobilität;"- Elt-Montagen ab <font color=""#c53030""><b>KW 33</b></font>.";;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;20%;;Aktive Projekte;6;
|
22-4066;DSW 21, Umbau E-Mobilität;- Elt-Montagen ab <font><b>KW 33</b></font>.;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;20%;;Aktive Projekte;6;
|
||||||
24-4043;S-K ODL-Heim Sanierung Krefeld;- Vorraussichtlich LV-Abgabe XX.XX<div>- LPH5 mündlich freigegeben <div>- Neuer EG Grundriss mit diversen Änderungen kommt noch von Architekten</div></div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;;;;;Aktive Projekte;10;
|
24-4043;S-K ODL-Heim Sanierung Krefeld;- Vorraussichtlich LV-Abgabe XX.XX<div>- LPH5 mündlich freigegeben <div>- Neuer EG Grundriss mit diversen Änderungen kommt noch von Architekten</div></div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;;;;;Aktive Projekte;10;
|
||||||
21-4001;Feuerwehrgeräteh. Rodenkirchen;"- KNX muß noch programmiert werden. <font>Abnahme 27.07</font> <div><font color=""#c53030""><b>- Fertigstellung Ende Juli</b></font><div><font color=""#c53030""><b>- Abnahmen laufen bis Ende KW31</b></font></div><div><font color=""#c53030""><b>- Blackbuilding Test 26.07</b></font></div></div>";X;;X;;;;X;X;;;;;;X;;;;;;;;;100%;100%;100%;;;Aktive Projekte;1;"{""start"":"""",""end"":"""",""milestones"":[{""id"":""mslrvh7hjau1e"",""name"":""Übergabe"",""date"":""2026-09-12"",""dependsOn"":[""mslv6h76cmx8k""],""position"":""after:mslv6h76cmx8k""}],""people"":[""p_ap"",""p_ak"",""p_sk"",""p_sg""],""progress"":""0"",""todos"":[{""id"":""msluufbp114g1"",""title"":""Test"",""start"":""2026-08-10"",""end"":""2026-08-14"",""done"":false,""dependsOn"":[],""assignees"":[""p_ap"",""p_ed""],""progress"":0},{""id"":""mslv6h76cmx8k"",""title"":""Test 2"",""start"":""2026-08-24"",""end"":""2026-09-04"",""done"":false,""dependsOn"":[""msluufbp114g1""],""assignees"":[""p_ap""],""progress"":41},{""id"":""mslw4l3qp1v18"",""title"":""Test 3"",""start"":""2026-09-14"",""end"":""2026-09-22"",""done"":false,""dependsOn"":[""m_mslrvh7hjau1e""],""assignees"":[],""progress"":0},{""id"":""msoltaa1itx48"",""title"":""Gehen"",""start"":""2026-09-23"",""end"":""2026-09-25"",""done"":false,""progress"":0,""dependsOn"":[""mslw4l3qp1v18""],""assignees"":[""p_ws""]}]}"
|
21-4001;Feuerwehrgeräteh. Rodenkirchen;- KNX muß noch programmiert werden. <font>Abnahme 27.07</font> <div><font><b>- Fertigstellung Ende Juli</b></font><div><font><b>- Abnahmen laufen bis Ende KW31</b></font></div><div><font><b>- Blackbuilding Test 26.07</b></font></div></div>;X;;X;;;;X;X;;;;;;X;;;;;;;;;100%;100%;100%;;;Aktive Projekte;1;"{""start"":"""",""end"":"""",""milestones"":[{""id"":""mslrvh7hjau1e"",""name"":""Übergabe"",""date"":""2026-09-12"",""dependsOn"":[""mslv6h76cmx8k""],""position"":""after:mslv6h76cmx8k""}],""people"":[""p_ap"",""p_ak"",""p_sk"",""p_sg""],""progress"":""0"",""todos"":[{""id"":""msluufbp114g1"",""title"":""Test"",""start"":""2026-08-10"",""end"":""2026-08-14"",""done"":false,""dependsOn"":[],""assignees"":[""p_ap"",""p_ed""],""progress"":0},{""id"":""mslv6h76cmx8k"",""title"":""Test 2"",""start"":""2026-08-24"",""end"":""2026-09-04"",""done"":false,""dependsOn"":[""msluufbp114g1""],""assignees"":[""p_ap""],""progress"":41},{""id"":""mslw4l3qp1v18"",""title"":""Test 3"",""start"":""2026-09-14"",""end"":""2026-09-22"",""done"":false,""dependsOn"":[""m_mslrvh7hjau1e""],""assignees"":[],""progress"":0},{""id"":""msoltaa1itx48"",""title"":""Gehen"",""start"":""2026-09-23"",""end"":""2026-09-25"",""done"":false,""progress"":0,""dependsOn"":[""mslw4l3qp1v18""],""assignees"":[""p_ws""]}]}"
|
||||||
22-4027;Sporthalle Soest;- SV und VOB Abnahme erfolgt.<div>- Nachprüfung VOB 24.07</div><div>- Keine Infos über Nachprüfung</div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;90%;;Aktive Projekte;3;
|
22-4027;Sporthalle Soest;- SV und VOB Abnahme erfolgt.<div>- Nachprüfung VOB 24.07</div><div>- Keine Infos über Nachprüfung</div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;90%;;Aktive Projekte;3;
|
||||||
22-4056;Gymnasium Hemer;;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;90%;;Aktive Projekte;4;
|
22-4056;Gymnasium Hemer;;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;90%;;Aktive Projekte;4;
|
||||||
22-4056-1;Gymnasium Hemer (Deckensanierung);- Beginn der Arbeiten vorraussichtlich KW 30 (KW 32 Kickoff);;;;;;;;;;;;;;;;;;;;;100%;;100%;100%;;;;Aktive Projekte;5;
|
22-4056-1;Gymnasium Hemer (Deckensanierung);- Beginn der Arbeiten vorraussichtlich KW 30 (KW 32 Kickoff);;;;;;;;;;;;;;;;;;;;;100%;;100%;100%;;;;Aktive Projekte;5;
|
||||||
23-4042;GWK Neufelder Straße;"- Nachweis für E-30 Leitungen notwendig. Rechnungen wurden nicht anerkannt. <div><font color=""#c53030"">- 22.07 Aufschaltung BMA und Amok</font></div><div><font color=""#c53030"">- KW 31 VOB Abnahme</font></div>";;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;95%;;Aktive Projekte;7;
|
23-4042;GWK Neufelder Straße;- Nachweis für E-30 Leitungen notwendig. Rechnungen wurden nicht anerkannt. <div><font>- 22.07 Aufschaltung BMA und Amok</font></div><div><font>- KW 31 VOB Abnahme</font></div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;95%;;Aktive Projekte;7;
|
||||||
23-4070;Umbau Bürogebäude NEW, MV10;"- neue NSHV anbieten(Warten auf Infos), <div>- 28.07 Sachverständiger Abnahme ELT&SiBe </div><div>- 30.07 Sachverständigen Abnahme BMA</div>";;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;90%;;Aktive Projekte;8;
|
23-4070;Umbau Bürogebäude NEW, MV10;"- neue NSHV anbieten(Warten auf Infos), <div>- 28.07 Sachverständiger Abnahme ELT&SiBe </div><div>- 30.07 Sachverständigen Abnahme BMA</div>";;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;90%;;Aktive Projekte;8;
|
||||||
23-4072;S-K OGS-Kaldenkirchen Schulanbau;"- Feininstallation beginnt, Fertigstellung voraussichtlich <font color=""#c53030"">01.08</font>";;;;;;;;;;;;;;;;;;;;;;;;;;70%;;Aktive Projekte;9;
|
23-4072;S-K OGS-Kaldenkirchen Schulanbau;- Feininstallation beginnt, Fertigstellung voraussichtlich <font>01.08</font>;;;;;;;;;;;;;;;;;;;;;;;;;;70%;;Aktive Projekte;9;
|
||||||
24-4049;BMA Remscheid;"- Feuerwehrabnahme organisiert die Errichterfirma. <div>- SV-Abnahmen 15 & 16.07: nicht abgenommen</div>";;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;90%;;Aktive Projekte;11;
|
24-4049;BMA Remscheid;"- Feuerwehrabnahme organisiert die Errichterfirma. <div>- SV-Abnahmen 15 & 16.07: nicht abgenommen</div>";;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;90%;;Aktive Projekte;11;
|
||||||
24-4069;S-K Erw. GS Blankenheim Neubau;- nur backup;;;;;;;;;;;;;;;;;;;;;;;100%;100%;;;;Kleine Projekte;0;
|
24-4069;S-K Erw. GS Blankenheim Neubau;- nur backup;;;;;;;;;;;;;;;;;;;;;;;100%;100%;;;;Kleine Projekte;0;
|
||||||
24-4096;Umbau Wohngebäude Kirchhoff;- Position HA wird von uns bestimmt. <div>- Notwendiges Leerrohr für Leuchten im Vordach ist zu klären.<div><font>- Abgabe Kostenberechnung bis Ende KW29</font></div><div><font>- LV Fertig bis Ende KW 32 </font> </div></div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;95%;85%;0%;;;Aktive Projekte;12;
|
24-4096;Umbau Wohngebäude Kirchhoff;- Position HA wird von uns bestimmt. <div>- Notwendiges Leerrohr für Leuchten im Vordach ist zu klären.<div><font>- Abgabe Kostenberechnung bis Ende KW29</font></div><div><font>- LV Fertig bis Ende KW 32 </font> </div></div>;;;;;;;;;;;;;;;;;;;100%;100%;100%;;95%;85%;0%;;;Aktive Projekte;12;
|
||||||
|
|||||||
|
Reference in new issue
Block a user