'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 && (
)}
{(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 && (
)}
))}
)
}