feat: A4 preview + example data + company profile presets
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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 (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-xl font-bold text-gray-900">Welcher Unternehmenstyp passt zu Ihnen?</h2>
|
||||||
|
<p className="text-sm text-gray-500 mt-2">
|
||||||
|
Waehlen Sie eine Vorlage fuer Ihre Branche — alle Felder werden vorbefuellt
|
||||||
|
und Sie koennen anschliessend anpassen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3">
|
||||||
|
{COMPANY_PROFILE_PRESETS.map((preset) => (
|
||||||
|
<button
|
||||||
|
key={preset.id}
|
||||||
|
onClick={() => onSelect(preset)}
|
||||||
|
className="flex flex-col items-center gap-2 p-4 bg-white border border-gray-200 rounded-xl hover:border-purple-400 hover:shadow-md transition-all text-center group"
|
||||||
|
>
|
||||||
|
<span className="text-3xl">{preset.icon}</span>
|
||||||
|
<span className="text-sm font-medium text-gray-900 group-hover:text-purple-700">
|
||||||
|
{preset.label}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-gray-500 leading-tight">
|
||||||
|
{preset.description}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<button
|
||||||
|
onClick={onSkip}
|
||||||
|
className="text-sm text-gray-400 hover:text-gray-600 underline"
|
||||||
|
>
|
||||||
|
Manuell ausfuellen (ohne Vorlage)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
import { LegalTemplateResult } from '@/lib/sdk/types'
|
import { LegalTemplateResult } from '@/lib/sdk/types'
|
||||||
import { RuleEngineResult } from '../ruleEngine'
|
import { RuleEngineResult } from '../ruleEngine'
|
||||||
|
|
||||||
@@ -12,6 +13,72 @@ interface GeneratorPreviewTabProps {
|
|||||||
onExportMarkdown: () => void
|
onExportMarkdown: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Lightweight Markdown → HTML (no dependency needed)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
function markdownToHtml(md: string): string {
|
||||||
|
let html = md
|
||||||
|
// Escape HTML entities first
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
|
||||||
|
// Headings
|
||||||
|
html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>')
|
||||||
|
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
||||||
|
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
||||||
|
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
||||||
|
|
||||||
|
// Horizontal rules
|
||||||
|
html = html.replace(/^---$/gm, '<hr/>')
|
||||||
|
|
||||||
|
// Bold + Italic
|
||||||
|
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>')
|
||||||
|
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||||
|
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||||
|
|
||||||
|
// Links
|
||||||
|
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" class="text-purple-600 underline">$1</a>')
|
||||||
|
|
||||||
|
// 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 '<!-- separator -->'
|
||||||
|
const tag = 'td'
|
||||||
|
return '<tr>' + cells.map(c => `<${tag}>${c.trim()}</${tag}>`).join('') + '</tr>'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wrap consecutive table rows
|
||||||
|
html = html.replace(/((?:<tr>.*<\/tr>\n?<!-- separator -->\n?)?(?:<tr>.*<\/tr>\n?)+)/g, (block) => {
|
||||||
|
const rows = block.split('\n').filter(r => r.startsWith('<tr>'))
|
||||||
|
if (rows.length === 0) return block
|
||||||
|
const headerRow = rows[0].replace(/<td>/g, '<th>').replace(/<\/td>/g, '</th>')
|
||||||
|
const bodyRows = rows.slice(1).join('\n')
|
||||||
|
return `<table><thead>${headerRow}</thead><tbody>${bodyRows}</tbody></table>`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove separator comments
|
||||||
|
html = html.replace(/<!-- separator -->\n?/g, '')
|
||||||
|
|
||||||
|
// Unordered lists
|
||||||
|
html = html.replace(/^- (.+)$/gm, '<li>$1</li>')
|
||||||
|
html = html.replace(/((?:<li>.*<\/li>\n?)+)/g, '<ul>$1</ul>')
|
||||||
|
|
||||||
|
// Paragraphs (lines that aren't already HTML)
|
||||||
|
html = html.replace(/^(?!<[a-z/]|$)(.+)$/gm, '<p>$1</p>')
|
||||||
|
|
||||||
|
// Clean up empty paragraphs
|
||||||
|
html = html.replace(/<p>\s*<\/p>/g, '')
|
||||||
|
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Component
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
export default function GeneratorPreviewTab({
|
export default function GeneratorPreviewTab({
|
||||||
template,
|
template,
|
||||||
ruleResult,
|
ruleResult,
|
||||||
@@ -20,12 +87,17 @@ export default function GeneratorPreviewTab({
|
|||||||
onCopy,
|
onCopy,
|
||||||
onExportMarkdown,
|
onExportMarkdown,
|
||||||
}: GeneratorPreviewTabProps) {
|
}: GeneratorPreviewTabProps) {
|
||||||
|
const [viewMode, setViewMode] = useState<'preview' | 'markdown'>('preview')
|
||||||
|
|
||||||
|
const htmlContent = markdownToHtml(renderedContent)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{/* Violations */}
|
||||||
{ruleResult && ruleResult.violations.length > 0 && (
|
{ruleResult && ruleResult.violations.length > 0 && (
|
||||||
<div className="bg-red-50 border border-red-200 rounded-xl p-4">
|
<div className="bg-red-50 border border-red-200 rounded-xl p-4">
|
||||||
<p className="text-sm font-semibold text-red-700 mb-2">
|
<p className="text-sm font-semibold text-red-700 mb-2">
|
||||||
🔴 {ruleResult.violations.length} Fehler
|
{ruleResult.violations.length} Fehler
|
||||||
</p>
|
</p>
|
||||||
<ul className="space-y-1">
|
<ul className="space-y-1">
|
||||||
{ruleResult.violations.map((v) => (
|
{ruleResult.violations.map((v) => (
|
||||||
@@ -36,6 +108,8 @@ export default function GeneratorPreviewTab({
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Warnings */}
|
||||||
{ruleResult && ruleResult.warnings.filter((w) => w.id !== 'WARN_LEGAL_REVIEW').length > 0 && (
|
{ruleResult && ruleResult.warnings.filter((w) => w.id !== 'WARN_LEGAL_REVIEW').length > 0 && (
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-4">
|
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-4">
|
||||||
<ul className="space-y-1">
|
<ul className="space-y-1">
|
||||||
@@ -43,69 +117,133 @@ export default function GeneratorPreviewTab({
|
|||||||
.filter((w) => w.id !== 'WARN_LEGAL_REVIEW')
|
.filter((w) => w.id !== 'WARN_LEGAL_REVIEW')
|
||||||
.map((w) => (
|
.map((w) => (
|
||||||
<li key={w.id} className="text-xs text-yellow-700">
|
<li key={w.id} className="text-xs text-yellow-700">
|
||||||
🟡 <span className="font-mono font-medium">[{w.id}]</span> {w.message}
|
<span className="font-mono font-medium">[{w.id}]</span> {w.message}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Legal notice */}
|
||||||
{ruleResult && (
|
{ruleResult && (
|
||||||
<div className="bg-blue-50 border border-blue-200 rounded-xl p-3">
|
<div className="bg-blue-50 border border-blue-200 rounded-xl p-3">
|
||||||
<p className="text-xs text-blue-700">
|
<p className="text-xs text-blue-700">
|
||||||
ℹ️ Rechtlicher Hinweis: Diese Vorlage ist MIT-lizenziert. Vor Produktionseinsatz
|
Rechtlicher Hinweis: Diese Vorlage ist MIT-lizenziert. Vor Produktionseinsatz
|
||||||
wird eine rechtliche Überprüfung dringend empfohlen.
|
wird eine rechtliche Ueberpruefung dringend empfohlen.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{ruleResult && ruleResult.appliedDefaults.length > 0 && (
|
|
||||||
<p className="text-xs text-gray-400">
|
|
||||||
Defaults angewendet: {ruleResult.appliedDefaults.join(', ')}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
{/* Toolbar */}
|
||||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||||
<span className="text-sm text-gray-600">
|
<div className="flex gap-1 bg-gray-100 rounded-lg p-0.5">
|
||||||
{missing.length > 0 && (
|
|
||||||
<span className="text-orange-600">
|
|
||||||
⚠ {missing.length} Platzhalter noch nicht ausgefüllt
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<button
|
<button
|
||||||
onClick={onCopy}
|
onClick={() => setViewMode('preview')}
|
||||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs border border-gray-200 rounded-lg hover:bg-gray-50 text-gray-600 transition-colors"
|
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
|
||||||
|
viewMode === 'preview' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-500'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
Vorschau
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
||||||
</svg>
|
|
||||||
Kopieren
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onExportMarkdown}
|
onClick={() => setViewMode('markdown')}
|
||||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs border border-gray-200 rounded-lg hover:bg-gray-50 text-gray-600 transition-colors"
|
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
|
||||||
|
viewMode === 'markdown' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-500'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
Markdown
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
</button>
|
||||||
</svg>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{missing.length > 0 && (
|
||||||
|
<span className="text-xs text-orange-600">
|
||||||
|
{missing.length} Platzhalter offen
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<button onClick={onCopy} className="px-3 py-1.5 text-xs border border-gray-200 rounded-lg hover:bg-gray-50 text-gray-600">
|
||||||
|
Kopieren
|
||||||
|
</button>
|
||||||
|
<button onClick={onExportMarkdown} className="px-3 py-1.5 text-xs border border-gray-200 rounded-lg hover:bg-gray-50 text-gray-600">
|
||||||
Markdown
|
Markdown
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => window.print()}
|
onClick={() => {
|
||||||
className="flex items-center gap-1.5 px-4 py-1.5 text-xs bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
const printWindow = window.open('', '_blank')
|
||||||
|
if (!printWindow) return
|
||||||
|
printWindow.document.write(`<!DOCTYPE html><html><head><title>${template.documentTitle || 'Dokument'}</title><style>
|
||||||
|
@page { size: A4; margin: 25mm 20mm; }
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 11pt; line-height: 1.6; color: #1a202c; max-width: 170mm; margin: 0 auto; }
|
||||||
|
h1 { font-size: 18pt; color: #5b21b6; margin: 24pt 0 8pt; border-bottom: 2px solid #7c3aed; padding-bottom: 4pt; }
|
||||||
|
h2 { font-size: 14pt; color: #1f2937; margin: 18pt 0 6pt; }
|
||||||
|
h3 { font-size: 12pt; color: #374151; margin: 12pt 0 4pt; }
|
||||||
|
h4 { font-size: 11pt; color: #4b5563; margin: 10pt 0 4pt; }
|
||||||
|
table { width: 100%; border-collapse: collapse; margin: 8pt 0; font-size: 10pt; }
|
||||||
|
th { background: #f5f3ff; color: #5b21b6; font-weight: 600; text-align: left; padding: 6pt 8pt; border: 1px solid #e5e7eb; }
|
||||||
|
td { padding: 5pt 8pt; border: 1px solid #e5e7eb; vertical-align: top; }
|
||||||
|
ul { padding-left: 20pt; }
|
||||||
|
li { margin: 2pt 0; }
|
||||||
|
hr { border: none; border-top: 1px solid #e5e7eb; margin: 16pt 0; }
|
||||||
|
a { color: #7c3aed; }
|
||||||
|
p { margin: 4pt 0; }
|
||||||
|
strong { font-weight: 600; }
|
||||||
|
</style></head><body>${htmlContent}</body></html>`)
|
||||||
|
printWindow.document.close()
|
||||||
|
printWindow.print()
|
||||||
|
}}
|
||||||
|
className="px-4 py-1.5 text-xs bg-purple-600 text-white rounded-lg hover:bg-purple-700"
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
|
|
||||||
</svg>
|
|
||||||
PDF drucken
|
PDF drucken
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 rounded-xl border border-gray-200 p-6 max-h-[600px] overflow-y-auto">
|
|
||||||
<pre className="text-sm text-gray-800 whitespace-pre-wrap leading-relaxed font-sans">
|
{/* Content */}
|
||||||
|
{viewMode === 'markdown' ? (
|
||||||
|
<div className="bg-gray-50 rounded-xl border border-gray-200 p-6 max-h-[800px] overflow-y-auto">
|
||||||
|
<pre className="text-sm text-gray-800 whitespace-pre-wrap leading-relaxed font-mono">
|
||||||
{renderedContent}
|
{renderedContent}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="bg-gray-100 rounded-xl p-8 flex justify-center overflow-y-auto max-h-[85vh]">
|
||||||
|
{/* A4 Page */}
|
||||||
|
<div
|
||||||
|
className="bg-white shadow-lg border border-gray-300"
|
||||||
|
style={{
|
||||||
|
width: '210mm',
|
||||||
|
minHeight: '297mm',
|
||||||
|
padding: '25mm 20mm',
|
||||||
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||||||
|
fontSize: '11pt',
|
||||||
|
lineHeight: '1.6',
|
||||||
|
color: '#1a202c',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<style>{`
|
||||||
|
.a4-content h1 { font-size: 18pt; color: #5b21b6; margin: 24pt 0 8pt; border-bottom: 2px solid #7c3aed; padding-bottom: 4pt; }
|
||||||
|
.a4-content h2 { font-size: 14pt; color: #1f2937; margin: 18pt 0 6pt; }
|
||||||
|
.a4-content h3 { font-size: 12pt; color: #374151; margin: 12pt 0 4pt; }
|
||||||
|
.a4-content h4 { font-size: 11pt; color: #4b5563; margin: 10pt 0 4pt; }
|
||||||
|
.a4-content table { width: 100%; border-collapse: collapse; margin: 8pt 0; font-size: 10pt; }
|
||||||
|
.a4-content th { background: #f5f3ff; color: #5b21b6; font-weight: 600; text-align: left; padding: 6pt 8pt; border: 1px solid #e5e7eb; }
|
||||||
|
.a4-content td { padding: 5pt 8pt; border: 1px solid #e5e7eb; vertical-align: top; }
|
||||||
|
.a4-content ul { padding-left: 20pt; margin: 4pt 0; }
|
||||||
|
.a4-content li { margin: 2pt 0; }
|
||||||
|
.a4-content hr { border: none; border-top: 1px solid #e5e7eb; margin: 16pt 0; }
|
||||||
|
.a4-content a { color: #7c3aed; text-decoration: underline; }
|
||||||
|
.a4-content p { margin: 4pt 0; }
|
||||||
|
.a4-content strong { font-weight: 600; }
|
||||||
|
`}</style>
|
||||||
|
<div
|
||||||
|
className="a4-content"
|
||||||
|
dangerouslySetInnerHTML={{ __html: htmlContent }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Attribution */}
|
||||||
{template.attributionRequired && template.attributionText && (
|
{template.attributionRequired && template.attributionText && (
|
||||||
<div className="text-xs text-orange-600 bg-orange-50 p-3 rounded-lg border border-orange-200">
|
<div className="text-xs text-orange-600 bg-orange-50 p-3 rounded-lg border border-orange-200">
|
||||||
<strong>Attribution erforderlich:</strong> {template.attributionText}
|
<strong>Attribution erforderlich:</strong> {template.attributionText}
|
||||||
|
|||||||
@@ -160,6 +160,33 @@ export default function GeneratorSection({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
// Load example data for current template type
|
||||||
|
const templateType = template.templateType || ''
|
||||||
|
const lang = template.language || 'de'
|
||||||
|
const exampleFile = `/sdk/document-generator/examples/${templateType}_${lang}.json`
|
||||||
|
fetch(exampleFile)
|
||||||
|
.then(r => r.ok ? r.json() : null)
|
||||||
|
.then(data => {
|
||||||
|
if (!data?.context) return
|
||||||
|
const ctx = data.context
|
||||||
|
for (const [section, fields] of Object.entries(ctx)) {
|
||||||
|
if (typeof fields === 'object' && fields) {
|
||||||
|
for (const [key, value] of Object.entries(fields as Record<string, unknown>)) {
|
||||||
|
onContextChange(section as keyof TemplateContext, key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {/* no example available */})
|
||||||
|
}}
|
||||||
|
className="px-3 py-1 text-xs bg-blue-50 text-blue-600 border border-blue-200 rounded-lg hover:bg-blue-100 transition-colors"
|
||||||
|
>
|
||||||
|
Beispieldaten
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors shrink-0" aria-label="Schließen">
|
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors shrink-0" aria-label="Schließen">
|
||||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
|||||||
@@ -0,0 +1,306 @@
|
|||||||
|
/**
|
||||||
|
* Company Profile Presets — Branchenvorlagen fuer typische Kundenszenarien
|
||||||
|
*
|
||||||
|
* Jeder Preset enthaelt ein vorbefuelltes CompanyProfile + typische Scope-Antworten.
|
||||||
|
* Der Kunde waehlt beim Onboarding ein Profil und passt es dann an.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface CompanyProfilePreset {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
description: string
|
||||||
|
icon: string
|
||||||
|
/** Vorbefuellte CompanyProfile-Felder */
|
||||||
|
profile: {
|
||||||
|
legalForm: string
|
||||||
|
industry: string[]
|
||||||
|
businessModel: string
|
||||||
|
companySize: string
|
||||||
|
employeeCount: string
|
||||||
|
headquartersCountry: string
|
||||||
|
targetMarkets: string[]
|
||||||
|
isDataController: boolean
|
||||||
|
isDataProcessor: boolean
|
||||||
|
}
|
||||||
|
/** Typische Scope-Antworten fuer diese Branche */
|
||||||
|
scopeHints: Record<string, string>
|
||||||
|
/** Typische Dokumente die diese Branche braucht */
|
||||||
|
recommendedDocs: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const COMPANY_PROFILE_PRESETS: CompanyProfilePreset[] = [
|
||||||
|
{
|
||||||
|
id: 'saas_startup',
|
||||||
|
label: 'SaaS Startup',
|
||||||
|
description: 'B2B Software-Startup, 1-5 Mitarbeiter, Cloud-basiert, remote-first',
|
||||||
|
icon: '🚀',
|
||||||
|
profile: {
|
||||||
|
legalForm: 'GmbH',
|
||||||
|
industry: ['tech'],
|
||||||
|
businessModel: 'b2b',
|
||||||
|
companySize: 'micro',
|
||||||
|
employeeCount: '1-9',
|
||||||
|
headquartersCountry: 'DE',
|
||||||
|
targetMarkets: ['DE', 'EU'],
|
||||||
|
isDataController: true,
|
||||||
|
isDataProcessor: true,
|
||||||
|
},
|
||||||
|
scopeHints: {
|
||||||
|
org_employee_count: '1-9',
|
||||||
|
org_industry: 'tech',
|
||||||
|
org_business_model: 'b2b',
|
||||||
|
proc_ai_usage: 'yes',
|
||||||
|
tech_hosting_location: 'eu',
|
||||||
|
tech_encryption_transit: 'yes',
|
||||||
|
tech_encryption_rest: 'yes',
|
||||||
|
comp_documentation_level: 'basic',
|
||||||
|
},
|
||||||
|
recommendedDocs: ['privacy_policy', 'impressum', 'agb', 'cookie_policy', 'dpa'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'consumer_app',
|
||||||
|
label: 'App Startup (Consumer)',
|
||||||
|
description: 'B2C Mobile App, 1-5 Mitarbeiter, App Store, Nutzerdaten',
|
||||||
|
icon: '📱',
|
||||||
|
profile: {
|
||||||
|
legalForm: 'GmbH',
|
||||||
|
industry: ['tech'],
|
||||||
|
businessModel: 'b2c',
|
||||||
|
companySize: 'micro',
|
||||||
|
employeeCount: '1-9',
|
||||||
|
headquartersCountry: 'DE',
|
||||||
|
targetMarkets: ['DE', 'EU'],
|
||||||
|
isDataController: true,
|
||||||
|
isDataProcessor: false,
|
||||||
|
},
|
||||||
|
scopeHints: {
|
||||||
|
org_employee_count: '1-9',
|
||||||
|
org_industry: 'tech',
|
||||||
|
org_business_model: 'b2c',
|
||||||
|
data_volume: '1000-10000',
|
||||||
|
proc_tracking: 'yes',
|
||||||
|
prod_consent_management: 'yes',
|
||||||
|
tech_hosting_location: 'eu',
|
||||||
|
},
|
||||||
|
recommendedDocs: ['privacy_policy', 'impressum', 'terms_of_use', 'cookie_policy', 'community_guidelines'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ecommerce',
|
||||||
|
label: 'E-Commerce / Online-Shop',
|
||||||
|
description: 'Online-Handel B2C, 5-20 Mitarbeiter, Webshop, Zahlungsabwicklung',
|
||||||
|
icon: '🛒',
|
||||||
|
profile: {
|
||||||
|
legalForm: 'GmbH',
|
||||||
|
industry: ['retail'],
|
||||||
|
businessModel: 'b2c',
|
||||||
|
companySize: 'small',
|
||||||
|
employeeCount: '10-49',
|
||||||
|
headquartersCountry: 'DE',
|
||||||
|
targetMarkets: ['DE', 'EU'],
|
||||||
|
isDataController: true,
|
||||||
|
isDataProcessor: false,
|
||||||
|
},
|
||||||
|
scopeHints: {
|
||||||
|
org_employee_count: '10-49',
|
||||||
|
org_industry: 'retail',
|
||||||
|
org_business_model: 'b2c',
|
||||||
|
prod_webshop: 'yes',
|
||||||
|
data_volume: '10000-100000',
|
||||||
|
tech_hosting_location: 'eu',
|
||||||
|
prod_consent_management: 'yes',
|
||||||
|
},
|
||||||
|
recommendedDocs: ['privacy_policy', 'impressum', 'agb', 'widerruf', 'cookie_policy', 'cookie_banner'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'it_agency',
|
||||||
|
label: 'IT-Dienstleister / Agentur',
|
||||||
|
description: 'IT-Beratung oder Agentur, 10-50 Mitarbeiter, Kundenprojekte',
|
||||||
|
icon: '💻',
|
||||||
|
profile: {
|
||||||
|
legalForm: 'GmbH',
|
||||||
|
industry: ['tech'],
|
||||||
|
businessModel: 'b2b',
|
||||||
|
companySize: 'small',
|
||||||
|
employeeCount: '10-49',
|
||||||
|
headquartersCountry: 'DE',
|
||||||
|
targetMarkets: ['DE', 'EU'],
|
||||||
|
isDataController: true,
|
||||||
|
isDataProcessor: true,
|
||||||
|
},
|
||||||
|
scopeHints: {
|
||||||
|
org_employee_count: '10-49',
|
||||||
|
org_industry: 'tech',
|
||||||
|
org_business_model: 'b2b',
|
||||||
|
proc_ai_usage: 'yes',
|
||||||
|
tech_hosting_location: 'eu',
|
||||||
|
comp_vendor_management: 'yes',
|
||||||
|
comp_training: 'yes',
|
||||||
|
},
|
||||||
|
recommendedDocs: ['privacy_policy', 'impressum', 'agb', 'dpa', 'nda', 'employee_dsi'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'maschinenbau',
|
||||||
|
label: 'Maschinenbau KMU',
|
||||||
|
description: 'Maschinenbau B2B, 50-200 Mitarbeiter, Produktion, CE-Kennzeichnung',
|
||||||
|
icon: '🏭',
|
||||||
|
profile: {
|
||||||
|
legalForm: 'GmbH',
|
||||||
|
industry: ['manufacturing'],
|
||||||
|
businessModel: 'b2b',
|
||||||
|
companySize: 'medium',
|
||||||
|
employeeCount: '50-249',
|
||||||
|
headquartersCountry: 'DE',
|
||||||
|
targetMarkets: ['DE', 'EU'],
|
||||||
|
isDataController: true,
|
||||||
|
isDataProcessor: false,
|
||||||
|
},
|
||||||
|
scopeHints: {
|
||||||
|
org_employee_count: '50-249',
|
||||||
|
org_industry: 'manufacturing',
|
||||||
|
org_business_model: 'b2b',
|
||||||
|
proc_employee_monitoring: 'no',
|
||||||
|
tech_hosting_location: 'eu',
|
||||||
|
comp_vendor_management: 'yes',
|
||||||
|
comp_documentation_level: 'structured',
|
||||||
|
},
|
||||||
|
recommendedDocs: ['privacy_policy', 'impressum', 'agb', 'dpa', 'employee_dsi', 'applicant_dsi', 'whistleblower_policy', 'tom_documentation'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'law_firm',
|
||||||
|
label: 'Rechtsanwaltskanzlei',
|
||||||
|
description: 'Kanzlei, 5-20 Mitarbeiter, Mandantendaten, besondere Vertraulichkeit',
|
||||||
|
icon: '⚖️',
|
||||||
|
profile: {
|
||||||
|
legalForm: 'PartG',
|
||||||
|
industry: ['legal'],
|
||||||
|
businessModel: 'b2b',
|
||||||
|
companySize: 'small',
|
||||||
|
employeeCount: '1-9',
|
||||||
|
headquartersCountry: 'DE',
|
||||||
|
targetMarkets: ['DE'],
|
||||||
|
isDataController: true,
|
||||||
|
isDataProcessor: false,
|
||||||
|
},
|
||||||
|
scopeHints: {
|
||||||
|
org_employee_count: '1-9',
|
||||||
|
org_industry: 'legal',
|
||||||
|
org_business_model: 'b2b',
|
||||||
|
data_art9: 'no',
|
||||||
|
tech_encryption_transit: 'yes',
|
||||||
|
tech_encryption_rest: 'yes',
|
||||||
|
comp_documentation_level: 'basic',
|
||||||
|
},
|
||||||
|
recommendedDocs: ['privacy_policy', 'impressum', 'dpa', 'employee_dsi', 'applicant_dsi'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'healthcare',
|
||||||
|
label: 'Arztpraxis / Gesundheit',
|
||||||
|
description: 'Gesundheitswesen, 5-50 Mitarbeiter, Patientendaten (Art. 9), hoher Schutzbedarf',
|
||||||
|
icon: '🏥',
|
||||||
|
profile: {
|
||||||
|
legalForm: 'GbR',
|
||||||
|
industry: ['healthcare'],
|
||||||
|
businessModel: 'b2c',
|
||||||
|
companySize: 'small',
|
||||||
|
employeeCount: '1-9',
|
||||||
|
headquartersCountry: 'DE',
|
||||||
|
targetMarkets: ['DE'],
|
||||||
|
isDataController: true,
|
||||||
|
isDataProcessor: false,
|
||||||
|
},
|
||||||
|
scopeHints: {
|
||||||
|
org_employee_count: '1-9',
|
||||||
|
org_industry: 'healthcare',
|
||||||
|
org_business_model: 'b2c',
|
||||||
|
data_art9: 'yes',
|
||||||
|
tech_encryption_transit: 'yes',
|
||||||
|
tech_encryption_rest: 'yes',
|
||||||
|
comp_documentation_level: 'basic',
|
||||||
|
},
|
||||||
|
recommendedDocs: ['privacy_policy', 'impressum', 'dpa', 'employee_dsi', 'tom_documentation', 'vvt_register', 'dsfa'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'handwerk',
|
||||||
|
label: 'Handwerksbetrieb',
|
||||||
|
description: 'Handwerk, 5-20 Mitarbeiter, Kundendaten, einfache IT',
|
||||||
|
icon: '🔧',
|
||||||
|
profile: {
|
||||||
|
legalForm: 'GmbH',
|
||||||
|
industry: ['crafts'],
|
||||||
|
businessModel: 'b2c',
|
||||||
|
companySize: 'small',
|
||||||
|
employeeCount: '1-9',
|
||||||
|
headquartersCountry: 'DE',
|
||||||
|
targetMarkets: ['DE'],
|
||||||
|
isDataController: true,
|
||||||
|
isDataProcessor: false,
|
||||||
|
},
|
||||||
|
scopeHints: {
|
||||||
|
org_employee_count: '1-9',
|
||||||
|
org_industry: 'other',
|
||||||
|
org_business_model: 'b2c',
|
||||||
|
data_art9: 'no',
|
||||||
|
tech_hosting_location: 'eu',
|
||||||
|
comp_documentation_level: 'none',
|
||||||
|
},
|
||||||
|
recommendedDocs: ['privacy_policy', 'impressum', 'agb'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'education',
|
||||||
|
label: 'Bildungseinrichtung',
|
||||||
|
description: 'Schule, Hochschule oder Weiterbildung, 20-100 Mitarbeiter, Schuelerdaten',
|
||||||
|
icon: '🎓',
|
||||||
|
profile: {
|
||||||
|
legalForm: 'gGmbH',
|
||||||
|
industry: ['education'],
|
||||||
|
businessModel: 'b2c',
|
||||||
|
companySize: 'medium',
|
||||||
|
employeeCount: '10-49',
|
||||||
|
headquartersCountry: 'DE',
|
||||||
|
targetMarkets: ['DE'],
|
||||||
|
isDataController: true,
|
||||||
|
isDataProcessor: false,
|
||||||
|
},
|
||||||
|
scopeHints: {
|
||||||
|
org_employee_count: '10-49',
|
||||||
|
org_industry: 'education',
|
||||||
|
org_business_model: 'b2c',
|
||||||
|
data_minors: 'yes',
|
||||||
|
tech_hosting_location: 'eu',
|
||||||
|
comp_training: 'yes',
|
||||||
|
},
|
||||||
|
recommendedDocs: ['privacy_policy', 'impressum', 'dpa', 'employee_dsi', 'dsfa', 'tom_documentation'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'enterprise',
|
||||||
|
label: 'Konzern / Enterprise',
|
||||||
|
description: 'Grossunternehmen, 500+ Mitarbeiter, international, reguliert, ISO 27001',
|
||||||
|
icon: '🏢',
|
||||||
|
profile: {
|
||||||
|
legalForm: 'AG',
|
||||||
|
industry: ['finance'],
|
||||||
|
businessModel: 'b2b',
|
||||||
|
companySize: 'enterprise',
|
||||||
|
employeeCount: '1000+',
|
||||||
|
headquartersCountry: 'DE',
|
||||||
|
targetMarkets: ['DE', 'EU', 'US'],
|
||||||
|
isDataController: true,
|
||||||
|
isDataProcessor: true,
|
||||||
|
},
|
||||||
|
scopeHints: {
|
||||||
|
org_employee_count: '1000+',
|
||||||
|
org_industry: 'finance',
|
||||||
|
org_business_model: 'b2b',
|
||||||
|
org_cert_target: 'iso27001',
|
||||||
|
data_art9: 'yes',
|
||||||
|
data_volume: '>1000000',
|
||||||
|
proc_ai_usage: 'yes',
|
||||||
|
tech_third_country: 'yes',
|
||||||
|
tech_hosting_location: 'eu_us_adequacy',
|
||||||
|
comp_vendor_management: 'yes',
|
||||||
|
comp_training: 'yes',
|
||||||
|
comp_documentation_level: 'comprehensive',
|
||||||
|
},
|
||||||
|
recommendedDocs: ['privacy_policy', 'impressum', 'agb', 'dpa', 'nda', 'sla', 'employee_dsi', 'applicant_dsi', 'whistleblower_policy', 'tom_documentation', 'vvt_register', 'isms_manual', 'dsfa', 'transfer_impact_assessment'],
|
||||||
|
},
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user