'use client' /** * P79 — Pre-Scan-Wizard (8 Pflichtfelder). * * 8 Pflichtfelder die vor dem Lauf abgefragt werden. Werte landen im * scan_context und filtern später die MC-Auswertung (zusammen mit P72 * scope_doc_type + applicable_industries). Erwartete Noise-Reduktion: * 70-80% bei falsch zugeordneten HIGH-MCs. */ import React, { useState, useEffect } from 'react' export interface ScanContext { industry: string business_model: string direct_sales: string legal_form: string group_structure: string employee_count: string special_data: string[] third_country_transfer: string } const INDUSTRIES = [ { id: '', label: '— bitte wählen —' }, { id: 'automotive', label: 'Automotive / OEM' }, { id: 'ecommerce', label: 'E-Commerce / Online-Handel' }, { id: 'saas', label: 'SaaS / Software' }, { id: 'banking', label: 'Banking / Finance' }, { id: 'insurance', label: 'Insurance / Versicherung' }, { id: 'healthcare', label: 'Healthcare / Gesundheit' }, { id: 'education', label: 'Bildung / Schule' }, { id: 'public', label: 'Öffentliche Verwaltung' }, { id: 'manufacturing', label: 'Industrie / Manufacturing' }, { id: 'media', label: 'Medien / Verlag' }, { id: 'other', label: 'Sonstige' }, ] const LEGAL_FORMS = [ { id: '', label: '— bitte wählen —' }, { id: 'ag', label: 'AG (Aktiengesellschaft)' }, { id: 'gmbh', label: 'GmbH' }, { id: 'gmbh_co_kg', label: 'GmbH & Co. KG' }, { id: 'kg', label: 'KG' }, { id: 'ohg', label: 'OHG' }, { id: 'ug', label: 'UG (haftungsbeschränkt)' }, { id: 'ek', label: 'e.K. / Einzelunternehmen' }, { id: 'verein', label: 'Verein' }, { id: 'stiftung', label: 'Stiftung' }, { id: 'behoerde', label: 'Behörde / Körperschaft öff. Rechts' }, { id: 'other', label: 'Sonstige' }, ] const GROUP_STRUCTURES = [ { id: '', label: '— bitte wählen —' }, { id: 'standalone', label: 'Eigenständig' }, { id: 'parent', label: 'Konzern-Mutter' }, { id: 'subsidiary', label: 'Konzern-Tochter' }, { id: 'joint_venture', label: 'Joint Venture' }, { id: 'processor', label: 'Reiner Auftragsverarbeiter' }, ] const EMPLOYEE_COUNTS = [ { id: '', label: '— bitte wählen —' }, { id: 'lt10', label: 'unter 10' }, { id: '10_19', label: '10-19' }, { id: '20_49', label: '20-49 (DSB ab 20 Pflicht)' }, { id: '50_249', label: '50-249 (Whistleblower-Pflicht)' }, { id: '250_499', label: '250-499' }, { id: '500_999', label: '500-999' }, { id: '1000_plus', label: '1.000+ (Konzern)' }, ] const SPECIAL_DATA_OPTIONS = [ { id: 'health', label: 'Gesundheitsdaten' }, { id: 'biometric', label: 'Biometrische Daten' }, { id: 'ethnicity', label: 'Religiöse / ethnische Herkunft' }, { id: 'sexual', label: 'Sexuelle Orientierung' }, { id: 'criminal', label: 'Strafrechtliche Daten' }, { id: 'minors', label: 'Minderjährige (<16)' }, { id: 'none', label: 'Keine besonderen Daten' }, ] const STORAGE_KEY = 'compliance-scan-context' function emptyContext(): ScanContext { return { industry: '', business_model: '', direct_sales: '', legal_form: '', group_structure: '', employee_count: '', special_data: [], third_country_transfer: '', } } export function isContextComplete(ctx: ScanContext): boolean { return Boolean( ctx.industry && ctx.business_model && ctx.direct_sales && ctx.legal_form && ctx.group_structure && ctx.employee_count && ctx.special_data.length > 0 && ctx.third_country_transfer ) } // Track B — consolidation: prefill from the shared CompanyProfile instead of // re-asking. Vocabularies differ across modules, so map tolerantly (only fields // that map cleanly; the rest the user fills). User-triggered, never silent. const DEV_TENANT = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' const _VALID_LEGAL = new Set(['ag', 'gmbh', 'gmbh_co_kg', 'kg', 'ohg', 'ug', 'ek', 'verein', 'stiftung', 'behoerde']) const _INDUSTRY_ALIAS: Record = { maschinenbau: 'manufacturing', industrie: 'manufacturing', manufacturing: 'manufacturing', automotive: 'automotive', oem: 'automotive', saas: 'saas', software: 'saas', ecommerce: 'ecommerce', handel: 'ecommerce', banking: 'banking', finance: 'banking', insurance: 'insurance', versicherung: 'insurance', healthcare: 'healthcare', gesundheit: 'healthcare', bildung: 'education', education: 'education', medien: 'media', media: 'media', verwaltung: 'public', public: 'public', } const _SIZE_TO_EMP: Record = { micro: 'lt10', small: '20_49', medium: '50_249', large: '250_499', enterprise: '1000_plus', } function mapProfileToScanContext(p: any): Partial { const out: Partial = {} const lf = String(p.legal_form || '').toLowerCase().replace(/[^a-z_]/g, '') if (_VALID_LEGAL.has(lf)) out.legal_form = lf const ind = String(Array.isArray(p.industry) ? p.industry[0] : (p.industry || '')).toLowerCase().trim() if (_INDUSTRY_ALIAS[ind]) out.industry = _INDUSTRY_ALIAS[ind] const size = String(p.company_size || '').toLowerCase() if (_SIZE_TO_EMP[size]) out.employee_count = _SIZE_TO_EMP[size] return out } export function PreScanWizard({ value, onChange, }: { value: ScanContext onChange: (ctx: ScanContext) => void }) { const [profile, setProfile] = useState(null) useEffect(() => { fetch(`/api/sdk/v1/company-profile?tenant_id=${DEV_TENANT}`) .then((r) => (r.ok ? r.json() : null)) .then((p) => { if (p && p.company_name) setProfile(p) }) .catch(() => {}) }, []) const update = (key: K, val: ScanContext[K]) => { onChange({ ...value, [key]: val }) } const toggleSpecialData = (id: string) => { const next = value.special_data.includes(id) ? value.special_data.filter(x => x !== id) : [...value.special_data.filter(x => x !== 'none' || id === 'none'), id] onChange({ ...value, special_data: id === 'none' ? ['none'] : next.filter(x => x !== 'none') }) } return (
Pflichtangaben zur Klassifizierung des Audits

