'use client' import { useState } from 'react' import { CompanyProfile } from '@/lib/sdk/types' import { ProcessingActivity, ActivityTemplate, ActivityDepartment } from './types' import { ALL_DATA_CATEGORIES, ALL_SPECIAL_CATEGORIES, getRelevantDepartments } from './activity-data' function CategoryCheckbox({ cat, activity, variant, template, expandedInfoCat, onToggleCategory, onToggleInfo, }: { cat: { id: string; label: string; desc: string; info: string } activity: ProcessingActivity variant: 'normal' | 'extra' | 'art9' | 'art9-extra' template?: ActivityTemplate | null expandedInfoCat: string | null onToggleCategory: (activityId: string, categoryId: string) => void onToggleInfo: (key: string | null) => void }) { const infoText = template?.categoryInfo?.[cat.id] || cat.info const isInfoExpanded = expandedInfoCat === `${activity.id}-${cat.id}` const colorClasses = variant.startsWith('art9') ? { check: 'text-red-600 focus:ring-red-500', hover: 'hover:bg-red-100', text: variant === 'art9-extra' ? 'text-gray-500' : 'text-gray-700' } : { check: 'text-purple-600 focus:ring-purple-500', hover: 'hover:bg-gray-100', text: variant === 'extra' ? 'text-gray-500' : 'text-gray-700' } const aufbewahrungIdx = infoText.indexOf('Aufbewahrung:') const loeschfristIdx = infoText.indexOf('L\u00F6schfrist') const speicherdauerIdx = infoText.indexOf('Speicherdauer:') const retentionIdx = [aufbewahrungIdx, loeschfristIdx, speicherdauerIdx].filter(i => i >= 0).sort((a, b) => a - b)[0] ?? -1 const hasRetention = retentionIdx >= 0 const mainText = hasRetention ? infoText.slice(0, retentionIdx).replace(/\.\s*$/, '') : infoText const retentionText = hasRetention ? infoText.slice(retentionIdx) : '' return (
{isInfoExpanded && (
{hasRetention ? ( <> {mainText} 🕓{retentionText} ) : infoText}
)}
) } function ActivityDetail({ activity, template, showExtraCategories, expandedInfoCat, onToggleCategory, onToggleInfo, onToggleExtraCategories, onUpdateActivity, onRemoveActivity, }: { activity: ProcessingActivity template: ActivityTemplate | null showExtraCategories: Set expandedInfoCat: string | null onToggleCategory: (activityId: string, categoryId: string) => void onToggleInfo: (key: string | null) => void onToggleExtraCategories: (activityId: string) => void onUpdateActivity: (id: string, updates: Partial) => void onRemoveActivity: (id: string) => void }) { 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) const isCustom = !template || activity.custom return (
{template?.legalHint && (
{template.legalHint}
)} {isCustom && (
onUpdateActivity(activity.id, { name: e.target.value })} placeholder="Name der Verarbeitungst\u00E4tigkeit" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" /> onUpdateActivity(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" />
)} {template?.hasServiceProvider && (
{activity.usesServiceProvider && (
onUpdateActivity(activity.id, { serviceProviderName: e.target.value })} placeholder="Name des Dienstleisters (optional)" className="w-full px-3 py-1.5 border border-blue-200 rounded text-xs focus:ring-2 focus:ring-blue-400 focus:border-transparent bg-white" />

Wird als Auftragsverarbeiter (AVV) im VVT erfasst.

)}
)}
{(isCustom ? ALL_DATA_CATEGORIES : primaryCats).map(cat => )}
{!isCustom && extraCats.length > 0 && (
{showingExtra && (
{extraCats.map(cat => )}
)}
)} {(isCustom ? ALL_SPECIAL_CATEGORIES.length > 0 : relevantArt9.length > 0) && (
{(isCustom ? ALL_SPECIAL_CATEGORIES : relevantArt9).map(cat => )}
{!isCustom && otherArt9.length > 0 && showingExtra && (
{otherArt9.map(cat => )}
)}
)}
) } export function StepProcessing({ data, onChange, }: { data: Partial & { processingSystems?: ProcessingActivity[] } onChange: (updates: Record) => void }) { const activities: ProcessingActivity[] = (data as any).processingSystems || [] const industry = data.industry || [] const [expandedActivity, setExpandedActivity] = useState(null) const [collapsedDepts, setCollapsedDepts] = useState>(new Set()) const [showExtraCats, setShowExtraCats] = useState>(new Set()) const [expandedInfoCat, setExpandedInfoCat] = useState(null) const departments = getRelevantDepartments(industry, data.businessModel, data.companySize) const activeIds = new Set(activities.map(a => a.id)) const toggleActivity = (template: ActivityTemplate, deptId: string) => { if (activeIds.has(template.id)) { onChange({ processingSystems: activities.filter(a => a.id !== template.id) }) } else { onChange({ processingSystems: [...activities, { id: template.id, name: template.name, purpose: template.purpose, data_categories: [...template.primary_categories], legal_basis: template.default_legal_basis, department: deptId, }], }) } } const updateActivity = (id: string, updates: Partial) => { onChange({ processingSystems: activities.map(a => a.id === id ? { ...a, ...updates } : a) }) } const toggleDataCategory = (activityId: string, categoryId: string) => { const activity = activities.find(a => a.id === activityId) if (!activity) return const cats = activity.data_categories.includes(categoryId) ? activity.data_categories.filter(c => c !== categoryId) : [...activity.data_categories, categoryId] 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) => { setShowExtraCats(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({ processingSystems: [...activities, { id, name: '', purpose: '', data_categories: [], legal_basis: 'contract', custom: true }] }) setExpandedActivity(id) } const removeActivity = (id: string) => { onChange({ processingSystems: activities.filter(a => a.id !== id) }) if (expandedActivity === id) setExpandedActivity(null) } const deptActivityCount = (dept: ActivityDepartment) => dept.activities.filter(a => activeIds.has(a.id)).length return (

Verarbeitungstätigkeiten

Wählen Sie pro Abteilung aus, welche Verarbeitungen stattfinden. Diese bilden die Grundlage für Ihr Verarbeitungsverzeichnis (VVT) nach Art. 30 DSGVO.

{departments.map(dept => { const isCollapsed = collapsedDepts.has(dept.id) const activeCount = deptActivityCount(dept) return (
{!isCollapsed && (
{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 (
{ if (!isActive) { toggleActivity(template, dept.id); setExpandedActivity(template.id) } else { setExpandedActivity(isExpanded ? null : template.id) } }} > { e.stopPropagation(); toggleActivity(template, dept.id) }} className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500 flex-shrink-0" />
{template.name} {template.legalHint && Pflicht} {template.hasServiceProvider && AVV-relevant}

{template.purpose}

{isActive && {activity?.data_categories.length || 0} Kat.} {isActive && ( )}
{isActive && isExpanded && activity && ( )}
) })}
)}
) })}
{activities.filter(a => a.custom).map(activity => (
setExpandedActivity(expandedActivity === activity.id ? null : activity.id)}> +
{activity.name || 'Neue Verarbeitungst\u00E4tigkeit'}
{expandedActivity === activity.id && ( )}
))}
) }