diff --git a/.DS_Store b/.DS_Store index 4284e7a..b51b8ab 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/node_modules/.DS_Store b/node_modules/.DS_Store index d040afd..bcda883 100644 Binary files a/node_modules/.DS_Store and b/node_modules/.DS_Store differ diff --git a/node_modules/math-intrinsics/.DS_Store b/node_modules/math-intrinsics/.DS_Store index 6f3a30f..54dec05 100644 Binary files a/node_modules/math-intrinsics/.DS_Store and b/node_modules/math-intrinsics/.DS_Store differ diff --git a/node_modules/object-inspect/.DS_Store b/node_modules/object-inspect/.DS_Store index b90bf74..9412063 100644 Binary files a/node_modules/object-inspect/.DS_Store and b/node_modules/object-inspect/.DS_Store differ diff --git a/node_modules/qs/.DS_Store b/node_modules/qs/.DS_Store index ff34709..1dd3e6d 100644 Binary files a/node_modules/qs/.DS_Store and b/node_modules/qs/.DS_Store differ diff --git a/node_modules/side-channel-list/.DS_Store b/node_modules/side-channel-list/.DS_Store index 2936618..09d00f9 100644 Binary files a/node_modules/side-channel-list/.DS_Store and b/node_modules/side-channel-list/.DS_Store differ diff --git a/node_modules/side-channel-map/.DS_Store b/node_modules/side-channel-map/.DS_Store index 30ef9c5..15e5e2e 100644 Binary files a/node_modules/side-channel-map/.DS_Store and b/node_modules/side-channel-map/.DS_Store differ diff --git a/node_modules/side-channel-weakmap/.DS_Store b/node_modules/side-channel-weakmap/.DS_Store index 0a9be26..138a8bf 100644 Binary files a/node_modules/side-channel-weakmap/.DS_Store and b/node_modules/side-channel-weakmap/.DS_Store differ diff --git a/node_modules/side-channel/.DS_Store b/node_modules/side-channel/.DS_Store index 56110b1..4c8e299 100644 Binary files a/node_modules/side-channel/.DS_Store and b/node_modules/side-channel/.DS_Store differ diff --git a/public/main.v2.js b/public/main.v2.js index 625ca4d..e3a63d6 100644 --- a/public/main.v2.js +++ b/public/main.v2.js @@ -2246,9 +2246,9 @@ async function exportPDF(){ container.style.width = '1200px'; container.style.background = '#fff'; 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.color = '#1a1a1a'; + container.style.color = 'var(--text)'; document.body.appendChild(container); const grouped = peopleByRole(w); @@ -2283,16 +2283,16 @@ async function exportPDF(){ cover.style.flexDirection = 'column'; cover.style.justifyContent = '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 ? `
Logo
` : `
⚡
`; cover.innerHTML = ` ${logoHtml} -

Projektübersicht ${DEPARTMENTS[currentDepartment].label}

-
${esc(w.label)}
- ${w.date ? `
Datum: ${esc(formatWeekDate(w.date))}
` : ''} -
Erstellt am ${new Date().toLocaleDateString('de-DE')}
+

Projektübersicht ${DEPARTMENTS[currentDepartment].label}

+
${esc(w.label)}
+ ${w.date ? `
Datum: ${esc(formatWeekDate(w.date))}
` : ''} +
Erstellt am ${new Date().toLocaleDateString('de-DE')}
`; container.appendChild(cover); @@ -2313,11 +2313,14 @@ async function exportPDF(){ const landscapePageW = pageHeightPx - marginPx * 2; const landscapePageH = pageWidthPx - marginPx * 2; + // JPEG quality (0.1 - 1.0) - lower = smaller file + const JPEG_QUALITY = 1; + async function addImageToLandscapePage(canvas){ const imgW = landscapePageW; const imgH = (canvas.height * imgW) / canvas.width; 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){ @@ -2328,7 +2331,7 @@ async function exportPDF(){ wrapper.style.background = '#fff'; wrapper.innerHTML = blockHTML; 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; wrapper.remove(); return { canvas, imgH }; @@ -2336,12 +2339,12 @@ async function exportPDF(){ try { // 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(); const coverW = pageWidthPx - marginPx * 2; const coverH = (coverCanvas.height * coverW) / coverCanvas.width; 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); @@ -2356,7 +2359,7 @@ async function exportPDF(){ // Hilfsfunktion: misst Höhe einer Tabelle mit gegebenen Zeilen async function measureBlockWithRows(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; wrapper.remove(); return { canvas, imgH }; @@ -2375,7 +2378,10 @@ async function exportPDF(){ for(let i = 0; i < projects.length; i++){ 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('', ``); + const testRows = currentRows + rowHTMLWithBg; const { imgH: testImgH } = await measureBlockWithRows(titleRow, testRows); // Wenn die aktuelle Seite überschritten würde und wir bereits Zeilen haben, @@ -2396,7 +2402,7 @@ async function exportPDF(){ // Projektboard-Blöcke zeichnen for(const block of groupBlocks){ 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); wrapper.remove(); } @@ -2424,25 +2430,36 @@ async function exportPDF(){ // buildPdfTableHeaderRows: baut die Kopfzeilen der Projektübersichts-Tabelle als HTML-String (zwei Zeilen). function buildPdfTableHeaderRows(allPeople){ - let h1 = `Pr.-Nr.BezeichnungStatus`; + let h1 = `Pr.-Nr.BezeichnungStatus`; const grouped = peopleByRole(currentWeek()); ROLES.forEach(r => { const count = Math.max(1, grouped[r].length); - h1 += `${r}`; + // 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 += `${r}`; }); - h1 += `Leistungsphasen (%)`; + h1 += `Leistungsphasen (%)`; let h2 = ``; 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){ - h2 += `–`; + h2 += `–`; } else { grouped[r].forEach(entry => { - h2 += `${esc(entry.person.name)}`; + h2 += `${esc(entry.person.name)}`; }); } }); - LPHS.forEach(l => h2 += `${l}`); + LPHS.forEach(l => h2 += `${l}`); h2 += ``; return h1 + h2; } @@ -2450,28 +2467,61 @@ function buildPdfTableHeaderRows(allPeople){ // buildPdfProjectRow: baut eine einzelne Projektzeile als HTML-String. function buildPdfProjectRow(p, allPeople){ let body = ``; - body += `${esc(p.nr)}`; - body += `${esc(p.name)}`; + body += `${esc(p.nr)}`; + body += `${esc(p.name)}`; const statusText = pdfStatusText(p.status); - body += `${statusText}`; + body += `${statusText}`; allPeople.forEach(entry => { const s = p.assign[assignKey(entry.person.id, entry.role)] || 0; const txt = s === 1 ? 'X' : s === 2 ? '(X)' : ''; - const bg = s === 1 ? '#9ae6b4' : s === 2 ? '#fbd38d' : '#fff'; - body += `${txt}`; + let roleBg = '#fff'; + 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 += `${txt}`; }); LPHS.forEach(l => { const v = (p.lph[l] !== undefined && p.lph[l] !== '') ? String(p.lph[l]).replace('%','') : ''; - body += `${esc(v ? v + '%' : '')}`; + body += `${esc(v ? v + '%' : '')}`; }); 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 += ``; + html += `${esc(pr.nr)}`; + html += `${esc(pr.name)}`; + const statusText = pdfStatusText(pr.status); + html += `${statusText}`; + 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 += `${txt}`; + }); + LPHS.forEach(l => { + const v = (pr.lph && pr.lph[l] !== undefined && pr.lph[l] !== '') ? String(pr.lph[l]).replace('%','') : ''; + html += `${esc(v ? v + '%' : '')}`; + }); + html += ``; + }); + return html; +} + // buildPdfGroupTitleRow: baut die Zeile mit dem Gruppentitel. function buildPdfGroupTitleRow(groupName, allPeople){ const colCount = 3 + allPeople.length + LPHS.length; - return `${esc(groupName)}`; + return `${esc(groupName)}`; } // 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. function buildPdfAttendanceBlock(presentList, absentList){ return `
-
Anwesenheit
+
Anwesenheit
Anwesend: ${presentList || '—'}
Abwesend: ${absentList || '—'}
`; @@ -2545,33 +2595,33 @@ function buildPdfLoadSection(allPeople){ if(l > maxLoad) maxLoad = l; }); - let html = `
Auslastung der Projektbeteiligten (aktuelle Sitzung)
`; - html += ''; + let html = `
Auslastung der Projektbeteiligten (aktuelle Sitzung)
`; + html += '
RolleKürzelPunkteBalkenProjektdetails
'; ROLES.forEach(r => { grouped[r].forEach(entry => { const s = stats[assignKey(entry.person.id, r)]; const load = s.main + s.opt * 0.5 + s.kleine * 0.3; 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(', ') || '—'; html += ` - - - - + + + - + `; }); }); html += '
RolleKürzelPunkteBalkenProjektdetails
${r}${esc(entry.person.name)}${load.toString().replace('.',',')} -
+
${r}${esc(entry.person.name)}${load.toString().replace('.',',')} +
-
${pct}%
+
${pct}%
${detail}${s.anbahnung ? `
In Anbahnung: ${s.anbahnung}` : ''}
${detail}${s.anbahnung ? `
In Anbahnung: ${s.anbahnung}` : ''}
'; - html += '
Haupt=1 · Optional=0,5 · Kleine Projekte=0,3 · In Anbahnung wird nicht in der Punktzahl gezählt.
'; + html += '
Haupt=1 · Optional=0,5 · Kleine Projekte=0,3 · In Anbahnung wird nicht in der Punktzahl gezählt.
'; html += '
'; return html; } diff --git a/sessions/.DS_Store b/sessions/.DS_Store index 3903cb3..0ac5090 100644 Binary files a/sessions/.DS_Store and b/sessions/.DS_Store differ diff --git a/sessions/ELT/KW_33___2026.csv b/sessions/ELT/KW_33___2026.csv index 6961169..638bbc4 100644 --- a/sessions/ELT/KW_33___2026.csv +++ b/sessions/ELT/KW_33___2026.csv @@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 18-4007;Janusz-Korczak-Gesamtsch. Bottrop;- KW 35 NW Raum Planung
- KW 44 Möbel
;;;;;;;;;;;;;;;;;;;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
- LV liegt zur Sichtung bei T.W.
;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;;;;Aktive Projekte;2; -22-4066;DSW 21, Umbau E-Mobilität;"- Elt-Montagen ab KW 33.";;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;20%;;Aktive Projekte;6; +22-4066;DSW 21, Umbau E-Mobilität;- Elt-Montagen ab KW 33.;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;20%;;Aktive Projekte;6; 24-4043;S-K ODL-Heim Sanierung Krefeld;- Vorraussichtlich LV-Abgabe XX.XX
- LPH5 mündlich freigegeben
- Neuer EG Grundriss mit diversen Änderungen kommt noch von Architekten
;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;;;;;Aktive Projekte;10; -21-4001;Feuerwehrgeräteh. Rodenkirchen;"- KNX muß noch programmiert werden. Abnahme 27.07
- Fertigstellung Ende Juli
- Abnahmen laufen bis Ende KW31
- Blackbuilding Test 26.07
";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. Abnahme 27.07
- Fertigstellung Ende Juli
- Abnahmen laufen bis Ende KW31
- Blackbuilding Test 26.07
;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.
- Nachprüfung VOB 24.07
- Keine Infos über Nachprüfung
;;;;;;;;;;;;;;;;;;;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-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.
- 22.07 Aufschaltung BMA und Amok
- KW 31 VOB Abnahme
";;;;;;;;;;;;;;;;;;;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.
- 22.07 Aufschaltung BMA und Amok
- KW 31 VOB Abnahme
;;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;95%;;Aktive Projekte;7; 23-4070;Umbau Bürogebäude NEW, MV10;"- neue NSHV anbieten(Warten auf Infos),
- 28.07 Sachverständiger Abnahme ELT&SiBe
- 30.07 Sachverständigen Abnahme BMA
";;;;;;;;;;;;;;;;;;;100%;100%;100%;;100%;100%;100%;90%;;Aktive Projekte;8; -23-4072;S-K OGS-Kaldenkirchen Schulanbau;"- Feininstallation beginnt, Fertigstellung voraussichtlich 01.08";;;;;;;;;;;;;;;;;;;;;;;;;;70%;;Aktive Projekte;9; +23-4072;S-K OGS-Kaldenkirchen Schulanbau;- Feininstallation beginnt, Fertigstellung voraussichtlich 01.08;;;;;;;;;;;;;;;;;;;;;;;;;;70%;;Aktive Projekte;9; 24-4049;BMA Remscheid;"- Feuerwehrabnahme organisiert die Errichterfirma.
- SV-Abnahmen 15 & 16.07: nicht abgenommen
";;;;;;;;;;;;;;;;;;;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-4096;Umbau Wohngebäude Kirchhoff;- Position HA wird von uns bestimmt.
- Notwendiges Leerrohr für Leuchten im Vordach ist zu klären.
- Abgabe Kostenberechnung bis Ende KW29
- LV Fertig bis Ende KW 32
;;;;;;;;;;;;;;;;;;;100%;100%;100%;;95%;85%;0%;;;Aktive Projekte;12;