'use client' /** * TOM - Technische und Organisatorische Maßnahmen * * Art. 32 DSGVO - Sicherheit der Verarbeitung * * Migriert auf SDK API: /sdk/v1/dsgvo/tom */ import { useState, useEffect } from 'react' import { PagePurpose } from '@/components/common/PagePurpose' interface TOM { id: string tenant_id: string namespace_id?: string category: string subcategory?: string name: string description: string type: string // technical, organizational implementation_status: string // planned, in_progress, implemented, verified, not_applicable implemented_at?: string verified_at?: string verified_by?: string effectiveness_rating?: string // low, medium, high documentation?: string responsible_person: string responsible_department: string review_frequency: string // monthly, quarterly, annually last_review_at?: string next_review_at?: string related_controls?: string[] created_at: string updated_at: string } interface CategoryGroup { id: string title: string article: string description: string toms: TOM[] } const CATEGORY_META: Record = { access_control: { title: 'Zugriffskontrolle', article: 'Art. 32 Abs. 1 lit. b', description: 'Fähigkeit, Vertraulichkeit und Integrität auf Dauer sicherzustellen' }, encryption: { title: 'Verschlüsselung', article: 'Art. 32 Abs. 1 lit. a', description: 'Pseudonymisierung und Verschlüsselung personenbezogener Daten' }, pseudonymization: { title: 'Pseudonymisierung', article: 'Art. 32 Abs. 1 lit. a', description: 'Verarbeitung ohne Zuordnung zu identifizierter Person' }, availability: { title: 'Verfügbarkeit & Belastbarkeit', article: 'Art. 32 Abs. 1 lit. b', description: 'Fähigkeit, Verfügbarkeit und Belastbarkeit der Systeme sicherzustellen' }, resilience: { title: 'Wiederherstellung', article: 'Art. 32 Abs. 1 lit. c', description: 'Rasche Wiederherstellung nach physischem oder technischem Zwischenfall' }, monitoring: { title: 'Protokollierung & Audit-Trail', article: 'Art. 32 Abs. 2', description: 'Nachweis der Einhaltung durch Protokollierung' }, incident_response: { title: 'Incident Response', article: 'Art. 33/34', description: 'Meldung von Verletzungen des Schutzes personenbezogener Daten' }, review: { title: 'Regelmäßige Überprüfung', article: 'Art. 32 Abs. 1 lit. d', description: 'Verfahren zur regelmäßigen Überprüfung, Bewertung und Evaluierung' } } export default function TOMPage() { const [toms, setToms] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [expandedCategory, setExpandedCategory] = useState('access_control') const [showCreateModal, setShowCreateModal] = useState(false) const [newTom, setNewTom] = useState({ category: 'access_control', subcategory: '', name: '', description: '', type: 'technical', implementation_status: 'planned', effectiveness_rating: 'medium', documentation: '', responsible_person: '', responsible_department: '', review_frequency: 'quarterly' }) useEffect(() => { loadTOMs() }, []) async function loadTOMs() { setLoading(true) setError(null) try { const res = await fetch('/sdk/v1/dsgvo/tom', { headers: { 'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '', 'X-User-ID': localStorage.getItem('bp_user_id') || '', } }) if (!res.ok) { throw new Error(`HTTP ${res.status}`) } const data = await res.json() setToms(data.toms || []) } catch (err) { console.error('Failed to load TOMs:', err) setError('Fehler beim Laden der TOMs') } finally { setLoading(false) } } async function createTOM() { try { const res = await fetch('/sdk/v1/dsgvo/tom', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '', 'X-User-ID': localStorage.getItem('bp_user_id') || '', }, body: JSON.stringify(newTom) }) if (!res.ok) { throw new Error(`HTTP ${res.status}`) } setShowCreateModal(false) setNewTom({ category: 'access_control', subcategory: '', name: '', description: '', type: 'technical', implementation_status: 'planned', effectiveness_rating: 'medium', documentation: '', responsible_person: '', responsible_department: '', review_frequency: 'quarterly' }) loadTOMs() } catch (err) { console.error('Failed to create TOM:', err) alert('Fehler beim Erstellen der Maßnahme') } } async function exportTOMs(format: 'csv' | 'json') { try { const res = await fetch(`/sdk/v1/dsgvo/export/tom?format=${format}`, { headers: { 'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '', 'X-User-ID': localStorage.getItem('bp_user_id') || '', } }) if (!res.ok) { throw new Error(`HTTP ${res.status}`) } const blob = await res.blob() const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `tom-export.${format}` a.click() window.URL.revokeObjectURL(url) } catch (err) { console.error('Export failed:', err) alert('Export fehlgeschlagen') } } // Group TOMs by category const categoryGroups: CategoryGroup[] = Object.entries(CATEGORY_META).map(([id, meta]) => ({ id, ...meta, toms: toms.filter(t => t.category === id) })).filter(group => group.toms.length > 0 || group.id === expandedCategory) const getStatusBadge = (status: string) => { switch (status) { case 'implemented': return Umgesetzt case 'verified': return Verifiziert case 'in_progress': return In Arbeit case 'planned': return Geplant case 'not_applicable': return N/A default: return null } } const getTypeBadge = (type: string) => { if (type === 'technical') { return Technisch } return Organisatorisch } const calculateCategoryScore = (categoryToms: TOM[]) => { if (categoryToms.length === 0) return 0 const total = categoryToms.length const implemented = categoryToms.filter(t => t.implementation_status === 'implemented' || t.implementation_status === 'verified').length const inProgress = categoryToms.filter(t => t.implementation_status === 'in_progress').length return Math.round(((implemented + inProgress * 0.5) / total) * 100) } const calculateOverallScore = () => { if (toms.length === 0) return 0 let total = toms.length let score = 0 toms.forEach(t => { if (t.implementation_status === 'implemented' || t.implementation_status === 'verified') score += 1 else if (t.implementation_status === 'in_progress') score += 0.5 }) return Math.round((score / total) * 100) } if (loading) { return (
Lade TOMs...
) } return (
{error && (
{error}
)} {/* Header Actions */}
{/* Overall Score */}

TOM-Umsetzungsgrad

Gesamtfortschritt aller technischen und organisatorischen Maßnahmen

= 80 ? 'text-green-600' : calculateOverallScore() >= 50 ? 'text-yellow-600' : 'text-red-600'}`}> {calculateOverallScore()}%
{toms.length} Maßnahmen
= 80 ? 'bg-green-500' : calculateOverallScore() >= 50 ? 'bg-yellow-500' : 'bg-red-500'}`} style={{ width: `${calculateOverallScore()}%` }} />
{/* TOM Categories */} {categoryGroups.length === 0 ? (
🔒

Keine Maßnahmen erfasst

Legen Sie technische und organisatorische Maßnahmen an.

) : (
{categoryGroups.map((category) => (
{expandedCategory === category.id && (
{category.toms.length === 0 ? (
Keine Maßnahmen in dieser Kategorie
) : ( category.toms.map((tom) => (

{tom.name}

{getTypeBadge(tom.type)}
{getStatusBadge(tom.implementation_status)}

{tom.description}

{tom.documentation && ( Nachweis: {tom.documentation} )} {tom.responsible_person && ( Verantwortlich: {tom.responsible_person} )} {tom.responsible_department && ( Abteilung: {tom.responsible_department} )} {tom.last_review_at && ( Letzte Prüfung: {new Date(tom.last_review_at).toLocaleDateString('de-DE')} )} {tom.review_frequency && ( Prüfung: {tom.review_frequency === 'monthly' ? 'Monatlich' : tom.review_frequency === 'quarterly' ? 'Quartalsweise' : 'Jährlich'} )}
{tom.effectiveness_rating && (
Wirksamkeit: {tom.effectiveness_rating === 'high' ? 'Hoch' : tom.effectiveness_rating === 'medium' ? 'Mittel' : 'Niedrig'}
)}
)) )}
)}
))}
)} {/* Info */}

Dokumentationspflicht

Gemäß Art. 32 Abs. 1 DSGVO müssen geeignete technische und organisatorische Maßnahmen implementiert werden, um ein dem Risiko angemessenes Schutzniveau zu gewährleisten. Diese Dokumentation dient als Nachweis für Aufsichtsbehörden.

{/* Create Modal */} {showCreateModal && (

Neue Maßnahme anlegen

setNewTom({ ...newTom, name: e.target.value })} className="w-full border border-slate-300 rounded-lg px-3 py-2" placeholder="z.B. TLS 1.3 für alle Verbindungen" />