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' : const STATES_BY_COUNTRY: Record<string, { label: string; options: string[] }> = {
data.headquartersCountry === 'AT' ? 'Bundesland' : DE: {
data.headquartersCountry === 'DE' ? 'Bundesland' : 'Region / Provinz' 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 ( return (
<div className="space-y-8"> <div className="space-y-8">
@@ -464,13 +492,26 @@ function StepLocations({
{/* State / Bundesland / Kanton */} {/* State / Bundesland / Kanton */}
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2">{stateLabel}</label> <label className="block text-sm font-medium text-gray-700 mb-2">{stateLabel}</label>
<input {countryStates ? (
type="text" <select
value={data.headquartersState || ''} value={data.headquartersState || ''}
onChange={e => onChange({ headquartersState: e.target.value })} 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"
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>
<div> <div>
@@ -596,7 +637,7 @@ function StepDataProtection({
// ============================================================================= // =============================================================================
// DSGVO-Standard Datenkategorien // DSGVO-Standard Datenkategorien
const DATA_CATEGORIES = [ const ALL_DATA_CATEGORIES = [
{ id: 'stammdaten', label: 'Stammdaten', desc: 'Name, Geburtsdatum, Geschlecht' }, { id: 'stammdaten', label: 'Stammdaten', desc: 'Name, Geburtsdatum, Geschlecht' },
{ id: 'kontaktdaten', label: 'Kontaktdaten', desc: 'E-Mail, Telefon, Adresse' }, { id: 'kontaktdaten', label: 'Kontaktdaten', desc: 'E-Mail, Telefon, Adresse' },
{ id: 'vertragsdaten', label: 'Vertragsdaten', desc: 'Vertragsnummer, Laufzeit, Konditionen' }, { id: 'vertragsdaten', label: 'Vertragsdaten', desc: 'Vertragsnummer, Laufzeit, Konditionen' },
@@ -609,7 +650,7 @@ const DATA_CATEGORIES = [
{ id: 'bewerberdaten', label: 'Bewerberdaten', desc: 'Lebenslauf, Zeugnisse, Anschreiben' }, { id: 'bewerberdaten', label: 'Bewerberdaten', desc: 'Lebenslauf, Zeugnisse, Anschreiben' },
] as const ] as const
const SPECIAL_DATA_CATEGORIES = [ const ALL_SPECIAL_CATEGORIES = [
{ id: 'gesundheit', label: 'Gesundheitsdaten', desc: 'Krankheitstage, Atteste, Diagnosen' }, { id: 'gesundheit', label: 'Gesundheitsdaten', desc: 'Krankheitstage, Atteste, Diagnosen' },
{ id: 'biometrie', label: 'Biometrische Daten', desc: 'Fingerabdruck, Gesichtserkennung' }, { id: 'biometrie', label: 'Biometrische Daten', desc: 'Fingerabdruck, Gesichtserkennung' },
{ id: 'religion', label: 'Religiöse Überzeugungen', desc: 'Konfession, Feiertage' }, { 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)' }, { id: 'interest', label: 'Berechtigtes Interesse (Art. 6 Abs. 1f)' },
] as const ] as const
// Verarbeitungstätigkeiten-Vorlagen nach Branche // Verarbeitungstätigkeiten mit aktivitätsspezifischen Datenkategorien
interface ProcessingActivityTemplate { interface ActivityTemplate {
id: string id: string
name: string name: string
purpose: 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 default_legal_basis: string
} }
const COMMON_ACTIVITIES: ProcessingActivityTemplate[] = [ interface ActivityDepartment {
{ id: 'personal', name: 'Personalverwaltung', purpose: 'Verwaltung von Mitarbeiterdaten, Gehaltsabrechnung, Arbeitszeitverwaltung', default_categories: ['stammdaten', 'kontaktdaten', 'beschaeftigtendaten', 'zahlungsdaten'], default_legal_basis: 'contract' }, id: string
{ id: 'buchhaltung', name: 'Buchhaltung / Rechnungswesen', purpose: 'Rechnungsstellung, Zahlungsabwicklung, Steuerliche Pflichten', default_categories: ['stammdaten', 'kontaktdaten', 'vertragsdaten', 'zahlungsdaten'], default_legal_basis: 'legal' }, name: string
{ id: 'bewerbung', name: 'Bewerbermanagement', purpose: 'Verwaltung von Bewerbungen und Auswahlverfahren', default_categories: ['stammdaten', 'kontaktdaten', 'bewerberdaten'], default_legal_basis: 'consent' }, icon: string
{ id: 'website', name: 'Website-Betrieb', purpose: 'Bereitstellung der Unternehmenswebsite, Analytics, Kontaktformulare', default_categories: ['nutzungsdaten', 'kontaktdaten'], default_legal_basis: 'interest' }, activities: ActivityTemplate[]
{ 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' },
],
} }
// Get processing activity templates for the selected industry // ── Universelle Abteilungen (immer sichtbar) ──
function getActivityTemplates(industry: string): ProcessingActivityTemplate[] {
const industrySpecific = INDUSTRY_ACTIVITIES[industry] || [] const UNIVERSAL_DEPARTMENTS: ActivityDepartment[] = [
// Merge common + industry-specific, avoiding duplicate IDs {
const existingIds = new Set(industrySpecific.map(a => a.id)) id: 'personal', name: 'Personal / HR', icon: '👥',
const common = COMMON_ACTIVITIES.filter(a => !existingIds.has(a.id)) activities: [
return [...industrySpecific, ...common] { 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 { interface ProcessingActivity {
@@ -695,6 +856,7 @@ interface ProcessingActivity {
purpose: string purpose: string
data_categories: string[] data_categories: string[]
legal_basis: string legal_basis: string
department?: string
custom?: boolean custom?: boolean
} }
@@ -705,6 +867,15 @@ interface AISystem {
processes_personal_data: boolean 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({ function StepProcessingAndAI({
data, data,
onChange, onChange,
@@ -716,12 +887,13 @@ function StepProcessingAndAI({
const aiSystems: AISystem[] = (data as any).aiSystems || [] const aiSystems: AISystem[] = (data as any).aiSystems || []
const industry = data.industry || '' const industry = data.industry || ''
const [expandedActivity, setExpandedActivity] = useState<string | null>(null) 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 departments = getRelevantDepartments(industry, data.businessModel, data.companySize)
const templates = getActivityTemplates(industry)
const activeIds = new Set(activities.map(a => a.id)) const activeIds = new Set(activities.map(a => a.id))
const toggleActivity = (template: ProcessingActivityTemplate) => { const toggleActivity = (template: ActivityTemplate, deptId: string) => {
if (activeIds.has(template.id)) { if (activeIds.has(template.id)) {
onChange({ processingSystems: activities.filter(a => a.id !== template.id) }) onChange({ processingSystems: activities.filter(a => a.id !== template.id) })
} else { } else {
@@ -730,8 +902,9 @@ function StepProcessingAndAI({
id: template.id, id: template.id,
name: template.name, name: template.name,
purpose: template.purpose, purpose: template.purpose,
data_categories: [...template.default_categories], data_categories: [...template.primary_categories],
legal_basis: template.default_legal_basis, legal_basis: template.default_legal_basis,
department: deptId,
}], }],
}) })
} }
@@ -752,6 +925,22 @@ function StepProcessingAndAI({
updateActivity(activityId, { data_categories: cats }) 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 addCustomActivity = () => {
const id = `custom_${Date.now()}` const id = `custom_${Date.now()}`
onChange({ onChange({
@@ -772,6 +961,10 @@ function StepProcessingAndAI({
if (expandedActivity === id) setExpandedActivity(null) 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 // AI Systems
const addAISystem = () => { const addAISystem = () => {
onChange({ aiSystems: [...aiSystems, { name: '', vendor: '', purpose: '', processes_personal_data: false }] }) onChange({ aiSystems: [...aiSystems, { name: '', vendor: '', purpose: '', processes_personal_data: false }] })
@@ -785,126 +978,189 @@ function StepProcessingAndAI({
onChange({ aiSystems: updated }) 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 ( return (
<div className="space-y-8"> <div className="space-y-8">
{/* Processing Activities */} {/* Processing Activities grouped by department */}
<div> <div>
<h3 className="text-sm font-medium text-gray-700 mb-1">Verarbeitungstätigkeiten</h3> <h3 className="text-sm font-medium text-gray-700 mb-1">Verarbeitungstätigkeiten</h3>
<p className="text-xs text-gray-500 mb-4"> <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> </p>
{/* Template checkboxes */} <div className="space-y-4">
<div className="space-y-2 mb-4"> {departments.map(dept => {
{templates.map(template => { const isCollapsed = collapsedDepts.has(dept.id)
const isActive = activeIds.has(template.id) const activeCount = deptActivityCount(dept)
const activity = activities.find(a => a.id === template.id)
const isExpanded = expandedActivity === template.id
return ( return (
<div key={template.id}> <div key={dept.id} className="border border-gray-200 rounded-lg overflow-hidden">
<div {/* Department header */}
className={`flex items-center gap-3 p-3 rounded-lg border-2 cursor-pointer transition-all ${ <button
isActive ? 'border-purple-500 bg-purple-50' : 'border-gray-200 hover:border-purple-300' type="button"
}`} onClick={() => toggleDeptCollapse(dept.id)}
onClick={() => { className="w-full flex items-center gap-3 px-4 py-3 bg-gray-50 hover:bg-gray-100 transition-colors text-left"
if (!isActive) {
toggleActivity(template)
setExpandedActivity(template.id)
} else {
setExpandedActivity(isExpanded ? null : template.id)
}
}}
> >
<input <span className="text-base">{dept.icon}</span>
type="checkbox" <span className="text-sm font-medium text-gray-900 flex-1">{dept.name}</span>
checked={isActive} {activeCount > 0 && (
onChange={e => { <span className="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded-full">
e.stopPropagation() {activeCount} aktiv
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> </span>
)} )}
{isActive && ( <svg className={`w-4 h-4 text-gray-400 transition-transform ${isCollapsed ? '' : 'rotate-180'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> </svg>
</svg> </button>
)}
</div>
{/* Expanded detail: data categories + legal basis */} {/* Department activities */}
{isActive && isExpanded && activity && ( {!isCollapsed && (
<div className="ml-4 mt-2 p-4 bg-gray-50 rounded-lg border border-gray-200 space-y-4"> <div className="p-3 space-y-2">
{/* Data Categories */} {dept.activities.map(template => {
<div> const isActive = activeIds.has(template.id)
<label className="block text-xs font-medium text-gray-600 mb-2">Betroffene Datenkategorien</label> const activity = activities.find(a => a.id === template.id)
<div className="grid grid-cols-2 gap-1.5"> const isExpanded = expandedActivity === template.id
{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"> 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 <input
type="checkbox" type="checkbox"
checked={activity.data_categories.includes(cat.id)} checked={isActive}
onChange={() => toggleDataCategory(activity.id, cat.id)} onChange={e => { e.stopPropagation(); toggleActivity(template, dept.id) }}
className="w-3.5 h-3.5 text-purple-600 rounded focus:ring-purple-500" 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> <div className="flex-1 min-w-0">
</label> <span className="text-sm font-medium text-gray-900">{template.name}</span>
))} <p className="text-xs text-gray-500 truncate">{template.purpose}</p>
</div> </div>
</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) */} {isActive && isExpanded && activity && renderActivityDetail(activity, template)}
<div> </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>
</div> </div>
)} )}
</div> </div>
@@ -914,12 +1170,12 @@ function StepProcessingAndAI({
{/* Custom activities */} {/* Custom activities */}
{activities.filter(a => a.custom).map(activity => ( {activities.filter(a => a.custom).map(activity => (
<div key={activity.id} className="mb-2"> <div key={activity.id} className="mt-2">
<div <div
className="flex items-center gap-3 p-3 rounded-lg border-2 border-purple-500 bg-purple-50 cursor-pointer" 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)} 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"> <div className="flex-1 min-w-0">
<span className="text-sm font-medium text-gray-900">{activity.name || 'Neue Verarbeitungstätigkeit'}</span> <span className="text-sm font-medium text-gray-900">{activity.name || 'Neue Verarbeitungstätigkeit'}</span>
</div> </div>
@@ -927,85 +1183,7 @@ function StepProcessingAndAI({
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg> </svg>
</div> </div>
{expandedActivity === activity.id && renderActivityDetail(activity, null)}
{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>
)}
</div> </div>
))} ))}
@@ -1013,7 +1191,7 @@ function StepProcessingAndAI({
<button <button
type="button" type="button"
onClick={addCustomActivity} 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 + Eigene Verarbeitungstätigkeit hinzufügen
</button> </button>