diff --git a/src/App.tsx b/src/App.tsx index 738e7cf..62c3198 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import Auftraege from './routes/Auftraege'; import Stammdaten from './routes/Stammdaten'; import Configurator from './routes/Configurator'; import MusterSeite from './routes/Muster'; +import Textmenge from './routes/Textmenge'; import { loadSession } from './lib/api'; /** @@ -37,6 +38,8 @@ export default function App() { } /> {/* Muster als eigene Seite (kein Popup) – Direktlink /muster. */} } /> + {/* Textmengen-Tool – öffentlich, Direktlink /textmenge. */} + } /> } diff --git a/src/routes/Textmenge.tsx b/src/routes/Textmenge.tsx new file mode 100644 index 0000000..bb6f7f8 --- /dev/null +++ b/src/routes/Textmenge.tsx @@ -0,0 +1,194 @@ +import { useEffect, useMemo, useState } from 'react'; +import { fetchFormats, fetchSettings, generatePreview } from '../lib/api'; +import type { Format, PublicSettings, PreviewDoc, PreviewOverflow } from '../lib/api'; +import PreviewPanel from '../components/PreviewPanel'; + +/** Schriftarten wie im Konfigurator/manuellen Druck. */ +const SCHRIFTEN = [ + { key: 'tilda', name: 'Tilda' }, + { key: 'alva', name: 'Alva' }, + { key: 'ellie', name: 'Ellie' }, +]; + +/** Stabile anonyme Kennung fürs Vorschau-Kontingent (wie im Konfigurator). */ +function clientId(): string { + const KEY = 'skrift.client'; + let id = localStorage.getItem(KEY); + if (!id) { + id = `c-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`; + localStorage.setItem(KEY, id); + } + return id; +} + +/** Fehlermeldung bei Zeilen-Überlauf (wie im Konfigurator). */ +function overflowMeldung(overflow?: PreviewOverflow[]) { + const schlimmste = (overflow ?? []).reduce<{ lineCount: number; lineLimit: number } | null>( + (m, o) => (o.lineCount > (m?.lineCount ?? 0) ? o : m), + null, + ); + const detail = schlimmste ? ` (${schlimmste.lineCount} von ${schlimmste.lineLimit} Zeilen)` : ''; + return `Der Text ist zu lang für das gewählte Format${detail}. Bitte kürze den Text.`; +} + +/** + * Textmengen-Tool (/textmenge): Format- und Schriftwahl, Texteingabe mit denselben + * Möglichkeiten wie sonst (Tabulator, gesperrte Zeichen, [[seitenumbruch]]). Über + * „Vorschau generieren" wird das Schriftstück gerendert (Kugelschreiber-Optik) – + * so sieht man, ob der Text passt, sonst kommt eine Fehlermeldung. + */ +export default function Textmenge() { + const [formate, setFormate] = useState([]); + const [settings, setSettings] = useState(null); + const [format, setFormat] = useState('a4'); + const [font, setFont] = useState('tilda'); + const [text, setText] = useState(''); + const [hinweis, setHinweis] = useState(''); + + const [docs, setDocs] = useState([]); + const [laedt, setLaedt] = useState(false); + const [fehler, setFehler] = useState(null); + const [verbleibend, setVerbleibend] = useState(0); + const [maximal, setMaximal] = useState(0); + + useEffect(() => { + fetchFormats() + .then((f) => { + setFormate(f); + setFormat(f.find((x) => x.key === 'a4')?.key ?? f[0]?.key ?? 'a4'); + }) + .catch(() => {}); + fetchSettings().then(setSettings).catch(() => {}); + }, []); + + // Gesperrte Zeichen wie im Konfigurator (Zeilenumbruch/Tab bleiben erlaubt). + const verbotenRe = useMemo(() => { + const wl = settings?.char_whitelist; + if (!wl) return null; + try { + return new RegExp(`[^${wl}\\n\\r\\t]`, 'g'); + } catch { + return null; + } + }, [settings?.char_whitelist]); + + const saeubere = (v: string) => { + if (!verbotenRe) return v; + const treffer = v.match(verbotenRe); + if (!treffer?.length) return v; + const einmalig = [...new Set(treffer)].map((c) => (c === ' ' ? '␣' : c)).join(' '); + setHinweis(`Zeichen ${einmalig} ist nicht erlaubt und wurde entfernt.`); + window.setTimeout(() => setHinweis(''), 4000); + return v.replace(verbotenRe, ''); + }; + + // Neuer Text/Format/Schrift → alte Vorschau verwerfen (nicht live neu laden). + useEffect(() => { + setDocs([]); + setFehler(null); + }, [text, font, format]); + + async function generieren() { + if (!text.trim()) return; + setLaedt(true); + setFehler(null); + try { + const ergebnis = await generatePreview({ + client_id: clientId(), + text, + font, + format, + entries: [{}], + }); + setVerbleibend(ergebnis.remaining ?? 0); + setMaximal(ergebnis.limit ?? 0); + if (ergebnis.has_overflow) { + setDocs([]); + setFehler(overflowMeldung(ergebnis.overflow)); + } else { + setDocs(ergebnis.docs ?? []); + } + } catch (e) { + setDocs([]); + setFehler((e as Error)?.message || 'Vorschau fehlgeschlagen.'); + } finally { + setLaedt(false); + } + } + + return ( +
+

Textmenge prüfen

+

+ Text eingeben, Format und Schrift wählen und eine Vorschau erzeugen – so siehst du, ob der Text auf ein Schriftstück passt. +

+ +
+
+
+ + +
+ +