mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-24 22:36:38 +02:00
fix(core): Word-Export — jobTitle, Logo, Akzentfarbe und Firmenfußzeile
- Jobtitel aus Profil statt experiences[0].area unter dem Namen - Platform-Logo aus Redis über Avatar einfügen - Dominante Akzentfarbe dynamisch aus Logo extrahieren - Firmen-Fußzeile aus Redis-Settings als DOCX-Footer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
196515daa8
commit
8fc894c74c
1 changed files with 100 additions and 4 deletions
|
|
@ -20,6 +20,7 @@ import {
|
|||
ImageRun,
|
||||
HeadingLevel,
|
||||
ShadingType,
|
||||
Footer,
|
||||
} from 'docx';
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -555,6 +556,48 @@ export class ProfileExportService {
|
|||
const data = await this.expertProfileService.getExportData(userId) as ExportData;
|
||||
const profile = data.expertProfile;
|
||||
const fullName = `${data.firstName} ${data.lastName}`;
|
||||
|
||||
// Branding + Firmendaten aus Redis laden
|
||||
const [brandingRaw, companyRaw] = await Promise.all([
|
||||
this.redis.get('platform_branding_logo'),
|
||||
this.redis.get('platform_company_settings'),
|
||||
]);
|
||||
|
||||
let platformLogo: Buffer | null = null;
|
||||
if (brandingRaw) {
|
||||
try {
|
||||
const branding = JSON.parse(brandingRaw) as { logo?: string };
|
||||
if (branding.logo) {
|
||||
const base64 = branding.logo.replace(/^data:image\/\w+;base64,/, '');
|
||||
platformLogo = Buffer.from(base64, 'base64');
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// Dominante Farbe aus Logo extrahieren
|
||||
if (platformLogo) {
|
||||
const extracted = await this.extractDominantColor(platformLogo);
|
||||
if (extracted) accentColor = extracted;
|
||||
}
|
||||
|
||||
let companyFooterText: string | null = null;
|
||||
if (companyRaw) {
|
||||
try {
|
||||
const c = JSON.parse(companyRaw) as {
|
||||
name?: string | null; street?: string | null; postalCode?: string | null;
|
||||
city?: string | null; phone?: string | null; email?: string | null; website?: string | null;
|
||||
};
|
||||
const parts: string[] = [];
|
||||
if (c.name) parts.push(c.name);
|
||||
const addr = [c.street, [c.postalCode, c.city].filter(Boolean).join(' ')].filter(Boolean).join(', ');
|
||||
if (addr) parts.push(addr);
|
||||
if (c.phone) parts.push(`Tel: ${c.phone}`);
|
||||
if (c.email) parts.push(c.email);
|
||||
if (c.website) parts.push(c.website);
|
||||
if (parts.length) companyFooterText = parts.join(' | ');
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
const accentHex = accentColor.replace('#', '');
|
||||
const lightAccent = lightenColor(accentColor, 0.85).replace('#', '');
|
||||
|
||||
|
|
@ -563,6 +606,31 @@ export class ProfileExportService {
|
|||
// --- Kontakt-Infos für linke Spalte ---
|
||||
const leftParagraphs: Paragraph[] = [];
|
||||
|
||||
// Platform-Logo
|
||||
if (platformLogo) {
|
||||
try {
|
||||
const logoBuffer = await sharp(platformLogo)
|
||||
.resize(120, 40, { fit: 'inside' })
|
||||
.png()
|
||||
.toBuffer();
|
||||
leftParagraphs.push(
|
||||
new Paragraph({
|
||||
children: [
|
||||
new ImageRun({
|
||||
data: logoBuffer,
|
||||
transformation: { width: 120, height: 40 },
|
||||
type: 'png',
|
||||
}),
|
||||
],
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 120 },
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.warn('Logo für DOCX konnte nicht geladen werden', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Avatar (rund zugeschnitten)
|
||||
let avatarImageRun: ImageRun | null = null;
|
||||
if (data.avatar) {
|
||||
|
|
@ -605,13 +673,13 @@ export class ProfileExportService {
|
|||
}),
|
||||
);
|
||||
|
||||
// Rolle
|
||||
if (profile && profile.experiences.length > 0) {
|
||||
// Rolle / Jobtitel
|
||||
if (data.jobTitle) {
|
||||
leftParagraphs.push(
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({
|
||||
text: profile.experiences[0].area,
|
||||
text: data.jobTitle,
|
||||
size: 20,
|
||||
color: accentHex,
|
||||
}),
|
||||
|
|
@ -854,6 +922,27 @@ export class ProfileExportService {
|
|||
);
|
||||
}
|
||||
|
||||
// --- Fußzeile ---
|
||||
const footerChildren: Paragraph[] = [];
|
||||
if (companyFooterText) {
|
||||
footerChildren.push(
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({
|
||||
text: companyFooterText,
|
||||
size: 14,
|
||||
color: '999999',
|
||||
}),
|
||||
],
|
||||
alignment: AlignmentType.CENTER,
|
||||
border: {
|
||||
top: { style: BorderStyle.SINGLE, size: 3, color: 'cccccc', space: 4 },
|
||||
},
|
||||
spacing: { before: 80 },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// --- Dokument zusammenstellen ---
|
||||
const document = new Document({
|
||||
styles: {
|
||||
|
|
@ -869,9 +958,16 @@ export class ProfileExportService {
|
|||
{
|
||||
properties: {
|
||||
page: {
|
||||
margin: { top: 720, bottom: 720, left: 720, right: 720 },
|
||||
margin: { top: 720, bottom: companyFooterText ? 800 : 720, left: 720, right: 720 },
|
||||
},
|
||||
},
|
||||
footers: companyFooterText
|
||||
? {
|
||||
default: new Footer({
|
||||
children: footerChildren,
|
||||
}),
|
||||
}
|
||||
: undefined,
|
||||
children: [layoutTable, ...sections],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue