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",
},
});
}