From a56ea2c843bcc2bd6e73022a1fa66cfbc44773c8 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sun, 3 May 2026 07:38:18 +0200 Subject: [PATCH] feat: A4 preview + example data + company profile presets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feature 1: DIN A4 Preview - Markdown→HTML renderer (inline, no dependency) - A4 page container (210mm × 297mm) with print styling - Toggle between "Vorschau" (rendered A4) and "Markdown" (raw) - Print button opens new window with @page A4 CSS - Purple theme for headings, styled tables Feature 2: Example Data Button - "Beispieldaten" button in Generator header - Loads examples/{templateType}_{lang}.json - Prefills all context fields for instant full preview Feature 3: Company Profile Presets - 10 industry presets: SaaS Startup, Consumer App, E-Commerce, IT-Agentur, Maschinenbau, Rechtsanwalt, Arztpraxis, Handwerk, Bildung, Enterprise - Each with pre-filled CompanyProfile + scope hints + recommended docs - PresetSelector component (card grid with icons) - "Manuell ausfuellen" skip option Co-Authored-By: Claude Opus 4.6 (1M context) --- .../_components/PresetSelector.tsx | 49 +++ .../_components/GeneratorPreviewTab.tsx | 214 +++++++++--- .../_components/GeneratorSection.tsx | 27 ++ .../lib/sdk/company-profile-presets.ts | 306 ++++++++++++++++++ 4 files changed, 558 insertions(+), 38 deletions(-) create mode 100644 admin-compliance/app/sdk/company-profile/_components/PresetSelector.tsx create mode 100644 admin-compliance/lib/sdk/company-profile-presets.ts diff --git a/admin-compliance/app/sdk/company-profile/_components/PresetSelector.tsx b/admin-compliance/app/sdk/company-profile/_components/PresetSelector.tsx new file mode 100644 index 0000000..6efe28f --- /dev/null +++ b/admin-compliance/app/sdk/company-profile/_components/PresetSelector.tsx @@ -0,0 +1,49 @@ +'use client' + +import { COMPANY_PROFILE_PRESETS, type CompanyProfilePreset } from '@/lib/sdk/company-profile-presets' + +interface PresetSelectorProps { + onSelect: (preset: CompanyProfilePreset) => void + onSkip: () => void +} + +export function PresetSelector({ onSelect, onSkip }: PresetSelectorProps) { + return ( +
+
+

Welcher Unternehmenstyp passt zu Ihnen?

+

+ Waehlen Sie eine Vorlage fuer Ihre Branche — alle Felder werden vorbefuellt + und Sie koennen anschliessend anpassen. +

+
+ +
+ {COMPANY_PROFILE_PRESETS.map((preset) => ( + + ))} +
+ +
+ +
+
+ ) +} diff --git a/admin-compliance/app/sdk/document-generator/_components/GeneratorPreviewTab.tsx b/admin-compliance/app/sdk/document-generator/_components/GeneratorPreviewTab.tsx index 4c3b260..cee658c 100644 --- a/admin-compliance/app/sdk/document-generator/_components/GeneratorPreviewTab.tsx +++ b/admin-compliance/app/sdk/document-generator/_components/GeneratorPreviewTab.tsx @@ -1,5 +1,6 @@ 'use client' +import { useState } from 'react' import { LegalTemplateResult } from '@/lib/sdk/types' import { RuleEngineResult } from '../ruleEngine' @@ -12,6 +13,72 @@ interface GeneratorPreviewTabProps { onExportMarkdown: () => void } +// ============================================================================ +// Lightweight Markdown → HTML (no dependency needed) +// ============================================================================ + +function markdownToHtml(md: string): string { + let html = md + // Escape HTML entities first + .replace(/&/g, '&') + .replace(//g, '>') + + // Headings + html = html.replace(/^#### (.+)$/gm, '

$1

') + html = html.replace(/^### (.+)$/gm, '

$1

') + html = html.replace(/^## (.+)$/gm, '

$1

') + html = html.replace(/^# (.+)$/gm, '

$1

