import type { ExportResult, ReportPayload } from '@/lib/reports/types'; /** * PDF export. Unlike CSV + Excel (which can serialise in the browser), * PDF generation runs server-side via `@react-pdf/renderer` so the * client posts the payload to `/api/v1/reports/export-pdf` and receives * the rendered bytes back. * * The server resolves the active port's branding (logo + primary * color + name) so per-port theming flows through automatically — the * client doesn't need to send branding fields. */ interface PdfExportOptions { /** Filename override mirroring the CSV / Excel exporters. */ filenameOverride?: string; } export async function exportReportAsPdf( payload: ReportPayload, options: PdfExportOptions = {}, ): Promise { // Serialise dates to ISO so they survive the JSON trip. const wireBody = { title: payload.title, description: payload.description, filenameSlug: payload.filenameSlug, range: { from: payload.range.from.toISOString(), to: payload.range.to.toISOString(), }, kpis: payload.kpis, sections: payload.sections.map((s) => ({ title: s.title, columns: s.columns.map((c) => ({ key: c.key, label: c.label, align: c.align, // `format` is a function and isn't serialisable; the server // falls back to plain stringification, which matches the CSV // exporter's default behaviour when no format is set. })), // Apply client-side format functions BEFORE serialising so the // server sees pre-formatted strings. This preserves money / // percentage / date formatting that the original ReportPayload // declared. rows: s.rows.map((row) => { const out: Record = {}; for (const col of s.columns) { out[col.key] = col.format ? col.format(row[col.key]) : row[col.key]; } return out; }), })), filenameOverride: options.filenameOverride, }; const res = await fetch('/api/v1/reports/export-pdf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(wireBody), credentials: 'include', }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(text || `PDF generation failed (${res.status})`); } const blob = await res.blob(); const cdHeader = res.headers.get('content-disposition') ?? ''; const match = cdHeader.match(/filename="([^"]+)"/); const filename = match?.[1] ?? options.filenameOverride ?? defaultPdfFilename(payload); return { filename, mimeType: 'application/pdf', body: blob, }; } export function defaultPdfFilename(payload: ReportPayload): string { const fromIso = payload.range.from.toISOString().slice(0, 10); const toIso = payload.range.to.toISOString().slice(0, 10); return `${payload.filenameSlug}-${fromIso}_${toIso}.pdf`; }