'use client' import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react' import { useSDK } from '@/lib/sdk' import { useEinwilligungen, EinwilligungenProvider } from '@/lib/sdk/einwilligungen/context' import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader' import { LegalTemplateResult, TemplateType, LicenseType, TEMPLATE_TYPE_LABELS, LICENSE_TYPE_LABELS, } from '@/lib/sdk/types' import { DataPointsPreview } from './components/DataPointsPreview' import { DocumentValidation } from './components/DocumentValidation' import { generateAllPlaceholders } from '@/lib/sdk/document-generator/datapoint-helpers' import { loadAllTemplates } from './searchTemplates' import { TemplateContext, EMPTY_CONTEXT, contextToPlaceholders, getRelevantSections, getUncoveredPlaceholders, getMissingRequired, } from './contextBridge' import { runRuleset, getDocType, applyBlockRemoval, buildBoolContext, applyConditionalBlocks, type RuleInput, type RuleEngineResult, } from './ruleEngine' // ============================================================================= // CATEGORY CONFIG // ============================================================================= const CATEGORIES: { key: string; label: string; types: string[] | null }[] = [ { key: 'all', label: 'Alle', types: null }, { key: 'privacy_policy', label: 'Datenschutz', types: ['privacy_policy'] }, { key: 'terms', label: 'AGB', types: ['terms_of_service', 'agb', 'clause'] }, { key: 'impressum', label: 'Impressum', types: ['impressum'] }, { key: 'dpa', label: 'AVV/DPA', types: ['dpa'] }, { key: 'nda', label: 'NDA', types: ['nda'] }, { key: 'sla', label: 'SLA', types: ['sla'] }, { key: 'acceptable_use', label: 'AUP', types: ['acceptable_use'] }, { key: 'widerruf', label: 'Widerruf', types: ['widerruf'] }, { key: 'cookie', label: 'Cookie', types: ['cookie_policy', 'cookie_banner'] }, { key: 'cloud', label: 'Cloud', types: ['cloud_service_agreement'] }, { key: 'misc', label: 'Weitere', types: ['community_guidelines', 'copyright_policy', 'data_usage_clause'] }, { key: 'dsfa', label: 'DSFA', types: ['dsfa'] }, ] // ============================================================================= // CONTEXT FORM CONFIG // ============================================================================= const SECTION_LABELS: Record = { PROVIDER: 'Anbieter', CUSTOMER: 'Kunde / Gegenpartei', SERVICE: 'Dienst / Produkt', LEGAL: 'Rechtliches', PRIVACY: 'Datenschutz', SLA: 'Service Level (SLA)', PAYMENTS: 'Zahlungskonditionen', SECURITY: 'Sicherheit & Logs', NDA: 'Geheimhaltung (NDA)', CONSENT: 'Cookie / Einwilligung', HOSTING: 'Hosting-Provider', FEATURES: 'Dokument-Features & Textbausteine', } type FieldType = 'text' | 'email' | 'number' | 'select' | 'textarea' | 'boolean' interface FieldDef { key: string label: string type?: FieldType opts?: string[] span?: boolean nullable?: boolean } const SECTION_FIELDS: Record = { PROVIDER: [ { key: 'LEGAL_NAME', label: 'Firmenname' }, { key: 'EMAIL', label: 'Kontakt-E-Mail', type: 'email' }, { key: 'LEGAL_FORM', label: 'Rechtsform' }, { key: 'ADDRESS_LINE', label: 'Adresse' }, { key: 'POSTAL_CODE', label: 'PLZ' }, { key: 'CITY', label: 'Stadt' }, { key: 'WEBSITE_URL', label: 'Website-URL' }, { key: 'CEO_NAME', label: 'Geschäftsführer' }, { key: 'REGISTER_COURT', label: 'Registergericht' }, { key: 'REGISTER_NUMBER', label: 'HRB-Nummer' }, { key: 'VAT_ID', label: 'USt-ID' }, { key: 'PHONE', label: 'Telefon' }, ], CUSTOMER: [ { key: 'LEGAL_NAME', label: 'Name / Firma' }, { key: 'EMAIL', label: 'E-Mail', type: 'email' }, { key: 'CONTACT_NAME', label: 'Ansprechpartner' }, { key: 'ADDRESS_LINE', label: 'Adresse' }, { key: 'POSTAL_CODE', label: 'PLZ' }, { key: 'CITY', label: 'Stadt' }, { key: 'COUNTRY', label: 'Land' }, { key: 'IS_CONSUMER', label: 'Verbraucher (B2C)', type: 'boolean' }, { key: 'IS_BUSINESS', label: 'Unternehmer (B2B)', type: 'boolean' }, ], SERVICE: [ { key: 'NAME', label: 'Dienstname' }, { key: 'DESCRIPTION', label: 'Beschreibung', type: 'textarea', span: true }, { key: 'MODEL', label: 'Modell', type: 'select', opts: ['SaaS', 'PaaS', 'IaaS', 'OnPrem', 'Hybrid'] }, { key: 'TIER', label: 'Plan / Tier' }, { key: 'DATA_LOCATION', label: 'Datenspeicherort' }, { key: 'EXPORT_WINDOW_DAYS', label: 'Export-Frist (Tage)', type: 'number' }, { key: 'MIN_TERM_MONTHS', label: 'Mindestlaufzeit (Monate)', type: 'number' }, { key: 'TERMINATION_NOTICE_DAYS', label: 'Kündigungsfrist (Tage)', type: 'number' }, ], LEGAL: [ { key: 'GOVERNING_LAW', label: 'Anwendbares Recht' }, { key: 'JURISDICTION_CITY', label: 'Gerichtsstand (Stadt)' }, { key: 'VERSION_DATE', label: 'Versionsstand (JJJJ-MM-TT)' }, { key: 'EFFECTIVE_DATE', label: 'Gültig ab (JJJJ-MM-TT)' }, ], PRIVACY: [ { key: 'DPO_NAME', label: 'DSB-Name' }, { key: 'DPO_EMAIL', label: 'DSB-E-Mail', type: 'email' }, { key: 'CONTACT_EMAIL', label: 'Datenschutz-Kontakt', type: 'email' }, { key: 'PRIVACY_POLICY_URL', label: 'Datenschutz-URL' }, { key: 'COOKIE_POLICY_URL', label: 'Cookie-Policy-URL' }, { key: 'ANALYTICS_RETENTION_MONTHS', label: 'Analytics-Aufbewahrung (Monate)', type: 'number' }, { key: 'SUPERVISORY_AUTHORITY_NAME', label: 'Aufsichtsbehörde' }, ], SLA: [ { key: 'AVAILABILITY_PERCENT', label: 'Verfügbarkeit (%)', type: 'number' }, { key: 'MAINTENANCE_NOTICE_HOURS', label: 'Wartungsankündigung (h)', type: 'number' }, { key: 'SUPPORT_EMAIL', label: 'Support-E-Mail', type: 'email' }, { key: 'SUPPORT_HOURS', label: 'Support-Zeiten' }, { key: 'RESPONSE_CRITICAL_H', label: 'Reaktion Kritisch (h)', type: 'number' }, { key: 'RESOLUTION_CRITICAL_H', label: 'Lösung Kritisch (h)', type: 'number' }, { key: 'RESPONSE_HIGH_H', label: 'Reaktion Hoch (h)', type: 'number' }, { key: 'RESOLUTION_HIGH_H', label: 'Lösung Hoch (h)', type: 'number' }, { key: 'RESPONSE_MEDIUM_H', label: 'Reaktion Mittel (h)', type: 'number' }, { key: 'RESOLUTION_MEDIUM_H', label: 'Lösung Mittel (h)', type: 'number' }, { key: 'RESPONSE_LOW_H', label: 'Reaktion Niedrig (h)', type: 'number' }, ], PAYMENTS: [ { key: 'MONTHLY_FEE_EUR', label: 'Monatl. Gebühr (EUR)', type: 'number' }, { key: 'PAYMENT_DUE_DAY', label: 'Fälligkeitstag', type: 'number' }, { key: 'PAYMENT_METHOD', label: 'Zahlungsmethode' }, { key: 'PAYMENT_DAYS', label: 'Zahlungsziel (Tage)', type: 'number' }, ], SECURITY: [ { key: 'INCIDENT_NOTICE_HOURS', label: 'Meldepflicht Vorfälle (h)', type: 'number' }, { key: 'LOG_RETENTION_DAYS', label: 'Log-Aufbewahrung (Tage)', type: 'number' }, { key: 'SECURITY_LOG_RETENTION_DAYS', label: 'Sicherheits-Log (Tage)', type: 'number' }, ], NDA: [ { key: 'PURPOSE', label: 'Zweck', type: 'textarea', span: true }, { key: 'DURATION_YEARS', label: 'Laufzeit (Jahre)', type: 'number' }, { key: 'PENALTY_AMOUNT_EUR', label: 'Vertragsstrafe EUR (leer = keine)', type: 'number', nullable: true }, ], CONSENT: [ { key: 'WEBSITE_NAME', label: 'Website-Name' }, { key: 'ANALYTICS_TOOLS', label: 'Analytics-Tools (leer = kein Block)', nullable: true }, { key: 'MARKETING_PARTNERS', label: 'Marketing-Partner (leer = kein Block)', nullable: true }, ], HOSTING: [ { key: 'PROVIDER_NAME', label: 'Hosting-Anbieter' }, { key: 'COUNTRY', label: 'Hosting-Land' }, { key: 'CONTRACT_TYPE', label: 'Vertragstyp (z. B. AVV nach Art. 28 DSGVO)' }, ], FEATURES: [ // ── DSI / Cookie ───────────────────────────────────────────────────────── { key: 'CONSENT_WITHDRAWAL_PATH', label: 'Einwilligungs-Widerrufspfad' }, { key: 'SECURITY_MEASURES_SUMMARY', label: 'Sicherheitsmaßnahmen (kurz)' }, { key: 'DATA_SUBJECT_REQUEST_CHANNEL', label: 'Kanal für Betroffenenanfragen' }, { key: 'HAS_THIRD_COUNTRY', label: 'Drittlandübermittlung möglich', type: 'boolean' }, { key: 'TRANSFER_GUARDS', label: 'Garantien (z. B. SCC)' }, // ── Cookie/Consent ─────────────────────────────────────────────────────── { key: 'HAS_FUNCTIONAL_COOKIES', label: 'Funktionale Cookies aktiviert', type: 'boolean' }, { key: 'CMP_NAME', label: 'Consent-Manager-Name (optional)' }, { key: 'CMP_LOGS_CONSENTS', label: 'Consent-Protokollierung aktiv', type: 'boolean' }, { key: 'ANALYTICS_TOOLS_DETAIL', label: 'Analyse-Tools (Detailtext)', type: 'textarea', span: true }, { key: 'MARKETING_TOOLS_DETAIL', label: 'Marketing-Tools (Detailtext)', type: 'textarea', span: true }, // ── Service-Features ───────────────────────────────────────────────────── { key: 'HAS_ACCOUNT', label: 'Nutzerkonten vorhanden', type: 'boolean' }, { key: 'HAS_PAYMENTS', label: 'Zahlungsabwicklung vorhanden', type: 'boolean' }, { key: 'PAYMENT_PROVIDER_DETAIL', label: 'Zahlungsanbieter (Detailtext)', type: 'textarea', span: true }, { key: 'HAS_SUPPORT', label: 'Support-Funktion vorhanden', type: 'boolean' }, { key: 'SUPPORT_CHANNELS_TEXT', label: 'Support-Kanäle / Zeiten' }, { key: 'HAS_NEWSLETTER', label: 'Newsletter vorhanden', type: 'boolean' }, { key: 'NEWSLETTER_PROVIDER_DETAIL', label: 'Newsletter-Anbieter (Detailtext)', type: 'textarea', span: true }, { key: 'HAS_SOCIAL_MEDIA', label: 'Social-Media-Präsenz', type: 'boolean' }, { key: 'SOCIAL_MEDIA_DETAIL', label: 'Social-Media-Details', type: 'textarea', span: true }, // ── AGB ────────────────────────────────────────────────────────────────── { key: 'HAS_PAID_PLANS', label: 'Kostenpflichtige Pläne', type: 'boolean' }, { key: 'PRICES_TEXT', label: 'Preise (Text/Link)', type: 'textarea', span: true }, { key: 'PAYMENT_TERMS_TEXT', label: 'Zahlungsbedingungen', type: 'textarea', span: true }, { key: 'CONTRACT_TERM_TEXT', label: 'Laufzeit & Kündigung', type: 'textarea', span: true }, { key: 'HAS_SLA', label: 'SLA vorhanden', type: 'boolean' }, { key: 'SLA_URL', label: 'SLA-URL' }, { key: 'HAS_EXPORT_POLICY', label: 'Datenexport/Löschung geregelt', type: 'boolean' }, { key: 'EXPORT_POLICY_TEXT', label: 'Datenexport-Regelung (Text)', type: 'textarea', span: true }, { key: 'HAS_WITHDRAWAL', label: 'Widerrufsrecht (B2C digital)', type: 'boolean' }, { key: 'CONSUMER_WITHDRAWAL_TEXT', label: 'Widerrufsbelehrung (Text)', type: 'textarea', span: true }, { key: 'LIMITATION_CAP_TEXT', label: 'Haftungsdeckel B2B (Text)' }, // ── Impressum ──────────────────────────────────────────────────────────── { key: 'HAS_REGULATED_PROFESSION', label: 'Reglementierter Beruf', type: 'boolean' }, { key: 'REGULATED_PROFESSION_TEXT', label: 'Berufsrecht-Text', type: 'textarea', span: true }, { key: 'HAS_EDITORIAL_RESPONSIBLE', label: 'V.i.S.d.P. (redaktionell)', type: 'boolean' }, { key: 'EDITORIAL_RESPONSIBLE_NAME', label: 'V.i.S.d.P. Name' }, { key: 'EDITORIAL_RESPONSIBLE_ADDRESS', label: 'V.i.S.d.P. Adresse' }, { key: 'HAS_DISPUTE_RESOLUTION', label: 'Streitbeilegungshinweis', type: 'boolean' }, { key: 'DISPUTE_RESOLUTION_TEXT', label: 'Streitbeilegungstext', type: 'textarea', span: true }, ], } // ============================================================================= // SMALL COMPONENTS // ============================================================================= function LicenseBadge({ licenseId, small = false }: { licenseId: LicenseType | null; small?: boolean }) { if (!licenseId) return null const colors: Partial> = { public_domain: 'bg-green-100 text-green-700 border-green-200', cc0: 'bg-green-100 text-green-700 border-green-200', unlicense: 'bg-green-100 text-green-700 border-green-200', mit: 'bg-blue-100 text-blue-700 border-blue-200', cc_by_4: 'bg-purple-100 text-purple-700 border-purple-200', reuse_notice: 'bg-orange-100 text-orange-700 border-orange-200', } return ( {LICENSE_TYPE_LABELS[licenseId] || licenseId} ) } // ============================================================================= // LIBRARY CARD // ============================================================================= function LibraryCard({ template, expanded, onTogglePreview, onUse, }: { template: LegalTemplateResult expanded: boolean onTogglePreview: () => void onUse: () => void }) { const typeLabel = template.templateType ? (TEMPLATE_TYPE_LABELS[template.templateType as TemplateType] || template.templateType) : null const placeholderCount = template.placeholders?.length ?? 0 return (

{template.documentTitle || 'Vorlage'}

{template.language}
{typeLabel && ( {typeLabel} )} {placeholderCount > 0 && ( {placeholderCount} Platzh. )}
{expanded && (
            {template.text}
          
)}
) } // ============================================================================= // CONTEXT SECTION FORM // ============================================================================= function ContextSectionForm({ section, context, onChange, }: { section: keyof TemplateContext context: TemplateContext onChange: (section: keyof TemplateContext, key: string, value: unknown) => void }) { const fields = SECTION_FIELDS[section] const sectionData = context[section] as Record return (
{fields.map((field) => { const rawValue = sectionData[field.key] const inputCls = 'w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-purple-400' if (field.type === 'boolean') { return (
onChange(section, field.key, e.target.checked)} className="w-4 h-4 accent-purple-600" />
) } if (field.type === 'select' && field.opts) { return (
) } if (field.type === 'textarea') { return (