From ed24c061c4fa0333a0dd536d2115a5812306bdc0 Mon Sep 17 00:00:00 2001 From: Thomas Reitz Date: Fri, 13 Mar 2026 22:08:18 +0100 Subject: [PATCH] =?UTF-8?q?fix(core):=20PDF-Export=20=E2=80=94=20Timeline-?= =?UTF-8?q?Linien=20korrekt=20und=20Faehigkeiten=20in=20linke=20Spalte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BERUFSERFAHRUNG: Timeline-Linie wird jetzt NACH dem Content gezeichnet (entryStartY gespeichert, Linie von entryStartY+8 bis yRight-4) Damit stimmt die Laenge exakt mit der tatsaechlichen Eintraghoehe ueberein - Seitenumbruch-Flag (pageBreakOccurred): Linie wird nicht gezeichnet wenn der Content ueber eine Seite hinausgeht - FAEHIGKEITEN: aus dem full-width Bereich am Seitenende entfernt und in die linke Spalte nach ERFAHRUNG verschoben (kleinere Chips: 7pt, 16px, 5px Pad) - Alte full-width FAEHIGKEITEN-Sektion entfernt Co-Authored-By: Claude Sonnet 4.6 --- .../expert-profile/profile-export.service.ts | 91 ++++++++----------- 1 file changed, 38 insertions(+), 53 deletions(-) diff --git a/packages/core-service/src/core/expert-profile/profile-export.service.ts b/packages/core-service/src/core/expert-profile/profile-export.service.ts index 0d20f1a..9b65c50 100644 --- a/packages/core-service/src/core/expert-profile/profile-export.service.ts +++ b/packages/core-service/src/core/expert-profile/profile-export.service.ts @@ -313,6 +313,34 @@ export class ProfileExportService { } } + // --- FÄHIGKEITEN (linke Spalte, nach Erfahrungen) --- + if (profile && profile.skills.length > 0) { + yLeft += 6; + yLeft = this.pdfSectionTitle(doc, 'FÄHIGKEITEN', leftColX, yLeft, leftColWidth, accentColor); + const skillStartX = leftColX; + const skillMaxX = leftColX + leftColWidth; + let skillX = skillStartX; + let skillY = yLeft; + const skillChipH = 16; + const skillChipPad = 5; + const skillChipGap = 4; + const skillBg = lightenColor(accentColor, 0.85); + for (const skill of profile.skills) { + doc.font('Helvetica').fontSize(7); + const tw = doc.widthOfString(skill); + const cw = tw + skillChipPad * 2; + if (skillX + cw > skillMaxX) { + skillX = skillStartX; + skillY += skillChipH + skillChipGap; + } + doc.roundedRect(skillX, skillY, cw, skillChipH, 7).fill(skillBg); + doc.font('Helvetica').fontSize(7).fillColor(accentColor); + doc.text(skill, skillX + skillChipPad, skillY + 4, { width: tw, lineBreak: false }); + skillX += cw + skillChipGap; + } + yLeft = skillY + skillChipH + 4; + } + // --- SEITE 1: Rechte Spalte — BERUFSERFAHRUNG --- if (profile && profile.projects.length > 0) { yRight = this.pdfSectionTitle(doc, 'BERUFSERFAHRUNG', rightColX, yRight, rightColWidth, accentColor); @@ -331,14 +359,11 @@ export class ProfileExportService { yRight = this.pdfSectionTitle(doc, 'BERUFSERFAHRUNG (Forts.)', rightColX, yRight, rightColWidth, accentColor); } - // Timeline-Punkt - doc.circle(timelineX, yRight + 4, 3.5).fill(accentColor); + let pageBreakOccurred = false; + const entryStartY = yRight; - // Timeline-Linie (bis zum nächsten Eintrag) - if (i < profile.projects.length - 1) { - doc.moveTo(timelineX, yRight + 8).lineTo(timelineX, yRight + 70) - .strokeColor(accentColor).lineWidth(1).stroke(); - } + // Timeline-Punkt + doc.circle(timelineX, entryStartY + 4, 3.5).fill(accentColor); // Zeitraum (fett, größer) const dateRange = this.formatDateRange(proj.fromMonth, proj.fromYear, proj.toMonth, proj.toYear, proj.isCurrent); @@ -367,6 +392,7 @@ export class ProfileExportService { if (yRight > pageBottom) { doc.addPage(); yRight = 40; + pageBreakOccurred = true; } const raw = task.trim(); const hasBullet = /^[•\u2022]\s/.test(raw) || /^\d+\.\s/.test(raw); @@ -380,7 +406,11 @@ export class ProfileExportService { yRight += 12; - // Aktualisiere Timeline-Linie (Länge basiert auf tatsächlicher Position) + // Timeline-Linie (korrekte Länge basiert auf tatsächlicher Höhe) + if (i < profile.projects.length - 1 && !pageBreakOccurred) { + doc.moveTo(timelineX, entryStartY + 8).lineTo(timelineX, yRight - 4) + .strokeColor(accentColor).lineWidth(1).stroke(); + } } } @@ -433,51 +463,6 @@ export class ProfileExportService { } } - // --- FÄHIGKEITEN (Skills als Chips) --- - if (profile && profile.skills.length > 0) { - let y = Math.max(yLeft, yRight); - if (y > pageBottom - 60) { - doc.addPage(); - y = 40; - } else { - y += 10; - } - - y = this.pdfSectionTitle(doc, 'FÄHIGKEITEN', 40, y, pageWidth - 80, accentColor); - - const chipStartX = 40; - const maxX = pageWidth - 40; - let chipX = chipStartX; - const chipHeight = 20; - const chipPadding = 10; - const chipGap = 6; - const lightBg = lightenColor(accentColor, 0.85); - - for (const skill of profile.skills) { - doc.font('Helvetica').fontSize(8); - const textWidth = doc.widthOfString(skill); - const chipWidth = textWidth + chipPadding * 2; - - if (chipX + chipWidth > maxX) { - chipX = chipStartX; - y += chipHeight + chipGap; - if (y > pageBottom) { - doc.addPage(); - y = 40; - } - } - - // Chip-Hintergrund (abgerundetes Rechteck) - doc.roundedRect(chipX, y, chipWidth, chipHeight, 10).fill(lightBg); - - // Chip-Text - doc.font('Helvetica').fontSize(8).fillColor(accentColor); - doc.text(skill, chipX + chipPadding, y + 5.5, { width: textWidth, lineBreak: false }); - - chipX += chipWidth + chipGap; - } - } - // --- ANHÄNGE (Bild-Anhänge als zusätzliche Seiten) --- const attachments = profile?.attachments ?? []; for (const att of attachments) {