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
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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user