mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-24 23:16:41 +02:00
fix: improve PDF export - vector icons, wider section lines, address line break, simplified language levels
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2e5a697224
commit
4d5aa84ac9
2 changed files with 78 additions and 25 deletions
|
|
@ -174,17 +174,23 @@ export class ProfileExportService {
|
|||
yLeft = this.pdfSectionTitle(doc, 'KONTAKT', leftColX, yLeft, leftColWidth, accentColor);
|
||||
|
||||
if (data.phone) {
|
||||
yLeft = this.pdfContactLine(doc, '\u260E', data.phone, leftColX, yLeft, leftColWidth);
|
||||
this.drawPhoneIcon(doc, leftColX, yLeft + 1, accentColor);
|
||||
yLeft = this.pdfContactText(doc, data.phone, leftColX + 16, yLeft, leftColWidth - 16);
|
||||
}
|
||||
if (data.mobile) {
|
||||
yLeft = this.pdfContactLine(doc, '\u260E', data.mobile, leftColX, yLeft, leftColWidth);
|
||||
this.drawPhoneIcon(doc, leftColX, yLeft + 1, accentColor);
|
||||
yLeft = this.pdfContactText(doc, data.mobile, leftColX + 16, yLeft, leftColWidth - 16);
|
||||
}
|
||||
if (data.email) {
|
||||
yLeft = this.pdfContactLine(doc, '\u2709', data.email, leftColX, yLeft, leftColWidth);
|
||||
this.drawEmailIcon(doc, leftColX, yLeft + 1, accentColor);
|
||||
yLeft = this.pdfContactText(doc, data.email, leftColX + 16, yLeft, leftColWidth - 16);
|
||||
}
|
||||
if (data.street || data.city) {
|
||||
const address = [data.street, [data.postalCode, data.city].filter(Boolean).join(' ')].filter(Boolean).join(', ');
|
||||
yLeft = this.pdfContactLine(doc, '\u2302', address, leftColX, yLeft, leftColWidth);
|
||||
this.drawLocationIcon(doc, leftColX + 1, yLeft, accentColor);
|
||||
const line1 = data.street || '';
|
||||
const line2 = [data.postalCode, data.city].filter(Boolean).join(' ');
|
||||
const addressText = [line1, line2].filter(Boolean).join('\n');
|
||||
yLeft = this.pdfContactText(doc, addressText, leftColX + 16, yLeft, leftColWidth - 16);
|
||||
}
|
||||
yLeft += 10;
|
||||
|
||||
|
|
@ -457,20 +463,15 @@ export class ProfileExportService {
|
|||
// Kontakt-Sektion
|
||||
leftParagraphs.push(this.docxSectionHeading('KONTAKT', accentHex));
|
||||
|
||||
const contactItems: Array<{ icon: string; text: string }> = [];
|
||||
if (data.phone) contactItems.push({ icon: '\u260E', text: data.phone });
|
||||
if (data.mobile) contactItems.push({ icon: '\u260E', text: data.mobile });
|
||||
if (data.email) contactItems.push({ icon: '\u2709', text: data.email });
|
||||
if (data.street || data.city) {
|
||||
const address = [data.street, [data.postalCode, data.city].filter(Boolean).join(' ')].filter(Boolean).join(', ');
|
||||
contactItems.push({ icon: '\u2302', text: address });
|
||||
}
|
||||
|
||||
const contactItems: Array<{ label: string; text: string }> = [];
|
||||
if (data.phone) contactItems.push({ label: 'Tel.', text: data.phone });
|
||||
if (data.mobile) contactItems.push({ label: 'Mobil', text: data.mobile });
|
||||
if (data.email) contactItems.push({ label: 'Mail', text: data.email });
|
||||
for (const item of contactItems) {
|
||||
leftParagraphs.push(
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({ text: `${item.icon} `, size: 18, color: accentHex }),
|
||||
new TextRun({ text: `${item.label} `, bold: true, size: 14, color: accentHex }),
|
||||
new TextRun({ text: item.text, size: 16, color: '555555' }),
|
||||
],
|
||||
spacing: { after: 60 },
|
||||
|
|
@ -478,6 +479,23 @@ export class ProfileExportService {
|
|||
);
|
||||
}
|
||||
|
||||
// Adresse separat (mit Zeilenumbruch)
|
||||
if (data.street || data.city) {
|
||||
const addrRuns: TextRun[] = [
|
||||
new TextRun({ text: 'Adr. ', bold: true, size: 14, color: accentHex }),
|
||||
];
|
||||
if (data.street) {
|
||||
addrRuns.push(new TextRun({ text: data.street, size: 16, color: '555555' }));
|
||||
}
|
||||
const cityLine = [data.postalCode, data.city].filter(Boolean).join(' ');
|
||||
if (cityLine) {
|
||||
addrRuns.push(new TextRun({ text: cityLine, size: 16, color: '555555', break: 1 }));
|
||||
}
|
||||
leftParagraphs.push(
|
||||
new Paragraph({ children: addrRuns, spacing: { after: 60 } }),
|
||||
);
|
||||
}
|
||||
|
||||
// Sprachen
|
||||
if (profile && profile.languages.length > 0) {
|
||||
leftParagraphs.push(this.docxSectionHeading('SPRACHEN', accentHex));
|
||||
|
|
@ -684,29 +702,64 @@ export class ProfileExportService {
|
|||
title: string,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
_width: number,
|
||||
accentColor: string,
|
||||
): number {
|
||||
doc.font('Helvetica-Bold').fontSize(10).fillColor('#333333');
|
||||
doc.text(title, x, y, { width });
|
||||
doc.text(title, x, y);
|
||||
const titleWidth = doc.widthOfString(title);
|
||||
y += 14;
|
||||
doc.moveTo(x, y).lineTo(x + Math.min(width, 60), y)
|
||||
.strokeColor(accentColor).lineWidth(1.5).stroke();
|
||||
doc.moveTo(x, y).lineTo(x + titleWidth + 4, y)
|
||||
.strokeColor(accentColor).lineWidth(2).stroke();
|
||||
y += 8;
|
||||
return y;
|
||||
}
|
||||
|
||||
private pdfContactLine(
|
||||
private pdfContactText(
|
||||
doc: PDFKit.PDFDocument,
|
||||
icon: string,
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
): number {
|
||||
doc.font('Helvetica').fontSize(8).fillColor('#777777');
|
||||
doc.text(`${icon} ${text}`, x, y, { width });
|
||||
return y + doc.heightOfString(`${icon} ${text}`, { width }) + 3;
|
||||
doc.font('Helvetica').fontSize(8).fillColor('#555555');
|
||||
doc.text(text, x, y, { width });
|
||||
const textHeight = doc.heightOfString(text, { width });
|
||||
return y + Math.max(textHeight, 10) + 3;
|
||||
}
|
||||
|
||||
// --- Vektor-Icons für Kontakt ---
|
||||
|
||||
private drawPhoneIcon(doc: PDFKit.PDFDocument, x: number, y: number, color: string): void {
|
||||
doc.save();
|
||||
// Telefon-Hörer: abgerundetes Rechteck
|
||||
doc.roundedRect(x + 1, y, 4, 8, 1.5).fillAndStroke(color, color);
|
||||
// Hörer-Bogen oben
|
||||
doc.moveTo(x, y + 1).quadraticCurveTo(x - 1, y + 4, x + 1, y + 7)
|
||||
.strokeColor(color).lineWidth(1.5).stroke();
|
||||
doc.restore();
|
||||
}
|
||||
|
||||
private drawEmailIcon(doc: PDFKit.PDFDocument, x: number, y: number, color: string): void {
|
||||
doc.save();
|
||||
// Briefumschlag
|
||||
doc.rect(x, y + 1, 10, 7).strokeColor(color).lineWidth(1).stroke();
|
||||
// V-Linie oben
|
||||
doc.moveTo(x, y + 1).lineTo(x + 5, y + 5).lineTo(x + 10, y + 1)
|
||||
.strokeColor(color).lineWidth(0.8).stroke();
|
||||
doc.restore();
|
||||
}
|
||||
|
||||
private drawLocationIcon(doc: PDFKit.PDFDocument, x: number, y: number, color: string): void {
|
||||
doc.save();
|
||||
// Pin-Kopf (Kreis)
|
||||
doc.circle(x + 4, y + 4, 3).fillAndStroke(color, color);
|
||||
// Pin-Spitze (Dreieck)
|
||||
doc.moveTo(x + 1.5, y + 5.5).lineTo(x + 4, y + 11).lineTo(x + 6.5, y + 5.5)
|
||||
.fill(color);
|
||||
// Innerer Kreis (weiß)
|
||||
doc.circle(x + 4, y + 4, 1.2).fill('#ffffff');
|
||||
doc.restore();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ interface LanguagesSectionProps {
|
|||
onUpdate: () => Promise<void>;
|
||||
}
|
||||
|
||||
const LANGUAGE_LEVELS = ['Muttersprache', 'C2', 'C1', 'B2', 'B1', 'A2', 'A1'];
|
||||
const LANGUAGE_LEVELS = ['Muttersprache', 'Fließend', 'Gut'];
|
||||
|
||||
export function LanguagesSection({ languages, onUpdate }: LanguagesSectionProps) {
|
||||
const [language, setLanguage] = useState('');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue