diff --git a/admin-compliance/app/sdk/vendor-compliance/layout.tsx b/admin-compliance/app/sdk/vendor-compliance/layout.tsx index cdaac9a..013d896 100644 --- a/admin-compliance/app/sdk/vendor-compliance/layout.tsx +++ b/admin-compliance/app/sdk/vendor-compliance/layout.tsx @@ -48,6 +48,15 @@ const navItems: NavItem[] = [ ), }, + { + href: '/sdk/vendor-compliance/transfers', + label: 'Drittlandtransfers', + icon: ( + + + + ), + }, { href: '/sdk/vendor-compliance/risks', label: 'Risiken', diff --git a/admin-compliance/app/sdk/vendor-compliance/transfers/page.tsx b/admin-compliance/app/sdk/vendor-compliance/transfers/page.tsx new file mode 100644 index 0000000..2b6609b --- /dev/null +++ b/admin-compliance/app/sdk/vendor-compliance/transfers/page.tsx @@ -0,0 +1,251 @@ +'use client' + +import { useMemo, useState } from 'react' +import { useVendorCompliance } from '@/lib/sdk/vendor-compliance' +import Link from 'next/link' + +// ============================================================================ +// Types +// ============================================================================ + +interface TransferEntry { + vendorId: string + vendorName: string + country: string + isEU: boolean + isAdequate: boolean + mechanisms: string[] + hasSCC: boolean + hasTIA: boolean + status: 'green' | 'yellow' | 'red' + statusLabel: string +} + +// ============================================================================ +// Helpers +// ============================================================================ + +function getTransferStatus( + isEU: boolean, + isAdequate: boolean, + mechanisms: string[], + hasSCC: boolean, +): { status: 'green' | 'yellow' | 'red'; label: string } { + if (isEU) return { status: 'green', label: 'EU/EWR' } + if (isAdequate) return { status: 'green', label: 'Angemessenheitsbeschluss' } + if (hasSCC && mechanisms.length > 0) return { status: 'yellow', label: 'SCC vorhanden' } + if (mechanisms.length > 0) return { status: 'yellow', label: 'Mechanismus vorhanden' } + return { status: 'red', label: 'Kein Transfermechanismus' } +} + +const statusColors = { + green: 'bg-green-100 text-green-800', + yellow: 'bg-amber-100 text-amber-800', + red: 'bg-red-100 text-red-800', +} + +const statusDots = { + green: 'bg-green-500', + yellow: 'bg-amber-500', + red: 'bg-red-500', +} + +// ============================================================================ +// Component +// ============================================================================ + +export default function TransfersPage() { + const { vendors, contracts } = useVendorCompliance() + const [filter, setFilter] = useState<'all' | 'green' | 'yellow' | 'red'>('all') + + // Build transfer entries from vendors with non-EU processing locations + const transfers = useMemo(() => { + if (!vendors) return [] + + const entries: TransferEntry[] = [] + + for (const vendor of vendors) { + const locations = (vendor as Record).processingLocations as Array<{ + country: string; isEU: boolean; isAdequate: boolean + }> || [] + const mechanisms = (vendor as Record).transferMechanisms as string[] || [] + + // Check if vendor has any SCC contract + const vendorContracts = (contracts || []).filter( + (c: Record) => c.vendorId === vendor.id + ) + const hasSCC = vendorContracts.some( + (c: Record) => c.documentType === 'SCC' + ) + + for (const loc of locations) { + const { status, label } = getTransferStatus(loc.isEU, loc.isAdequate, mechanisms, hasSCC) + + entries.push({ + vendorId: vendor.id, + vendorName: vendor.name, + country: loc.country, + isEU: loc.isEU, + isAdequate: loc.isAdequate, + mechanisms, + hasSCC, + hasTIA: false, // TODO: Check if TIA document exists for this vendor + status, + statusLabel: label, + }) + } + } + + return entries + }, [vendors, contracts]) + + // Filter + const filtered = filter === 'all' + ? transfers + : transfers.filter((t) => t.status === filter) + + // Stats + const stats = useMemo(() => ({ + total: transfers.length, + eu: transfers.filter((t) => t.isEU).length, + adequate: transfers.filter((t) => !t.isEU && t.isAdequate).length, + thirdCountry: transfers.filter((t) => !t.isEU && !t.isAdequate).length, + red: transfers.filter((t) => t.status === 'red').length, + }), [transfers]) + + return ( +
+
+

Drittlandtransfers

+

+ Uebersicht aller Datenuebermittlungen nach Verarbeitungsstandort. Art. 44-49 DSGVO. +

+
+ + {/* Stats */} +
+
+
Gesamt
+
{stats.total}
+
+
+
EU/EWR + Adequat
+
{stats.eu + stats.adequate}
+
+
+
Drittland (mit Mechanismus)
+
{stats.thirdCountry - stats.red}
+
+
+
Handlungsbedarf
+
{stats.red}
+
+
+ + {/* Filter */} +
+ {(['all', 'green', 'yellow', 'red'] as const).map((f) => ( + + ))} +
+ + {/* Table */} +
+ + + + + + + + + + + + + + {filtered.length === 0 ? ( + + + + ) : ( + filtered.map((t, i) => ( + + + + + + + + + + )) + )} + +
StatusVendorLandMechanismusSCCTIAAktionen
+ {transfers.length === 0 + ? 'Keine Vendors mit Verarbeitungsstandorten erfasst.' + : 'Keine Eintraege fuer den gewaehlten Filter.'} +
+ + + {t.statusLabel} + + + + {t.vendorName} + + + {t.country} + {t.isEU && (EU)} + + {t.mechanisms.length > 0 + ? t.mechanisms.join(', ') + : Keiner} + + {t.hasSCC + ? Vorhanden + : -} + + {t.hasTIA + ? Vorhanden + : !t.isEU && !t.isAdequate + ? Fehlt + : -} + + {!t.isEU && !t.isAdequate && ( + + TIA erstellen + + )} +
+
+ + {/* Help text */} +
+ Hinweis: Fuer Datenuebermittlungen in Drittlaender ohne Angemessenheitsbeschluss sind + EU-Standardvertragsklauseln (SCC) und ein Transfer Impact Assessment (TIA) erforderlich (EuGH Schrems II, Art. 46 DSGVO). + Templates fuer SCC und TIA finden Sie im Document Generator unter der Kategorie "Drittlandtransfer". +
+
+ ) +}