') + + // Horizontal rules + html = html.replace(/^---$/gm, '
') + + // Bold + Italic + html = html.replace(/\*\*\*(.+?)\*\*\*/g, '$1') + html = html.replace(/\*\*(.+?)\*\*/g, '$1') + html = html.replace(/\*(.+?)\*/g, '$1') + + // Links + html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') + + // Tables (simple) + html = html.replace(/^\|(.+)\|$/gm, (match) => { + const cells = match.split('|').filter(c => c.trim()) + const isHeader = cells.every(c => /^[\s-:]+$/.test(c)) + if (isHeader) return '' + const tag = 'td' + return '' + cells.map(c => `<${tag}>${c.trim()}`).join('') + '' + }) + + // Wrap consecutive table rows + html = html.replace(/((?:.*<\/tr>\n?\n?)?(?:.*<\/tr>\n?)+)/g, (block) => { + const rows = block.split('\n').filter(r => r.startsWith('')) + if (rows.length === 0) return block + const headerRow = rows[0].replace(//g, '').replace(/<\/td>/g, '') + const bodyRows = rows.slice(1).join('\n') + return `${headerRow}${bodyRows}
` + }) + + // Remove separator comments + html = html.replace(/\n?/g, '') + + // Unordered lists + html = html.replace(/^- (.+)$/gm, '
  • $1
  • ') + html = html.replace(/((?:
  • .*<\/li>\n?)+)/g, '
      $1
    ') + + // Paragraphs (lines that aren't already HTML) + html = html.replace(/^(?!<[a-z/]|$)(.+)$/gm, '

    $1

    ') + + // Clean up empty paragraphs + html = html.replace(/

    \s*<\/p>/g, '') + + return html +} + +// ============================================================================ +// Component +// ============================================================================ + export default function GeneratorPreviewTab({ template, ruleResult, @@ -20,12 +87,17 @@ export default function GeneratorPreviewTab({ onCopy, onExportMarkdown, }: GeneratorPreviewTabProps) { + const [viewMode, setViewMode] = useState<'preview' | 'markdown'>('preview') + + const htmlContent = markdownToHtml(renderedContent) + return (

    + {/* Violations */} {ruleResult && ruleResult.violations.length > 0 && (

    - 🔴 {ruleResult.violations.length} Fehler + {ruleResult.violations.length} Fehler

      {ruleResult.violations.map((v) => ( @@ -36,6 +108,8 @@ export default function GeneratorPreviewTab({
    )} + + {/* Warnings */} {ruleResult && ruleResult.warnings.filter((w) => w.id !== 'WARN_LEGAL_REVIEW').length > 0 && (
      @@ -43,69 +117,133 @@ export default function GeneratorPreviewTab({ .filter((w) => w.id !== 'WARN_LEGAL_REVIEW') .map((w) => (
    • - 🟡 [{w.id}] {w.message} + [{w.id}] {w.message}
    • ))}
    )} + + {/* Legal notice */} {ruleResult && (

    - ℹ️ Rechtlicher Hinweis: Diese Vorlage ist MIT-lizenziert. Vor Produktionseinsatz - wird eine rechtliche Überprüfung dringend empfohlen. + Rechtlicher Hinweis: Diese Vorlage ist MIT-lizenziert. Vor Produktionseinsatz + wird eine rechtliche Ueberpruefung dringend empfohlen.

    )} - {ruleResult && ruleResult.appliedDefaults.length > 0 && ( -

    - Defaults angewendet: {ruleResult.appliedDefaults.join(', ')} -

    - )} + {/* Toolbar */}
    - - {missing.length > 0 && ( - - ⚠ {missing.length} Platzhalter noch nicht ausgefüllt - - )} - -
    +
    +
    + +
    + {missing.length > 0 && ( + + {missing.length} Platzhalter offen + + )} + +
    -
    -
    -          {renderedContent}
    -        
    -
    + + {/* Content */} + {viewMode === 'markdown' ? ( +
    +
    +            {renderedContent}
    +          
    +
    + ) : ( +
    + {/* A4 Page */} +
    + +
    +
    +
    + )} + + {/* Attribution */} {template.attributionRequired && template.attributionText && (
    Attribution erforderlich: {template.attributionText} diff --git a/admin-compliance/app/sdk/document-generator/_components/GeneratorSection.tsx b/admin-compliance/app/sdk/document-generator/_components/GeneratorSection.tsx index 01a722c..ea313e6 100644 --- a/admin-compliance/app/sdk/document-generator/_components/GeneratorSection.tsx +++ b/admin-compliance/app/sdk/document-generator/_components/GeneratorSection.tsx @@ -160,6 +160,33 @@ export default function GeneratorSection({
    )}
    +
    + +