import { Page, View } from "@react-pdf/renderer"; import type { ReactNode } from "react"; import type { PageFrame } from "./engine/geometry"; import { chromeHeight } from "./engine/metrics"; import type { RenderModel } from "./model/build"; import type { TemplateDefinition } from "./templates/contract"; import { AvailableWidthProvider } from "./blocks/AvailableWidth"; import type { DecorItem } from "./blocks/FirstPageHeader"; import { PageChrome } from "./blocks/PageChrome"; /** Height the footer strip takes inside the bottom padding: the text line, a 6 pt gap and the rule. */ export const FOOTER_RESERVE = 24; /** Room above the content on pages 2+ for the continuation band, inside the top margin. */ export const CONTINUATION_RESERVE = 18; export interface InvoicePageProps { frame: PageFrame; model: RenderModel; template: TemplateDefinition; children?: ReactNode; } /** * The only `` in the app: size and padding come from the frame (no page-size or margin literal * anywhere else), the flow is the template's, and the chrome (footer, continuation band) is added after * it as fixed page-level boxes. No lineHeight here: it would reach the render-prop page-number text. */ export function InvoicePage({ frame, model, template, children }: InvoicePageProps) { const { pad } = frame; const { chrome, decor } = template; const marginBottom = pad.bottom - frame.footerReserve; const ruleY = frame.page.height - marginBottom - chromeHeight(chrome.role ?? "footer") - 6.6; const bandLeft = `${model.docTitle} ${model.meta.number} ยท ${model.meta.date}`; const items: DecorItem[] = [...(decor.everyPage?.(frame, model) ?? [])]; if (chrome.rule) { items.push({ x: pad.left, y: ruleY, width: frame.contentWidth, height: 0.6, children: }); } // The footer sits inside the template's text column, which may be narrower than the page content width. const inset = chrome.inset ?? { left: 0, right: 0 }; const chromeFrame: PageFrame = { ...frame, pad: { ...pad, left: pad.left + inset.left, right: pad.right + inset.right }, contentWidth: frame.contentWidth - inset.left - inset.right, }; const bandInset = chrome.bandInset ?? inset; const bandBox = { left: pad.left + bandInset.left, width: frame.contentWidth - bandInset.left - bandInset.right }; return ( {children} ); }