Vor dem Scan: 8 Angaben zum Unternehmen

Diese Angaben filtern irrelevante Compliance-Themen heraus (z.B. eHealth- Vorschriften bei einem Autobauer) und liefern eine realistische Einschätzung statt pauschaler Verstoss-Listen.

{profile && (
Unternehmensprofil erkannt: {profile.company_name} {profile.industry ? ` · ${Array.isArray(profile.industry) ? profile.industry.join(', ') : profile.industry}` : ''} {profile.legal_form ? ` · ${profile.legal_form}` : ''} {profile.company_size ? ` · ${profile.company_size}` : ''} {' '}— diese Angaben müssen Sie nicht erneut eingeben.
)}
{SPECIAL_DATA_OPTIONS.map(o => ( ))}
{!isContextComplete(value) && (
Bitte alle 8 Pflichtfelder ausfüllen — der Scan-Button wird erst aktiv, wenn die Klassifizierung komplett ist.
)}
) } const inputStyle: React.CSSProperties = { width: '100%', padding: '6px 8px', fontSize: 12, border: '1px solid #cbd5e1', borderRadius: 4, background: '#fff', } function Field({ label, children, colSpan }: { label: string; children: React.ReactNode; colSpan?: number }) { return (
{children}
) } export function useScanContext(): [ScanContext, (ctx: ScanContext) => void] { const [ctx, setCtx] = useState(() => { if (typeof window === 'undefined') return emptyContext() try { const s = localStorage.getItem(STORAGE_KEY) return s ? { ...emptyContext(), ...JSON.parse(s) } : emptyContext() } catch { return emptyContext() } }) useEffect(() => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(ctx)) } catch {} }, [ctx]) return [ctx, setCtx] }