feat(sdk): Step 6 nach Abteilungen + aktivitätsspezifische Datenkategorien
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 34s
CI / test-python-backend-compliance (push) Successful in 38s
CI / test-python-document-crawler (push) Successful in 24s
CI / test-python-dsms-gateway (push) Successful in 21s

- Verarbeitungstätigkeiten nach 11+ Abteilungen gruppiert (Personal, Finanzen, Vertrieb, Marketing, IT, Recht, Einkauf, Produktion, Logistik, Kundenservice, Facility)
- Branchenspezifische Abteilungen (E-Commerce, Gesundheit, Finanz, Bildung, Immobilien)
- 3-Stufen Datenkategorien: primär (sichtbar), weitere (aufklappbar), Art. 9 (rot, nur wenn plausibel relevant)
- Bundesland/Kanton-Dropdown für DE (16), AT (9), CH (26) statt Freitext

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-08 23:53:52 +01:00
parent e96f623af0
commit de486aeab0

View File

@@ -382,9 +382,37 @@ function StepLocations({
}
}
const stateLabel = data.headquartersCountry === 'CH' ? 'Kanton' :
data.headquartersCountry === 'AT' ? 'Bundesland' :
data.headquartersCountry === 'DE' ? 'Bundesland' : 'Region / Provinz'
const STATES_BY_COUNTRY: Record<string, { label: string; options: string[] }> = {
DE: {
label: 'Bundesland',
options: [
'Baden-Württemberg', 'Bayern', 'Berlin', 'Brandenburg', 'Bremen',
'Hamburg', 'Hessen', 'Mecklenburg-Vorpommern', 'Niedersachsen',
'Nordrhein-Westfalen', 'Rheinland-Pfalz', 'Saarland', 'Sachsen',
'Sachsen-Anhalt', 'Schleswig-Holstein', 'Thüringen',
],
},
AT: {
label: 'Bundesland',
options: [
'Burgenland', 'Kärnten', 'Niederösterreich', 'Oberösterreich',
'Salzburg', 'Steiermark', 'Tirol', 'Vorarlberg', 'Wien',
],
},
CH: {
label: 'Kanton',
options: [
'Aargau', 'Appenzell Ausserrhoden', 'Appenzell Innerrhoden',
'Basel-Landschaft', 'Basel-Stadt', 'Bern', 'Freiburg', 'Genf',
'Glarus', 'Graubünden', 'Jura', 'Luzern', 'Neuenburg', 'Nidwalden',
'Obwalden', 'Schaffhausen', 'Schwyz', 'Solothurn', 'St. Gallen',
'Tessin', 'Thurgau', 'Uri', 'Waadt', 'Wallis', 'Zug', 'Zürich',
],
},
}
const countryStates = data.headquartersCountry ? STATES_BY_COUNTRY[data.headquartersCountry] : null
const stateLabel = countryStates?.label || 'Region / Provinz'
return (
<div className="space-y-8">
@@ -464,13 +492,26 @@ function StepLocations({
{/* State / Bundesland / Kanton */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">{stateLabel}</label>
<input
type="text"
value={data.headquartersState || ''}
onChange={e => onChange({ headquartersState: e.target.value })}
placeholder={data.headquartersCountry === 'CH' ? 'z.B. Zürich' : data.headquartersCountry === 'AT' ? 'z.B. Wien' : 'z.B. Bayern'}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
{countryStates ? (
<select
value={data.headquartersState || ''}
onChange={e => onChange({ headquartersState: e.target.value })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
<option value="">Bitte wählen...</option>
{countryStates.options.map(s => (
<option key={s} value={s}>{s}</option>
))}
</select>
) : (
<input
type="text"
value={data.headquartersState || ''}
onChange={e => onChange({ headquartersState: e.target.value })}
placeholder="Region / Provinz"
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
)}
</div>
<div>
@@ -596,7 +637,7 @@ function StepDataProtection({
// =============================================================================
// DSGVO-Standard Datenkategorien
const DATA_CATEGORIES = [
const ALL_DATA_CATEGORIES = [
{ id: 'stammdaten', label: 'Stammdaten', desc: 'Name, Geburtsdatum, Geschlecht' },
{ id: 'kontaktdaten', label: 'Kontaktdaten', desc: 'E-Mail, Telefon, Adresse' },
{ id: 'vertragsdaten', label: 'Vertragsdaten', desc: 'Vertragsnummer, Laufzeit, Konditionen' },
@@ -609,7 +650,7 @@ const DATA_CATEGORIES = [
{ id: 'bewerberdaten', label: 'Bewerberdaten', desc: 'Lebenslauf, Zeugnisse, Anschreiben' },
] as const
const SPECIAL_DATA_CATEGORIES = [
const ALL_SPECIAL_CATEGORIES = [
{ id: 'gesundheit', label: 'Gesundheitsdaten', desc: 'Krankheitstage, Atteste, Diagnosen' },
{ id: 'biometrie', label: 'Biometrische Daten', desc: 'Fingerabdruck, Gesichtserkennung' },
{ id: 'religion', label: 'Religiöse Überzeugungen', desc: 'Konfession, Feiertage' },
@@ -625,68 +666,188 @@ const LEGAL_BASES = [
{ id: 'interest', label: 'Berechtigtes Interesse (Art. 6 Abs. 1f)' },
] as const
// Verarbeitungstätigkeiten-Vorlagen nach Branche
interface ProcessingActivityTemplate {
// Verarbeitungstätigkeiten mit aktivitätsspezifischen Datenkategorien
interface ActivityTemplate {
id: string
name: string
purpose: string
default_categories: string[]
primary_categories: string[] // Sichtbar + vorausgewählt
art9_relevant: string[] // Art. 9 Kategorien die plausibel relevant sind
default_legal_basis: string
}
const COMMON_ACTIVITIES: ProcessingActivityTemplate[] = [
{ id: 'personal', name: 'Personalverwaltung', purpose: 'Verwaltung von Mitarbeiterdaten, Gehaltsabrechnung, Arbeitszeitverwaltung', default_categories: ['stammdaten', 'kontaktdaten', 'beschaeftigtendaten', 'zahlungsdaten'], default_legal_basis: 'contract' },
{ id: 'buchhaltung', name: 'Buchhaltung / Rechnungswesen', purpose: 'Rechnungsstellung, Zahlungsabwicklung, Steuerliche Pflichten', default_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten', 'zahlungsdaten'], default_legal_basis: 'legal' },
{ id: 'bewerbung', name: 'Bewerbermanagement', purpose: 'Verwaltung von Bewerbungen und Auswahlverfahren', default_categories: ['stammdaten', 'kontaktdaten', 'bewerberdaten'], default_legal_basis: 'consent' },
{ id: 'website', name: 'Website-Betrieb', purpose: 'Bereitstellung der Unternehmenswebsite, Analytics, Kontaktformulare', default_categories: ['nutzungsdaten', 'kontaktdaten'], default_legal_basis: 'interest' },
{ id: 'email', name: 'E-Mail-Kommunikation', purpose: 'Geschäftliche E-Mail-Korrespondenz', default_categories: ['stammdaten', 'kontaktdaten', 'kommunikation'], default_legal_basis: 'interest' },
]
const INDUSTRY_ACTIVITIES: Record<string, ProcessingActivityTemplate[]> = {
'Technologie / IT': [
{ id: 'crm', name: 'CRM / Kundenverwaltung', purpose: 'Pflege von Kundenbeziehungen, Vertriebspipeline', default_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten', 'kommunikation'], default_legal_basis: 'contract' },
{ id: 'support', name: 'Support / Ticketsystem', purpose: 'Kundenanfragen bearbeiten, Fehlerbehebung', default_categories: ['stammdaten', 'kontaktdaten', 'kommunikation', 'nutzungsdaten'], default_legal_basis: 'contract' },
{ id: 'saas', name: 'SaaS-Plattform / Nutzerkonten', purpose: 'Bereitstellung der Software, Nutzerverwaltung', default_categories: ['stammdaten', 'kontaktdaten', 'nutzungsdaten', 'vertragsdaten'], default_legal_basis: 'contract' },
],
'E-Commerce / Handel': [
{ id: 'bestellung', name: 'Bestellabwicklung', purpose: 'Bestellannahme, Zahlungsabwicklung, Versand', default_categories: ['stammdaten', 'kontaktdaten', 'zahlungsdaten', 'vertragsdaten', 'standortdaten'], default_legal_basis: 'contract' },
{ id: 'kundenkonto', name: 'Kundenkonto-Verwaltung', purpose: 'Registrierung, Login, Bestellhistorie', default_categories: ['stammdaten', 'kontaktdaten', 'nutzungsdaten'], default_legal_basis: 'contract' },
{ id: 'newsletter', name: 'Newsletter / Marketing', purpose: 'E-Mail-Marketing, Werbeaktionen', default_categories: ['stammdaten', 'kontaktdaten', 'nutzungsdaten'], default_legal_basis: 'consent' },
],
'Finanzdienstleistungen': [
{ id: 'konto', name: 'Kontoverwaltung', purpose: 'Kontoeröffnung, KYC, Kontopflege', default_categories: ['stammdaten', 'kontaktdaten', 'zahlungsdaten', 'vertragsdaten'], default_legal_basis: 'contract' },
{ id: 'compliance_fin', name: 'Regulatorische Pflichten', purpose: 'Geldwäscheprävention, Meldepflichten', default_categories: ['stammdaten', 'zahlungsdaten'], default_legal_basis: 'legal' },
],
'Gesundheitswesen': [
{ id: 'patient', name: 'Patientenverwaltung', purpose: 'Aufnahme, Behandlung, Dokumentation', default_categories: ['stammdaten', 'kontaktdaten', 'gesundheit'], default_legal_basis: 'contract' },
{ id: 'termin', name: 'Terminplanung', purpose: 'Terminvergabe und -verwaltung', default_categories: ['stammdaten', 'kontaktdaten'], default_legal_basis: 'contract' },
{ id: 'abrechnung_kv', name: 'Abrechnung / KV', purpose: 'Kassenärztliche Abrechnung, Privatliquidation', default_categories: ['stammdaten', 'zahlungsdaten', 'gesundheit'], default_legal_basis: 'legal' },
],
'Beratung / Consulting': [
{ id: 'crm', name: 'CRM / Kundenverwaltung', purpose: 'Pflege von Kundenbeziehungen, Projekthistorie', default_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten', 'kommunikation'], default_legal_basis: 'contract' },
{ id: 'projekt', name: 'Projektmanagement', purpose: 'Projektverwaltung, Zeiterfassung, Dokumentation', default_categories: ['stammdaten', 'beschaeftigtendaten', 'kommunikation'], default_legal_basis: 'contract' },
],
'Bildung': [
{ id: 'schueler', name: 'Schüler-/Teilnehmerverwaltung', purpose: 'Verwaltung von Lernenden, Noten, Anwesenheit', default_categories: ['stammdaten', 'kontaktdaten', 'nutzungsdaten'], default_legal_basis: 'contract' },
{ id: 'lernplattform', name: 'Lernplattform / LMS', purpose: 'Bereitstellung von Lernmaterialien, Prüfungen', default_categories: ['stammdaten', 'nutzungsdaten', 'kommunikation'], default_legal_basis: 'contract' },
],
'Marketing / Agentur': [
{ id: 'crm', name: 'CRM / Kundenverwaltung', purpose: 'Pflege von Kundenbeziehungen, Kampagnen', default_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten', 'kommunikation'], default_legal_basis: 'contract' },
{ id: 'campaign', name: 'Kampagnen / Analytics', purpose: 'Werbekampagnen, Tracking, Auswertung', default_categories: ['nutzungsdaten', 'kontaktdaten', 'standortdaten'], default_legal_basis: 'consent' },
],
'Produktion / Industrie': [
{ id: 'lieferanten', name: 'Lieferantenverwaltung', purpose: 'Bestellungen, Lieferantenqualifizierung', default_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten', 'zahlungsdaten'], default_legal_basis: 'contract' },
{ id: 'zutrittskontrolle', name: 'Zutrittskontrolle', purpose: 'Werksgelände-Zugang, Besuchermanagement', default_categories: ['stammdaten', 'standortdaten', 'bilddaten'], default_legal_basis: 'interest' },
],
interface ActivityDepartment {
id: string
name: string
icon: string
activities: ActivityTemplate[]
}
// Get processing activity templates for the selected industry
function getActivityTemplates(industry: string): ProcessingActivityTemplate[] {
const industrySpecific = INDUSTRY_ACTIVITIES[industry] || []
// Merge common + industry-specific, avoiding duplicate IDs
const existingIds = new Set(industrySpecific.map(a => a.id))
const common = COMMON_ACTIVITIES.filter(a => !existingIds.has(a.id))
return [...industrySpecific, ...common]
// ── Universelle Abteilungen (immer sichtbar) ──
const UNIVERSAL_DEPARTMENTS: ActivityDepartment[] = [
{
id: 'personal', name: 'Personal / HR', icon: '👥',
activities: [
{ id: 'personalverwaltung', name: 'Personalverwaltung', purpose: 'Verwaltung von Beschäftigtendaten für das Arbeitsverhältnis', primary_categories: ['stammdaten', 'kontaktdaten', 'beschaeftigtendaten', 'zahlungsdaten'], art9_relevant: ['gesundheit', 'religion', 'gewerkschaft'], default_legal_basis: 'contract' },
{ id: 'lohnbuchhaltung', name: 'Lohn- und Gehaltsabrechnung', purpose: 'Berechnung und Auszahlung von Löhnen und Gehältern', primary_categories: ['beschaeftigtendaten', 'zahlungsdaten', 'stammdaten'], art9_relevant: ['gesundheit', 'religion'], default_legal_basis: 'legal' },
{ id: 'bewerbermanagement', name: 'Bewerbermanagement', purpose: 'Entgegennahme, Prüfung und Bearbeitung von Bewerbungen', primary_categories: ['bewerberdaten', 'stammdaten', 'kontaktdaten', 'kommunikation'], art9_relevant: ['gesundheit'], default_legal_basis: 'consent' },
{ id: 'arbeitszeiterfassung', name: 'Arbeitszeiterfassung', purpose: 'Erfassung und Dokumentation der Arbeitszeiten', primary_categories: ['beschaeftigtendaten'], art9_relevant: [], default_legal_basis: 'legal' },
{ id: 'weiterbildung', name: 'Fort- und Weiterbildung', purpose: 'Verwaltung von Schulungen und Weiterbildungsmaßnahmen', primary_categories: ['beschaeftigtendaten', 'stammdaten'], art9_relevant: [], default_legal_basis: 'contract' },
],
},
{
id: 'finanzen', name: 'Finanzen / Buchhaltung', icon: '💰',
activities: [
{ id: 'finanzbuchhaltung', name: 'Finanzbuchhaltung', purpose: 'Buchführung, Rechnungsstellung, steuerliche Dokumentation', primary_categories: ['stammdaten', 'zahlungsdaten', 'vertragsdaten', 'kontaktdaten'], art9_relevant: [], default_legal_basis: 'legal' },
{ id: 'zahlungsverkehr', name: 'Zahlungsverkehr', purpose: 'Abwicklung von ein- und ausgehenden Zahlungen', primary_categories: ['zahlungsdaten', 'stammdaten', 'kontaktdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'mahnwesen', name: 'Mahnwesen / Inkasso', purpose: 'Überwachung offener Forderungen und Mahnverfahren', primary_categories: ['stammdaten', 'kontaktdaten', 'zahlungsdaten', 'vertragsdaten'], art9_relevant: [], default_legal_basis: 'interest' },
{ id: 'reisekostenabrechnung', name: 'Reisekostenabrechnung', purpose: 'Abrechnung und Erstattung von Dienstreisekosten', primary_categories: ['beschaeftigtendaten', 'zahlungsdaten', 'standortdaten'], art9_relevant: [], default_legal_basis: 'contract' },
],
},
{
id: 'vertrieb', name: 'Vertrieb / Sales', icon: '📈',
activities: [
{ id: 'crm', name: 'CRM / Kundenverwaltung', purpose: 'Verwaltung von Kundenbeziehungen, Kontakthistorie, Verkaufschancen', primary_categories: ['stammdaten', 'kontaktdaten', 'kommunikation', 'vertragsdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'angebotserstellung', name: 'Angebotserstellung', purpose: 'Erstellung und Nachverfolgung von Angeboten', primary_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'vertragsmanagement', name: 'Vertragsmanagement', purpose: 'Verwaltung, Archivierung und Nachverfolgung von Verträgen', primary_categories: ['vertragsdaten', 'stammdaten', 'kontaktdaten'], art9_relevant: [], default_legal_basis: 'contract' },
],
},
{
id: 'marketing', name: 'Marketing', icon: '📣',
activities: [
{ id: 'newsletter', name: 'Newsletter / E-Mail-Marketing', purpose: 'Versand von Newslettern und E-Mail-Marketing an Abonnenten', primary_categories: ['kontaktdaten', 'nutzungsdaten', 'stammdaten'], art9_relevant: [], default_legal_basis: 'consent' },
{ id: 'website_tracking', name: 'Website-Tracking / Analytics', purpose: 'Analyse des Nutzerverhaltens auf der Website mittels Tracking-Tools', primary_categories: ['nutzungsdaten', 'standortdaten'], art9_relevant: [], default_legal_basis: 'consent' },
{ id: 'social_media', name: 'Social-Media-Marketing', purpose: 'Betrieb von Unternehmensprofilen und Werbekampagnen', primary_categories: ['kontaktdaten', 'nutzungsdaten', 'kommunikation'], art9_relevant: [], default_legal_basis: 'consent' },
{ id: 'consent_management', name: 'Consent-Management (Cookies)', purpose: 'Verwaltung der Einwilligungen für Cookies und Tracking', primary_categories: ['nutzungsdaten'], art9_relevant: [], default_legal_basis: 'consent' },
],
},
{
id: 'it', name: 'IT / Administration', icon: '🖥️',
activities: [
{ id: 'zugangsverwaltung', name: 'Zugangsverwaltung (IAM)', purpose: 'Verwaltung von Benutzerkonten, Passwörtern und Zugriffsrechten', primary_categories: ['beschaeftigtendaten', 'stammdaten', 'nutzungsdaten'], art9_relevant: ['biometrie'], default_legal_basis: 'contract' },
{ id: 'email_kommunikation', name: 'E-Mail-Kommunikation', purpose: 'Geschäftliche E-Mail-Korrespondenz', primary_categories: ['kontaktdaten', 'kommunikation', 'stammdaten'], art9_relevant: [], default_legal_basis: 'interest' },
{ id: 'datensicherung', name: 'Datensicherung / Backup', purpose: 'Sicherung von Unternehmensdaten zum Schutz vor Datenverlust', primary_categories: ['nutzungsdaten', 'beschaeftigtendaten'], art9_relevant: [], default_legal_basis: 'interest' },
{ id: 'website_betrieb', name: 'Website-Betrieb', purpose: 'Bereitstellung der Unternehmenswebsite und Kontaktformulare', primary_categories: ['nutzungsdaten', 'kontaktdaten'], art9_relevant: [], default_legal_basis: 'interest' },
{ id: 'it_sicherheit', name: 'IT-Sicherheit / Logging', purpose: 'Überwachung der IT-Sicherheit, Log-Analyse, Vorfallbehandlung', primary_categories: ['nutzungsdaten', 'beschaeftigtendaten'], art9_relevant: [], default_legal_basis: 'interest' },
],
},
{
id: 'recht', name: 'Recht / Compliance', icon: '⚖️',
activities: [
{ id: 'datenschutzanfragen', name: 'Betroffenenrechte (DSGVO)', purpose: 'Bearbeitung von Auskunfts-, Lösch- und Berichtigungsanfragen', primary_categories: ['stammdaten', 'kontaktdaten', 'kommunikation'], art9_relevant: [], default_legal_basis: 'legal' },
{ id: 'auftragsverarbeitung', name: 'Auftragsverarbeitung (AVV)', purpose: 'Dokumentation und Verwaltung von Auftragsverarbeitungsverhältnissen', primary_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten'], art9_relevant: [], default_legal_basis: 'legal' },
{ id: 'whistleblowing', name: 'Hinweisgebersystem', purpose: 'Entgegennahme und Bearbeitung von Meldungen nach HinSchG', primary_categories: ['stammdaten', 'kontaktdaten', 'kommunikation'], art9_relevant: [], default_legal_basis: 'legal' },
],
},
]
// ── Abteilungen die je nach Kontext relevant sind ──
const OPTIONAL_DEPARTMENTS: ActivityDepartment[] = [
{
id: 'einkauf', name: 'Einkauf / Beschaffung', icon: '🛒',
activities: [
{ id: 'lieferantenverwaltung', name: 'Lieferantenverwaltung', purpose: 'Erfassung und Pflege von Lieferantenstammdaten', primary_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten', 'zahlungsdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'bestellwesen', name: 'Bestellwesen', purpose: 'Abwicklung von Bestellungen bei Lieferanten', primary_categories: ['stammdaten', 'vertragsdaten', 'zahlungsdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'lieferantenbewertung', name: 'Lieferantenbewertung', purpose: 'Bewertung und Qualifizierung von Lieferanten', primary_categories: ['stammdaten', 'kontaktdaten'], art9_relevant: [], default_legal_basis: 'interest' },
],
},
{
id: 'produktion', name: 'Produktion / Fertigung', icon: '🏭',
activities: [
{ id: 'produktionsplanung', name: 'Produktionsplanung', purpose: 'Planung und Steuerung von Produktionsprozessen inkl. Personalzuordnung', primary_categories: ['beschaeftigtendaten', 'stammdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'qualitaetskontrolle', name: 'Qualitätskontrolle', purpose: 'Prüfung und Dokumentation der Produktqualität', primary_categories: ['beschaeftigtendaten', 'stammdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'arbeitssicherheit', name: 'Arbeitssicherheit / Arbeitsschutz', purpose: 'Dokumentation von Arbeitsschutzmaßnahmen, Unfällen, Gefährdungsbeurteilungen', primary_categories: ['beschaeftigtendaten'], art9_relevant: ['gesundheit'], default_legal_basis: 'legal' },
{ id: 'schichtplanung', name: 'Schichtplanung', purpose: 'Erstellung und Verwaltung von Schichtplänen', primary_categories: ['beschaeftigtendaten'], art9_relevant: [], default_legal_basis: 'contract' },
],
},
{
id: 'logistik', name: 'Logistik / Versand', icon: '🚚',
activities: [
{ id: 'versandabwicklung', name: 'Versandabwicklung', purpose: 'Verarbeitung von Empfänger- und Versanddaten für den Warenversand', primary_categories: ['stammdaten', 'kontaktdaten', 'standortdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'lieferverfolgung', name: 'Lieferverfolgung / Sendungstracking', purpose: 'Nachverfolgung von Sendungen und Zustellung', primary_categories: ['stammdaten', 'kontaktdaten', 'standortdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'lagerverwaltung', name: 'Lagerverwaltung', purpose: 'Verwaltung von Lagerbeständen und Warenbewegungen', primary_categories: ['stammdaten', 'beschaeftigtendaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'retouren', name: 'Retourenmanagement', purpose: 'Bearbeitung von Warenrücksendungen', primary_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten'], art9_relevant: [], default_legal_basis: 'contract' },
],
},
{
id: 'kundenservice', name: 'Kundenservice / Support', icon: '🎧',
activities: [
{ id: 'ticketsystem', name: 'Ticketsystem / Support', purpose: 'Erfassung und Bearbeitung von Kundenanfragen und Supportfällen', primary_categories: ['stammdaten', 'kontaktdaten', 'kommunikation'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'beschwerdemanagement', name: 'Beschwerdemanagement', purpose: 'Bearbeitung und Dokumentation von Kundenbeschwerden', primary_categories: ['stammdaten', 'kontaktdaten', 'kommunikation'], art9_relevant: [], default_legal_basis: 'contract' },
],
},
{
id: 'facility', name: 'Facility Management', icon: '🏢',
activities: [
{ id: 'zutrittskontrolle', name: 'Zutrittskontrolle', purpose: 'Kontrolle und Protokollierung des Zutritts zu Gebäuden und Räumen', primary_categories: ['beschaeftigtendaten', 'stammdaten', 'bilddaten'], art9_relevant: ['biometrie'], default_legal_basis: 'interest' },
{ id: 'videoueberwachung', name: 'Videoüberwachung', purpose: 'Überwachung von Gebäuden und Geländen mittels Videokameras', primary_categories: ['bilddaten', 'beschaeftigtendaten'], art9_relevant: ['biometrie'], default_legal_basis: 'interest' },
{ id: 'besuchermanagement', name: 'Besuchermanagement', purpose: 'Erfassung und Verwaltung von Besucherdaten', primary_categories: ['stammdaten', 'kontaktdaten'], art9_relevant: [], default_legal_basis: 'interest' },
],
},
]
// ── Branchenspezifische Abteilungen ──
const INDUSTRY_DEPARTMENTS: Record<string, ActivityDepartment[]> = {
'E-Commerce / Handel': [{
id: 'ecommerce', name: 'E-Commerce / Webshop', icon: '🛍️',
activities: [
{ id: 'bestellabwicklung', name: 'Bestellabwicklung (Webshop)', purpose: 'Verarbeitung von Kundenbestellungen im Online-Shop', primary_categories: ['stammdaten', 'kontaktdaten', 'zahlungsdaten', 'vertragsdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'kundenkonto', name: 'Kundenkonto-Verwaltung', purpose: 'Verwaltung registrierter Kundenkonten im Online-Shop', primary_categories: ['stammdaten', 'kontaktdaten', 'nutzungsdaten', 'zahlungsdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'webshop_analyse', name: 'Webshop-Analyse / Conversion', purpose: 'Analyse des Kaufverhaltens und Conversion-Rates', primary_categories: ['nutzungsdaten', 'standortdaten'], art9_relevant: [], default_legal_basis: 'consent' },
{ id: 'produktbewertungen', name: 'Produktbewertungen / Reviews', purpose: 'Verwaltung von Kundenrezensionen und Produktbewertungen', primary_categories: ['stammdaten', 'kontaktdaten', 'kommunikation'], art9_relevant: [], default_legal_basis: 'consent' },
],
}],
'Gesundheitswesen': [{
id: 'gesundheit_dept', name: 'Medizin / Patientenversorgung', icon: '🏥',
activities: [
{ id: 'patientenverwaltung', name: 'Patientenverwaltung', purpose: 'Verwaltung von Patientenstammdaten und Krankengeschichte', primary_categories: ['stammdaten', 'kontaktdaten', 'zahlungsdaten'], art9_relevant: ['gesundheit', 'genetik'], default_legal_basis: 'contract' },
{ id: 'terminplanung_med', name: 'Terminplanung (Patienten)', purpose: 'Vergabe und Verwaltung von Patiententerminen', primary_categories: ['stammdaten', 'kontaktdaten'], art9_relevant: ['gesundheit'], default_legal_basis: 'contract' },
{ id: 'kv_abrechnung', name: 'KV-Abrechnung', purpose: 'Abrechnung von Leistungen gegenüber Kassenärztlichen Vereinigungen', primary_categories: ['stammdaten', 'zahlungsdaten'], art9_relevant: ['gesundheit'], default_legal_basis: 'legal' },
{ id: 'med_dokumentation', name: 'Medizinische Dokumentation', purpose: 'Dokumentation von Diagnosen, Therapien und Behandlungsverläufen', primary_categories: ['stammdaten'], art9_relevant: ['gesundheit', 'genetik'], default_legal_basis: 'legal' },
],
}],
'Finanzdienstleistungen': [{
id: 'finanz_dept', name: 'Regulatorik / Finanzaufsicht', icon: '🏦',
activities: [
{ id: 'kyc', name: 'Know Your Customer (KYC)', purpose: 'Identifizierung und Verifizierung von Kunden gemäß GwG', primary_categories: ['stammdaten', 'kontaktdaten', 'bilddaten'], art9_relevant: [], default_legal_basis: 'legal' },
{ id: 'kontoverwaltung', name: 'Kontoverwaltung', purpose: 'Verwaltung von Kundenkonten und Kontobewegungen', primary_categories: ['stammdaten', 'kontaktdaten', 'zahlungsdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'geldwaeschepraevention', name: 'Geldwäscheprävention (AML)', purpose: 'Überwachung verdächtiger Transaktionen nach GwG', primary_categories: ['stammdaten', 'zahlungsdaten'], art9_relevant: [], default_legal_basis: 'legal' },
],
}],
'Bildung': [{
id: 'bildung_dept', name: 'Bildung / Lehre', icon: '🎓',
activities: [
{ id: 'schuelerverwaltung', name: 'Schüler-/Teilnehmerverwaltung', purpose: 'Verwaltung von Lernenden, Noten, Anwesenheit', primary_categories: ['stammdaten', 'kontaktdaten', 'nutzungsdaten'], art9_relevant: ['gesundheit', 'religion'], default_legal_basis: 'contract' },
{ id: 'lernplattform', name: 'Lernplattform / LMS', purpose: 'Bereitstellung und Nutzung digitaler Lernplattformen', primary_categories: ['stammdaten', 'nutzungsdaten', 'kommunikation'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'pruefungsverwaltung', name: 'Prüfungsverwaltung', purpose: 'Verwaltung und Dokumentation von Prüfungen und Noten', primary_categories: ['stammdaten'], art9_relevant: [], default_legal_basis: 'contract' },
],
}],
'Immobilien': [{
id: 'immobilien_dept', name: 'Immobilienverwaltung', icon: '🏠',
activities: [
{ id: 'mieterverwaltung', name: 'Mieterverwaltung', purpose: 'Verwaltung von Mietverträgen und Mieterdaten', primary_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten', 'zahlungsdaten'], art9_relevant: [], default_legal_basis: 'contract' },
{ id: 'nebenkostenabrechnung', name: 'Nebenkostenabrechnung', purpose: 'Erstellung und Versand von Nebenkostenabrechnungen', primary_categories: ['stammdaten', 'zahlungsdaten'], art9_relevant: [], default_legal_basis: 'contract' },
],
}],
}
// Compute which departments to show based on company context
function getRelevantDepartments(industry: string, businessModel: string | undefined, companySize: string | undefined): ActivityDepartment[] {
const departments: ActivityDepartment[] = [...UNIVERSAL_DEPARTMENTS]
// Always show optional departments — user can choose
departments.push(...OPTIONAL_DEPARTMENTS)
// Add industry-specific departments
const industryDepts = INDUSTRY_DEPARTMENTS[industry]
if (industryDepts) {
departments.push(...industryDepts)
}
return departments
}
interface ProcessingActivity {
@@ -695,6 +856,7 @@ interface ProcessingActivity {
purpose: string
data_categories: string[]
legal_basis: string
department?: string
custom?: boolean
}
@@ -705,6 +867,15 @@ interface AISystem {
processes_personal_data: boolean
}
// Helper: find template for an activity ID across all departments
function findTemplate(departments: ActivityDepartment[], activityId: string): ActivityTemplate | null {
for (const dept of departments) {
const t = dept.activities.find(a => a.id === activityId)
if (t) return t
}
return null
}
function StepProcessingAndAI({
data,
onChange,
@@ -716,12 +887,13 @@ function StepProcessingAndAI({
const aiSystems: AISystem[] = (data as any).aiSystems || []
const industry = data.industry || ''
const [expandedActivity, setExpandedActivity] = useState<string | null>(null)
const [collapsedDepts, setCollapsedDepts] = useState<Set<string>>(new Set())
const [showExtraCategories, setShowExtraCategories] = useState<Set<string>>(new Set())
// Get suggested templates based on industry
const templates = getActivityTemplates(industry)
const departments = getRelevantDepartments(industry, data.businessModel, data.companySize)
const activeIds = new Set(activities.map(a => a.id))
const toggleActivity = (template: ProcessingActivityTemplate) => {
const toggleActivity = (template: ActivityTemplate, deptId: string) => {
if (activeIds.has(template.id)) {
onChange({ processingSystems: activities.filter(a => a.id !== template.id) })
} else {
@@ -730,8 +902,9 @@ function StepProcessingAndAI({
id: template.id,
name: template.name,
purpose: template.purpose,
data_categories: [...template.default_categories],
data_categories: [...template.primary_categories],
legal_basis: template.default_legal_basis,
department: deptId,
}],
})
}
@@ -752,6 +925,22 @@ function StepProcessingAndAI({
updateActivity(activityId, { data_categories: cats })
}
const toggleDeptCollapse = (deptId: string) => {
setCollapsedDepts(prev => {
const next = new Set(prev)
if (next.has(deptId)) next.delete(deptId); else next.add(deptId)
return next
})
}
const toggleExtraCategories = (activityId: string) => {
setShowExtraCategories(prev => {
const next = new Set(prev)
if (next.has(activityId)) next.delete(activityId); else next.add(activityId)
return next
})
}
const addCustomActivity = () => {
const id = `custom_${Date.now()}`
onChange({
@@ -772,6 +961,10 @@ function StepProcessingAndAI({
if (expandedActivity === id) setExpandedActivity(null)
}
// Count active activities per department
const deptActivityCount = (dept: ActivityDepartment) =>
dept.activities.filter(a => activeIds.has(a.id)).length
// AI Systems
const addAISystem = () => {
onChange({ aiSystems: [...aiSystems, { name: '', vendor: '', purpose: '', processes_personal_data: false }] })
@@ -785,126 +978,189 @@ function StepProcessingAndAI({
onChange({ aiSystems: updated })
}
// Render activity detail panel (shared between template and custom)
const renderActivityDetail = (activity: ProcessingActivity, template: ActivityTemplate | null) => {
const primaryIds = new Set(template?.primary_categories || [])
const art9Ids = new Set(template?.art9_relevant || [])
const primaryCats = ALL_DATA_CATEGORIES.filter(c => primaryIds.has(c.id))
const extraCats = ALL_DATA_CATEGORIES.filter(c => !primaryIds.has(c.id))
const relevantArt9 = ALL_SPECIAL_CATEGORIES.filter(c => art9Ids.has(c.id))
const otherArt9 = ALL_SPECIAL_CATEGORIES.filter(c => !art9Ids.has(c.id))
const showingExtra = showExtraCategories.has(activity.id)
// For custom activities, show all categories
const isCustom = !template || activity.custom
return (
<div className="ml-4 mt-2 p-4 bg-gray-50 rounded-lg border border-gray-200 space-y-4">
{/* Custom: name + purpose fields */}
{isCustom && (
<div className="grid grid-cols-1 gap-3">
<input type="text" value={activity.name} onChange={e => updateActivity(activity.id, { name: e.target.value })} placeholder="Name der Verarbeitungstätigkeit" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<input type="text" value={activity.purpose} onChange={e => updateActivity(activity.id, { purpose: e.target.value })} placeholder="Zweck der Verarbeitung" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
</div>
)}
{/* Primary Data Categories */}
<div>
<label className="block text-xs font-medium text-gray-600 mb-2">Betroffene Datenkategorien</label>
<div className="grid grid-cols-2 gap-1.5">
{(isCustom ? ALL_DATA_CATEGORIES : primaryCats).map(cat => (
<label key={cat.id} className="flex items-center gap-2 text-xs p-1.5 rounded hover:bg-gray-100 cursor-pointer">
<input type="checkbox" checked={activity.data_categories.includes(cat.id)} onChange={() => toggleDataCategory(activity.id, cat.id)} className="w-3.5 h-3.5 text-purple-600 rounded focus:ring-purple-500" />
<span className="text-gray-700" title={cat.desc}>{cat.label}</span>
</label>
))}
</div>
</div>
{/* Extra categories (expandable, only for template-based) */}
{!isCustom && extraCats.length > 0 && (
<div>
<button type="button" onClick={() => toggleExtraCategories(activity.id)} className="text-xs text-purple-600 hover:text-purple-800">
{showingExtra ? '▾ Weitere Kategorien ausblenden' : `▸ Weitere ${extraCats.length} Kategorien anzeigen`}
</button>
{showingExtra && (
<div className="grid grid-cols-2 gap-1.5 mt-2">
{extraCats.map(cat => (
<label key={cat.id} className="flex items-center gap-2 text-xs p-1.5 rounded hover:bg-gray-100 cursor-pointer">
<input type="checkbox" checked={activity.data_categories.includes(cat.id)} onChange={() => toggleDataCategory(activity.id, cat.id)} className="w-3.5 h-3.5 text-purple-600 rounded focus:ring-purple-500" />
<span className="text-gray-500" title={cat.desc}>{cat.label}</span>
</label>
))}
</div>
)}
</div>
)}
{/* Art. 9 Special Categories — only show if relevant for this activity */}
{(isCustom ? ALL_SPECIAL_CATEGORIES.length > 0 : relevantArt9.length > 0) && (
<div className="bg-red-50 rounded-lg p-3 border border-red-100">
<label className="block text-xs font-medium text-red-700 mb-2">
Besondere Kategorien (Art. 9 DSGVO)
<span className="font-normal text-red-400 ml-1"> erfordern zusätzliche Rechtsgrundlage</span>
</label>
<div className="grid grid-cols-2 gap-1.5">
{(isCustom ? ALL_SPECIAL_CATEGORIES : relevantArt9).map(cat => (
<label key={cat.id} className="flex items-center gap-2 text-xs p-1.5 rounded hover:bg-red-100 cursor-pointer">
<input type="checkbox" checked={activity.data_categories.includes(cat.id)} onChange={() => toggleDataCategory(activity.id, cat.id)} className="w-3.5 h-3.5 text-red-600 rounded focus:ring-red-500" />
<span className="text-gray-700" title={cat.desc}>{cat.label}</span>
</label>
))}
</div>
{/* Show remaining Art. 9 categories if expanded */}
{!isCustom && otherArt9.length > 0 && showingExtra && (
<div className="grid grid-cols-2 gap-1.5 mt-2 pt-2 border-t border-red-100">
{otherArt9.map(cat => (
<label key={cat.id} className="flex items-center gap-2 text-xs p-1.5 rounded hover:bg-red-100 cursor-pointer">
<input type="checkbox" checked={activity.data_categories.includes(cat.id)} onChange={() => toggleDataCategory(activity.id, cat.id)} className="w-3.5 h-3.5 text-red-600 rounded focus:ring-red-500" />
<span className="text-gray-500" title={cat.desc}>{cat.label}</span>
</label>
))}
</div>
)}
</div>
)}
{/* Legal Basis */}
<div>
<label className="block text-xs font-medium text-gray-600 mb-2">Rechtsgrundlage</label>
<select value={activity.legal_basis} onChange={e => updateActivity(activity.id, { legal_basis: e.target.value })} className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent">
{LEGAL_BASES.map(lb => (
<option key={lb.id} value={lb.id}>{lb.label}</option>
))}
</select>
</div>
<button type="button" onClick={() => removeActivity(activity.id)} className="text-xs text-red-500 hover:text-red-700">
Verarbeitungstätigkeit entfernen
</button>
</div>
)
}
return (
<div className="space-y-8">
{/* Processing Activities */}
{/* Processing Activities grouped by department */}
<div>
<h3 className="text-sm font-medium text-gray-700 mb-1">Verarbeitungstätigkeiten</h3>
<p className="text-xs text-gray-500 mb-4">
Wählen Sie aus, welche Verarbeitungstätigkeiten in Ihrem Unternehmen stattfinden. Diese bilden die Grundlage für Ihr Verarbeitungsverzeichnis (VVT) nach Art. 30 DSGVO.
Wählen Sie pro Abteilung aus, welche Verarbeitungen stattfinden. Diese bilden die Grundlage für Ihr Verarbeitungsverzeichnis (VVT) nach Art. 30 DSGVO.
</p>
{/* Template checkboxes */}
<div className="space-y-2 mb-4">
{templates.map(template => {
const isActive = activeIds.has(template.id)
const activity = activities.find(a => a.id === template.id)
const isExpanded = expandedActivity === template.id
<div className="space-y-4">
{departments.map(dept => {
const isCollapsed = collapsedDepts.has(dept.id)
const activeCount = deptActivityCount(dept)
return (
<div key={template.id}>
<div
className={`flex items-center gap-3 p-3 rounded-lg border-2 cursor-pointer transition-all ${
isActive ? 'border-purple-500 bg-purple-50' : 'border-gray-200 hover:border-purple-300'
}`}
onClick={() => {
if (!isActive) {
toggleActivity(template)
setExpandedActivity(template.id)
} else {
setExpandedActivity(isExpanded ? null : template.id)
}
}}
<div key={dept.id} className="border border-gray-200 rounded-lg overflow-hidden">
{/* Department header */}
<button
type="button"
onClick={() => toggleDeptCollapse(dept.id)}
className="w-full flex items-center gap-3 px-4 py-3 bg-gray-50 hover:bg-gray-100 transition-colors text-left"
>
<input
type="checkbox"
checked={isActive}
onChange={e => {
e.stopPropagation()
toggleActivity(template)
}}
className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500 flex-shrink-0"
/>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium text-gray-900">{template.name}</span>
<p className="text-xs text-gray-500 truncate">{template.purpose}</p>
</div>
{isActive && (
<span className="text-xs text-purple-600 flex-shrink-0">
{activity?.data_categories.length || 0} Kategorien
<span className="text-base">{dept.icon}</span>
<span className="text-sm font-medium text-gray-900 flex-1">{dept.name}</span>
{activeCount > 0 && (
<span className="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded-full">
{activeCount} aktiv
</span>
)}
{isActive && (
<svg className={`w-4 h-4 text-gray-400 transition-transform ${isExpanded ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
)}
</div>
<svg className={`w-4 h-4 text-gray-400 transition-transform ${isCollapsed ? '' : 'rotate-180'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{/* Expanded detail: data categories + legal basis */}
{isActive && isExpanded && activity && (
<div className="ml-4 mt-2 p-4 bg-gray-50 rounded-lg border border-gray-200 space-y-4">
{/* Data Categories */}
<div>
<label className="block text-xs font-medium text-gray-600 mb-2">Betroffene Datenkategorien</label>
<div className="grid grid-cols-2 gap-1.5">
{DATA_CATEGORIES.map(cat => (
<label key={cat.id} className="flex items-center gap-2 text-xs p-1.5 rounded hover:bg-gray-100 cursor-pointer">
{/* Department activities */}
{!isCollapsed && (
<div className="p-3 space-y-2">
{dept.activities.map(template => {
const isActive = activeIds.has(template.id)
const activity = activities.find(a => a.id === template.id)
const isExpanded = expandedActivity === template.id
return (
<div key={template.id}>
<div
className={`flex items-center gap-3 p-3 rounded-lg border-2 cursor-pointer transition-all ${
isActive ? 'border-purple-500 bg-purple-50' : 'border-gray-100 hover:border-purple-300'
}`}
onClick={() => {
if (!isActive) {
toggleActivity(template, dept.id)
setExpandedActivity(template.id)
} else {
setExpandedActivity(isExpanded ? null : template.id)
}
}}
>
<input
type="checkbox"
checked={activity.data_categories.includes(cat.id)}
onChange={() => toggleDataCategory(activity.id, cat.id)}
className="w-3.5 h-3.5 text-purple-600 rounded focus:ring-purple-500"
checked={isActive}
onChange={e => { e.stopPropagation(); toggleActivity(template, dept.id) }}
className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500 flex-shrink-0"
/>
<span className="text-gray-700" title={cat.desc}>{cat.label}</span>
</label>
))}
</div>
</div>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium text-gray-900">{template.name}</span>
<p className="text-xs text-gray-500 truncate">{template.purpose}</p>
</div>
{isActive && (
<span className="text-xs text-purple-600 flex-shrink-0">
{activity?.data_categories.length || 0} Kat.
</span>
)}
{isActive && (
<svg className={`w-4 h-4 text-gray-400 transition-transform ${isExpanded ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
)}
</div>
{/* Special Categories (Art. 9) */}
<div>
<label className="block text-xs font-medium text-gray-600 mb-2">
Besondere Kategorien (Art. 9 DSGVO)
<span className="font-normal text-gray-400 ml-1"> nur wenn zutreffend</span>
</label>
<div className="grid grid-cols-2 gap-1.5">
{SPECIAL_DATA_CATEGORIES.map(cat => (
<label key={cat.id} className="flex items-center gap-2 text-xs p-1.5 rounded hover:bg-red-50 cursor-pointer">
<input
type="checkbox"
checked={activity.data_categories.includes(cat.id)}
onChange={() => toggleDataCategory(activity.id, cat.id)}
className="w-3.5 h-3.5 text-red-600 rounded focus:ring-red-500"
/>
<span className="text-gray-700" title={cat.desc}>{cat.label}</span>
</label>
))}
</div>
</div>
{/* Legal Basis */}
<div>
<label className="block text-xs font-medium text-gray-600 mb-2">Rechtsgrundlage</label>
<select
value={activity.legal_basis}
onChange={e => updateActivity(activity.id, { legal_basis: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
{LEGAL_BASES.map(lb => (
<option key={lb.id} value={lb.id}>{lb.label}</option>
))}
</select>
</div>
{/* Remove button for template-based activities */}
<button
type="button"
onClick={() => toggleActivity(template)}
className="text-xs text-red-500 hover:text-red-700"
>
Verarbeitungstätigkeit entfernen
</button>
{isActive && isExpanded && activity && renderActivityDetail(activity, template)}
</div>
)
})}
</div>
)}
</div>
@@ -914,12 +1170,12 @@ function StepProcessingAndAI({
{/* Custom activities */}
{activities.filter(a => a.custom).map(activity => (
<div key={activity.id} className="mb-2">
<div key={activity.id} className="mt-2">
<div
className="flex items-center gap-3 p-3 rounded-lg border-2 border-purple-500 bg-purple-50 cursor-pointer"
onClick={() => setExpandedActivity(expandedActivity === activity.id ? null : activity.id)}
>
<span className="w-4 h-4 flex items-center justify-center text-purple-600 flex-shrink-0 text-sm"></span>
<span className="w-4 h-4 flex items-center justify-center text-purple-600 flex-shrink-0 text-sm">+</span>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium text-gray-900">{activity.name || 'Neue Verarbeitungstätigkeit'}</span>
</div>
@@ -927,85 +1183,7 @@ function StepProcessingAndAI({
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
{expandedActivity === activity.id && (
<div className="ml-4 mt-2 p-4 bg-gray-50 rounded-lg border border-gray-200 space-y-4">
<div className="grid grid-cols-1 gap-3">
<input
type="text"
value={activity.name}
onChange={e => updateActivity(activity.id, { name: e.target.value })}
placeholder="Name der Verarbeitungstätigkeit"
className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
<input
type="text"
value={activity.purpose}
onChange={e => updateActivity(activity.id, { purpose: e.target.value })}
placeholder="Zweck der Verarbeitung"
className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
</div>
{/* Data Categories */}
<div>
<label className="block text-xs font-medium text-gray-600 mb-2">Betroffene Datenkategorien</label>
<div className="grid grid-cols-2 gap-1.5">
{DATA_CATEGORIES.map(cat => (
<label key={cat.id} className="flex items-center gap-2 text-xs p-1.5 rounded hover:bg-gray-100 cursor-pointer">
<input
type="checkbox"
checked={activity.data_categories.includes(cat.id)}
onChange={() => toggleDataCategory(activity.id, cat.id)}
className="w-3.5 h-3.5 text-purple-600 rounded focus:ring-purple-500"
/>
<span className="text-gray-700">{cat.label}</span>
</label>
))}
</div>
</div>
{/* Special Categories */}
<div>
<label className="block text-xs font-medium text-gray-600 mb-2">Besondere Kategorien (Art. 9)</label>
<div className="grid grid-cols-2 gap-1.5">
{SPECIAL_DATA_CATEGORIES.map(cat => (
<label key={cat.id} className="flex items-center gap-2 text-xs p-1.5 rounded hover:bg-red-50 cursor-pointer">
<input
type="checkbox"
checked={activity.data_categories.includes(cat.id)}
onChange={() => toggleDataCategory(activity.id, cat.id)}
className="w-3.5 h-3.5 text-red-600 rounded focus:ring-red-500"
/>
<span className="text-gray-700">{cat.label}</span>
</label>
))}
</div>
</div>
{/* Legal Basis */}
<div>
<label className="block text-xs font-medium text-gray-600 mb-2">Rechtsgrundlage</label>
<select
value={activity.legal_basis}
onChange={e => updateActivity(activity.id, { legal_basis: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
{LEGAL_BASES.map(lb => (
<option key={lb.id} value={lb.id}>{lb.label}</option>
))}
</select>
</div>
<button
type="button"
onClick={() => removeActivity(activity.id)}
className="text-xs text-red-500 hover:text-red-700"
>
Verarbeitungstätigkeit entfernen
</button>
</div>
)}
{expandedActivity === activity.id && renderActivityDetail(activity, null)}
</div>
))}
@@ -1013,7 +1191,7 @@ function StepProcessingAndAI({
<button
type="button"
onClick={addCustomActivity}
className="w-full px-3 py-2 text-sm text-purple-700 bg-purple-50 border-2 border-dashed border-purple-200 rounded-lg hover:bg-purple-100 hover:border-purple-300 transition-colors"
className="w-full mt-3 px-3 py-2 text-sm text-purple-700 bg-purple-50 border-2 border-dashed border-purple-200 rounded-lg hover:bg-purple-100 hover:border-purple-300 transition-colors"
>
+ Eigene Verarbeitungstätigkeit hinzufügen
</button>