import { useEffect, useMemo, useState } from 'react'; /** * Live-Vorschau neben dem Formular. * * Die Vorschau ist absichtlich kontingentiert: Jede Generierung kostet einen * Aufruf der Scriptalizer-API (Tageslimit). Der Zähler ist daher Teil der * Bedienung und kein Beiwerk. * * Schriftstück und zugehöriger Umschlag gehören zusammen und werden gemeinsam * gezeigt (Schriftstück oben, Umschlag unten). Die Pfeile blättern je Empfänger. */ export type PreviewDoc = { art: 'brief' | 'umschlag'; svg: string }; /** Ein Empfänger-Dokument: Schriftstück + optional zugehöriger Umschlag. */ type Gruppe = { brief?: PreviewDoc; umschlag?: PreviewDoc }; type Props = { titel: string; format: string; docs: PreviewDoc[]; gesamtDokumente: number; verbleibend: number; maximal: number; laedt: boolean; fehler: string | null; onGenerate: () => void; bereit: boolean; }; /** Flache [brief, umschlag, brief, …]-Liste zu Empfänger-Gruppen bündeln. */ function gruppiere(docs: PreviewDoc[]): Gruppe[] { const gruppen: Gruppe[] = []; for (const d of docs) { if (d.art === 'umschlag') { const letzte = gruppen[gruppen.length - 1]; if (letzte && letzte.brief && !letzte.umschlag) letzte.umschlag = d; else gruppen.push({ umschlag: d }); } else { gruppen.push({ brief: d }); } } return gruppen; } export default function PreviewPanel({ titel, format, docs, gesamtDokumente, verbleibend, maximal, laedt, fehler, onGenerate, bereit, }: Props) { const [index, setIndex] = useState(0); const [zoomSvg, setZoomSvg] = useState(null); // Die Zoom-Fläche wird auf den Inhaltsbereich begrenzt (unterhalb der // Preiszeile, rechts der Seitenleiste), statt das ganze Fenster zu belegen. const [zoomBox, setZoomBox] = useState({}); const gruppen = useMemo(() => gruppiere(docs), [docs]); function messeBox(): React.CSSProperties { if (typeof window === 'undefined' || window.innerWidth <= 900) return {}; const content = document.querySelector('.sk-content'); if (!content) return {}; const c = content.getBoundingClientRect(); const bar = document.querySelector('.sk-pricebar'); const top = Math.max(c.top, bar ? bar.getBoundingClientRect().bottom : c.top); return { top: `${top}px`, left: `${c.left}px`, width: `${c.width}px`, height: `${Math.max(0, c.bottom - top)}px`, right: 'auto', bottom: 'auto', }; } function openZoom(svg: string) { setZoomBox(messeBox()); setZoomSvg(svg); } /** Seitenverhältnis (Breite/Höhe) aus der viewBox – sperrt die Lightbox-Abmessungen. */ function seitenverhaeltnis(svg: string): string { const m = svg.match(/viewBox="0 0 ([\d.]+) ([\d.]+)"/); return m ? `${m[1]} / ${m[2]}` : '210 / 297'; } /** Musterbild der großen Vorschau als PNG herunterladen (SVG → Canvas). */ function bildSpeichern() { if (!zoomSvg) return; const m = zoomSvg.match(/viewBox="0 0 ([\d.]+) ([\d.]+)"/); const w = Math.round(m ? Number(m[1]) : 794); const h = Math.round(m ? Number(m[2]) : 1123); const scale = 2; // für ein scharfes Musterbild // Gespeicherte Vorschau in Kugelschreiber-Blau (wie die Anzeige); die // an die Produktion gehenden SVGs bleiben davon unberührt (schwarz). const inkSvg = zoomSvg.replace(/stroke="#0{3,6}"/gi, 'stroke="#1a2a6c"'); const url = URL.createObjectURL(new Blob([inkSvg], { type: 'image/svg+xml;charset=utf-8' })); const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = w * scale; canvas.height = h * scale; const ctx = canvas.getContext('2d'); if (ctx) { ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); canvas.toBlob((png) => { if (png) { const a = document.createElement('a'); a.href = URL.createObjectURL(png); a.download = 'skrift-musterbild.png'; a.click(); URL.revokeObjectURL(a.href); } URL.revokeObjectURL(url); }, 'image/png'); } else { URL.revokeObjectURL(url); } }; img.onerror = () => URL.revokeObjectURL(url); img.src = url; } // Nach einer neuen Generierung wieder vorn beginnen. useEffect(() => { setIndex(0); }, [docs]); // Vergrößerte Ansicht: Escape schließt, Resize misst die Fläche neu. useEffect(() => { if (!zoomSvg) return; const zu = (e: KeyboardEvent) => e.key === 'Escape' && setZoomSvg(null); const neu = () => setZoomBox(messeBox()); window.addEventListener('keydown', zu); window.addEventListener('resize', neu); return () => { window.removeEventListener('keydown', zu); window.removeEventListener('resize', neu); }; }, [zoomSvg]); const aktuell = gruppen[index] ?? null; const anzahl = gruppen.length || gesamtDokumente; /** Ein einzelnes Dokument (Schriftstück oder Umschlag) mit Beschriftung. */ const DocBox = ({ label, svg }: { label: string; svg: string }) => (
openZoom(svg)} role="button" tabIndex={0} title="Zum Vergrößern klicken" onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') openZoom(svg); }} >
{label}
{/* Das SVG stammt aus unserer eigenen Generierung, nicht aus Nutzereingaben. */}
); return ( ); }