Files
pn-new-crm/src/lib/pdf/render.ts
Matt 73184c51e0 feat(pdf): brand kit foundation for @react-pdf/renderer
Phase 1 / commit 1 of 14 — installs deps and lays down the brand-kit
primitives used by every internal-only PDF. No callers wired yet.

Adds:
  @react-pdf/renderer 4.5.1   one engine for internal exports
  unpdf 1.6.2                 reserved for berth-PDF parser tier-2
  react-image-crop 11.0.10    admin logo crop UI (commit 2)
  svgo 4.0.1                  SVG sanitization on logo upload (commit 2)

brand-kit/
  tokens.ts          single source of truth for colors/fonts/spacing
  logo.ts            resolvePortLogo() — cached, soft-fallback
  DocumentShell      <Document><Page> + fixed Header + fixed Footer
  Header             dark band, logo slot (letterboxed) + text fallback
  Footer             page N of M + generated-at + confidential tag
  Section            heading + bottom border
  KeyValueGrid       2-col (default) or stacked label/value
  DataTable          zebra rows + sticky header + totals row + empty state
  Badge              5 tone pills
  charts/
    BarChart         pure SVG, 4-tick y-axis, optional value labels
    LineChart        pure SVG, line + markers + grid
    PieChart         pure SVG, donut-or-pie + side legend
    FunnelChart      pure SVG, slope-cut slices for pipeline stages

render.ts            renderToBuffer + renderToStream wrappers, typed

svg-primitives.tsx   <SvgLabel> wraps react-pdf SVG <Text> to bridge
                     missing TS declarations for fontSize/fontFamily

Smoke test renders a kitchen-sink Document including every primitive
and every chart, plus an empty-data path. 1293+4 vitest tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:45:28 +02:00

33 lines
1.1 KiB
TypeScript

import { renderToBuffer, renderToStream, type DocumentProps } from '@react-pdf/renderer';
import type { ReactElement } from 'react';
import { logger } from '@/lib/logger';
type DocumentElement = ReactElement<DocumentProps>;
/**
* Render a react-pdf element tree to PDF bytes. Use for one-shot PDFs that
* fit in memory comfortably (reports, record exports, parent-company exports).
*
* For photo-heavy or hundreds-of-entries PDFs, see `renderPdfStream` or use
* `expense-pdf.service.ts` (pdfkit streaming).
*/
export async function renderPdf(element: DocumentElement): Promise<Buffer> {
try {
const buf = await renderToBuffer(element);
return buf;
} catch (err) {
logger.error({ err }, 'PDF render failed');
throw new Error('Failed to render PDF');
}
}
/**
* Stream-render a react-pdf element tree. Pages are emitted incrementally so
* memory peaks are bounded. Caller should pipe the stream to the response
* (or convert via `Readable.toWeb` for a Web Response).
*/
export async function renderPdfStream(element: DocumentElement): Promise<NodeJS.ReadableStream> {
return renderToStream(element);
}