import { Document, Page, Path, StyleSheet, Svg, Text, View, renderToBuffer } from "@react-pdf/renderer"; import type { DocumentProps } from "@react-pdf/renderer"; import type { ReactElement } from "react"; /** * Gemeinsames Aussehen aller PDF-Ausgaben: einfarbiges Logo, Titel, Datum, Seitenzahl. * Gerendert wird serverseitig ohne Browser (@react-pdf/renderer). */ const NAVY = "#1E2A44"; const stile = StyleSheet.create({ seite: { paddingTop: 40, paddingBottom: 48, paddingHorizontal: 40, fontSize: 9, color: "#1F2D3D" }, kopf: { flexDirection: "row", alignItems: "center", marginBottom: 18 }, kopfText: { marginLeft: 10 }, marke: { fontSize: 14, color: NAVY }, titel: { fontSize: 16, marginBottom: 2 }, untertitel: { fontSize: 9, color: "#6C757D" }, tabellenKopf: { flexDirection: "row", borderBottomWidth: 1, borderBottomColor: NAVY, paddingBottom: 4, marginBottom: 2, fontSize: 8, color: NAVY, }, zeile: { flexDirection: "row", borderBottomWidth: 0.5, borderBottomColor: "#DEE2E6", paddingVertical: 3, }, summenZeile: { flexDirection: "row", borderTopWidth: 1, borderTopColor: NAVY, paddingTop: 4, marginTop: 2 }, fuss: { position: "absolute", bottom: 24, left: 40, right: 40, flexDirection: "row", justifyContent: "space-between", fontSize: 7, color: "#6C757D", }, abschnitt: { marginTop: 14, marginBottom: 6, fontSize: 11, color: NAVY }, }); function Logo() { return ( ); } export interface Spalte { titel: string; breite: number; wert: (zeile: T) => string; rechts?: boolean; } /** Etwas Luft zwischen den Spalten, sonst stoßen rechtsbündige Zahlen an den nächsten Text. */ const zelle = (breite: number, rechts?: boolean) => ({ width: `${breite}%`, textAlign: (rechts ? "right" : "left") as "right" | "left", paddingRight: 6, }); export function Tabelle({ spalten, zeilen, summe, }: { spalten: Spalte[]; zeilen: T[]; summe?: string[]; }) { return ( {spalten.map((spalte) => ( {spalte.titel} ))} {zeilen.map((zeile, index) => ( {spalten.map((spalte) => ( {spalte.wert(zeile)} ))} ))} {summe ? ( {summe.map((wert, index) => ( {wert} ))} ) : null} ); } export function Abschnitt({ titel }: { titel: string }) { return {titel}; } export function Bericht({ titel, untertitel, children, quer = false, }: { titel: string; untertitel?: string; children: React.ReactNode; quer?: boolean; }) { const erstellt = new Intl.DateTimeFormat("de-DE", { timeZone: "Europe/Berlin", dateStyle: "long", }).format(new Date()); return ( ProxLedger Proxmox Subscription Dashboard {titel} {untertitel ? {untertitel} : null} {children} Erstellt am {erstellt} `Seite ${pageNumber} von ${totalPages}`} /> ); } export async function pdfAntwort(element: ReactElement, dateiname: string): Promise { const puffer = await renderToBuffer(element); return new Response(new Uint8Array(puffer), { headers: { "Content-Type": "application/pdf", "Content-Disposition": `attachment; filename="${dateiname}"`, "Cache-Control": "no-store", }, }); }