fix(admin-v2): Restore complete admin-v2 application

The admin-v2 application was incomplete in the repository. This commit
restores all missing components:

- Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education,
  infrastructure, communication, development, onboarding, rbac
- SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen,
  vendor-compliance, tom-generator, dsr, and more
- Developer portal (25 pages): API docs, SDK guides, frameworks
- All components, lib files, hooks, and types
- Updated package.json with all dependencies

The issue was caused by incomplete initial repository state - the full
admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2
but was never fully synced to the main admin-v2 directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
BreakPilot Dev
2026-02-08 23:40:15 -08:00
parent f28244753f
commit 660295e218
385 changed files with 138126 additions and 3079 deletions

664
admin-v2/lib/sdk/dsr/api.ts Normal file
View File

@@ -0,0 +1,664 @@
/**
* DSR API Client
*
* API client for Data Subject Request management
* Connects to the Go Consent Service backend
*/
import {
DSRRequest,
DSRListResponse,
DSRFilters,
DSRCreateRequest,
DSRUpdateRequest,
DSRVerifyIdentityRequest,
DSRCompleteRequest,
DSRRejectRequest,
DSRExtendDeadlineRequest,
DSRSendCommunicationRequest,
DSRCommunication,
DSRAuditEntry,
DSRStatistics,
DSRDataExport,
DSRErasureChecklist
} from './types'
// =============================================================================
// CONFIGURATION
// =============================================================================
const DSR_API_BASE = process.env.NEXT_PUBLIC_CONSENT_SERVICE_URL || 'http://localhost:8081'
const API_TIMEOUT = 30000 // 30 seconds
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
function getTenantId(): string {
// In a real app, this would come from auth context or localStorage
if (typeof window !== 'undefined') {
return localStorage.getItem('tenantId') || 'default-tenant'
}
return 'default-tenant'
}
function getAuthHeaders(): HeadersInit {
const headers: HeadersInit = {
'Content-Type': 'application/json',
'X-Tenant-ID': getTenantId()
}
// Add auth token if available
if (typeof window !== 'undefined') {
const token = localStorage.getItem('authToken')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
}
return headers
}
async function fetchWithTimeout<T>(
url: string,
options: RequestInit = {},
timeout: number = API_TIMEOUT
): Promise<T> {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
headers: {
...getAuthHeaders(),
...options.headers
}
})
if (!response.ok) {
const errorBody = await response.text()
let errorMessage = `HTTP ${response.status}: ${response.statusText}`
try {
const errorJson = JSON.parse(errorBody)
errorMessage = errorJson.error || errorJson.message || errorMessage
} catch {
// Keep the HTTP status message
}
throw new Error(errorMessage)
}
// Handle empty responses
const contentType = response.headers.get('content-type')
if (contentType && contentType.includes('application/json')) {
return response.json()
}
return {} as T
} finally {
clearTimeout(timeoutId)
}
}
// =============================================================================
// DSR LIST & CRUD
// =============================================================================
/**
* Fetch all DSR requests with optional filters
*/
export async function fetchDSRList(filters?: DSRFilters): Promise<DSRListResponse> {
const params = new URLSearchParams()
if (filters) {
if (filters.status) {
const statuses = Array.isArray(filters.status) ? filters.status : [filters.status]
statuses.forEach(s => params.append('status', s))
}
if (filters.type) {
const types = Array.isArray(filters.type) ? filters.type : [filters.type]
types.forEach(t => params.append('type', t))
}
if (filters.priority) params.set('priority', filters.priority)
if (filters.assignedTo) params.set('assignedTo', filters.assignedTo)
if (filters.overdue !== undefined) params.set('overdue', String(filters.overdue))
if (filters.search) params.set('search', filters.search)
if (filters.dateFrom) params.set('dateFrom', filters.dateFrom)
if (filters.dateTo) params.set('dateTo', filters.dateTo)
}
const queryString = params.toString()
const url = `${DSR_API_BASE}/api/v1/admin/dsr${queryString ? `?${queryString}` : ''}`
return fetchWithTimeout<DSRListResponse>(url)
}
/**
* Fetch a single DSR request by ID
*/
export async function fetchDSR(id: string): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`)
}
/**
* Create a new DSR request
*/
export async function createDSR(request: DSRCreateRequest): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(`${DSR_API_BASE}/api/v1/admin/dsr`, {
method: 'POST',
body: JSON.stringify(request)
})
}
/**
* Update a DSR request
*/
export async function updateDSR(id: string, update: DSRUpdateRequest): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`, {
method: 'PUT',
body: JSON.stringify(update)
})
}
/**
* Delete a DSR request (soft delete - marks as cancelled)
*/
export async function deleteDSR(id: string): Promise<void> {
await fetchWithTimeout<void>(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`, {
method: 'DELETE'
})
}
// =============================================================================
// DSR WORKFLOW ACTIONS
// =============================================================================
/**
* Verify the identity of the requester
*/
export async function verifyIdentity(
dsrId: string,
verification: DSRVerifyIdentityRequest
): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/verify-identity`,
{
method: 'POST',
body: JSON.stringify(verification)
}
)
}
/**
* Complete a DSR request
*/
export async function completeDSR(
dsrId: string,
completion?: DSRCompleteRequest
): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/complete`,
{
method: 'POST',
body: JSON.stringify(completion || {})
}
)
}
/**
* Reject a DSR request
*/
export async function rejectDSR(
dsrId: string,
rejection: DSRRejectRequest
): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/reject`,
{
method: 'POST',
body: JSON.stringify(rejection)
}
)
}
/**
* Extend the deadline for a DSR request
*/
export async function extendDeadline(
dsrId: string,
extension: DSRExtendDeadlineRequest
): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/extend`,
{
method: 'POST',
body: JSON.stringify(extension)
}
)
}
/**
* Assign a DSR request to a user
*/
export async function assignDSR(
dsrId: string,
assignedTo: string
): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/assign`,
{
method: 'POST',
body: JSON.stringify({ assignedTo })
}
)
}
// =============================================================================
// COMMUNICATION
// =============================================================================
/**
* Get all communications for a DSR request
*/
export async function getCommunications(dsrId: string): Promise<DSRCommunication[]> {
return fetchWithTimeout<DSRCommunication[]>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/communications`
)
}
/**
* Send a communication (email, letter, internal note)
*/
export async function sendCommunication(
dsrId: string,
communication: DSRSendCommunicationRequest
): Promise<DSRCommunication> {
return fetchWithTimeout<DSRCommunication>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/send-communication`,
{
method: 'POST',
body: JSON.stringify(communication)
}
)
}
// =============================================================================
// AUDIT LOG
// =============================================================================
/**
* Get audit log entries for a DSR request
*/
export async function getAuditLog(dsrId: string): Promise<DSRAuditEntry[]> {
return fetchWithTimeout<DSRAuditEntry[]>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/audit`
)
}
// =============================================================================
// STATISTICS
// =============================================================================
/**
* Get DSR statistics
*/
export async function getDSRStatistics(): Promise<DSRStatistics> {
return fetchWithTimeout<DSRStatistics>(
`${DSR_API_BASE}/api/v1/admin/dsr/statistics`
)
}
// =============================================================================
// DATA EXPORT (Art. 15, 20)
// =============================================================================
/**
* Generate data export for Art. 15 (access) or Art. 20 (portability)
*/
export async function generateDataExport(
dsrId: string,
format: 'json' | 'csv' | 'xml' | 'pdf' = 'json'
): Promise<DSRDataExport> {
return fetchWithTimeout<DSRDataExport>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/export`,
{
method: 'POST',
body: JSON.stringify({ format })
}
)
}
/**
* Download generated data export
*/
export async function downloadDataExport(dsrId: string): Promise<Blob> {
const response = await fetch(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/export/download`,
{
headers: getAuthHeaders()
}
)
if (!response.ok) {
throw new Error(`Download failed: ${response.statusText}`)
}
return response.blob()
}
// =============================================================================
// ERASURE CHECKLIST (Art. 17)
// =============================================================================
/**
* Get the erasure checklist for an Art. 17 request
*/
export async function getErasureChecklist(dsrId: string): Promise<DSRErasureChecklist> {
return fetchWithTimeout<DSRErasureChecklist>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/erasure-checklist`
)
}
/**
* Update the erasure checklist
*/
export async function updateErasureChecklist(
dsrId: string,
checklist: DSRErasureChecklist
): Promise<DSRErasureChecklist> {
return fetchWithTimeout<DSRErasureChecklist>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/erasure-checklist`,
{
method: 'PUT',
body: JSON.stringify(checklist)
}
)
}
// =============================================================================
// EMAIL TEMPLATES
// =============================================================================
/**
* Get available email templates
*/
export async function getEmailTemplates(): Promise<{ id: string; name: string; stage: string }[]> {
return fetchWithTimeout<{ id: string; name: string; stage: string }[]>(
`${DSR_API_BASE}/api/v1/admin/dsr/email-templates`
)
}
/**
* Preview an email with variables filled in
*/
export async function previewEmail(
templateId: string,
dsrId: string
): Promise<{ subject: string; body: string }> {
return fetchWithTimeout<{ subject: string; body: string }>(
`${DSR_API_BASE}/api/v1/admin/dsr/email-templates/${templateId}/preview`,
{
method: 'POST',
body: JSON.stringify({ dsrId })
}
)
}
// =============================================================================
// MOCK DATA FUNCTIONS (for development without backend)
// =============================================================================
export function createMockDSRList(): DSRRequest[] {
const now = new Date()
return [
{
id: 'dsr-001',
referenceNumber: 'DSR-2025-000001',
type: 'access',
status: 'intake',
priority: 'high',
requester: {
name: 'Max Mustermann',
email: 'max.mustermann@example.de'
},
source: 'web_form',
sourceDetails: 'Kontaktformular auf breakpilot.de',
receivedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() + 28 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() + 28 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
identityVerification: {
verified: false
},
assignment: {
assignedTo: null
},
createdAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
},
{
id: 'dsr-002',
referenceNumber: 'DSR-2025-000002',
type: 'erasure',
status: 'identity_verification',
priority: 'high',
requester: {
name: 'Anna Schmidt',
email: 'anna.schmidt@example.de',
phone: '+49 170 1234567'
},
source: 'email',
requestText: 'Ich moechte, dass alle meine Daten geloescht werden.',
receivedAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() + 9 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() + 9 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
identityVerification: {
verified: false
},
assignment: {
assignedTo: 'DSB Mueller',
assignedAt: new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000).toISOString()
},
createdAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
},
{
id: 'dsr-003',
referenceNumber: 'DSR-2025-000003',
type: 'rectification',
status: 'processing',
priority: 'normal',
requester: {
name: 'Peter Meier',
email: 'peter.meier@example.de'
},
source: 'email',
requestText: 'Meine Adresse ist falsch gespeichert.',
receivedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
identityVerification: {
verified: true,
method: 'existing_account',
verifiedAt: new Date(now.getTime() - 6 * 24 * 60 * 60 * 1000).toISOString(),
verifiedBy: 'DSB Mueller'
},
assignment: {
assignedTo: 'DSB Mueller',
assignedAt: new Date(now.getTime() - 6 * 24 * 60 * 60 * 1000).toISOString()
},
rectificationDetails: {
fieldsToCorrect: [
{
field: 'Adresse',
currentValue: 'Musterstr. 1, 12345 Berlin',
requestedValue: 'Musterstr. 10, 12345 Berlin',
corrected: false
}
]
},
createdAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
},
{
id: 'dsr-004',
referenceNumber: 'DSR-2025-000004',
type: 'portability',
status: 'processing',
priority: 'normal',
requester: {
name: 'Lisa Weber',
email: 'lisa.weber@example.de'
},
source: 'web_form',
receivedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() + 20 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() + 20 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
identityVerification: {
verified: true,
method: 'id_document',
verifiedAt: new Date(now.getTime() - 8 * 24 * 60 * 60 * 1000).toISOString(),
verifiedBy: 'DSB Mueller'
},
assignment: {
assignedTo: 'IT Team',
assignedAt: new Date(now.getTime() - 8 * 24 * 60 * 60 * 1000).toISOString()
},
notes: 'JSON-Export wird vorbereitet',
createdAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
},
{
id: 'dsr-005',
referenceNumber: 'DSR-2025-000005',
type: 'objection',
status: 'rejected',
priority: 'low',
requester: {
name: 'Thomas Klein',
email: 'thomas.klein@example.de'
},
source: 'letter',
requestText: 'Ich widerspreche der Verarbeitung meiner Daten fuer Marketingzwecke.',
receivedAt: new Date(now.getTime() - 35 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
completedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
identityVerification: {
verified: true,
method: 'postal',
verifiedAt: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString(),
verifiedBy: 'DSB Mueller'
},
assignment: {
assignedTo: 'Rechtsabteilung',
assignedAt: new Date(now.getTime() - 28 * 24 * 60 * 60 * 1000).toISOString()
},
objectionDetails: {
processingPurpose: 'Marketing',
legalBasis: 'Berechtigtes Interesse (Art. 6(1)(f))',
objectionGrounds: 'Keine konkreten Gruende genannt',
decision: 'rejected',
decisionReason: 'Zwingende schutzwuerdige Gruende fuer die Verarbeitung ueberwiegen',
decisionBy: 'Rechtsabteilung',
decisionAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString()
},
notes: 'Widerspruch unberechtigt - zwingende schutzwuerdige Gruende',
createdAt: new Date(now.getTime() - 35 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
},
{
id: 'dsr-006',
referenceNumber: 'DSR-2025-000006',
type: 'access',
status: 'completed',
priority: 'normal',
requester: {
name: 'Sarah Braun',
email: 'sarah.braun@example.de'
},
source: 'email',
receivedAt: new Date(now.getTime() - 45 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
completedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(),
identityVerification: {
verified: true,
method: 'id_document',
verifiedAt: new Date(now.getTime() - 42 * 24 * 60 * 60 * 1000).toISOString(),
verifiedBy: 'DSB Mueller'
},
assignment: {
assignedTo: 'DSB Mueller',
assignedAt: new Date(now.getTime() - 42 * 24 * 60 * 60 * 1000).toISOString()
},
dataExport: {
format: 'pdf',
generatedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(),
generatedBy: 'DSB Mueller',
fileName: 'datenauskunft_sarah_braun.pdf',
fileSize: 245000,
includesThirdPartyData: false
},
createdAt: new Date(now.getTime() - 45 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
}
]
}
export function createMockStatistics(): DSRStatistics {
return {
total: 6,
byStatus: {
intake: 1,
identity_verification: 1,
processing: 2,
completed: 1,
rejected: 1,
cancelled: 0
},
byType: {
access: 2,
rectification: 1,
erasure: 1,
restriction: 0,
portability: 1,
objection: 1
},
overdue: 0,
dueThisWeek: 2,
averageProcessingDays: 18,
completedThisMonth: 1
}
}

View File

@@ -0,0 +1,6 @@
/**
* DSR Module Exports
*/
export * from './types'
export * from './api'

View File

@@ -0,0 +1,581 @@
/**
* DSR (Data Subject Request) Types
*
* TypeScript definitions for GDPR Art. 15-21 Data Subject Requests
* Based on the Go Consent Service backend API structure
*/
// =============================================================================
// ENUMS & CONSTANTS
// =============================================================================
export type DSRType =
| 'access' // Art. 15 - Auskunftsrecht
| 'rectification' // Art. 16 - Berichtigungsrecht
| 'erasure' // Art. 17 - Loeschungsrecht
| 'restriction' // Art. 18 - Einschraenkungsrecht
| 'portability' // Art. 20 - Datenuebertragbarkeit
| 'objection' // Art. 21 - Widerspruchsrecht
export type DSRStatus =
| 'intake' // Eingang - Anfrage dokumentiert
| 'identity_verification' // Identitaetspruefung
| 'processing' // In Bearbeitung
| 'completed' // Abgeschlossen
| 'rejected' // Abgelehnt
| 'cancelled' // Storniert
export type DSRPriority = 'low' | 'normal' | 'high' | 'critical'
export type DSRSource =
| 'web_form' // Kontaktformular/Portal
| 'email' // E-Mail
| 'letter' // Brief
| 'phone' // Telefon
| 'in_person' // Persoenlich
| 'other' // Sonstiges
export type IdentityVerificationMethod =
| 'id_document' // Ausweiskopie
| 'email' // E-Mail-Bestaetigung
| 'phone' // Telefonische Bestaetigung
| 'postal' // Postalische Bestaetigung
| 'existing_account' // Bestehendes Kundenkonto
| 'other' // Sonstiges
export type CommunicationType =
| 'incoming' // Eingehend (vom Betroffenen)
| 'outgoing' // Ausgehend (an Betroffenen)
| 'internal' // Intern (Notizen)
export type CommunicationChannel =
| 'email'
| 'letter'
| 'phone'
| 'portal'
| 'internal_note'
// =============================================================================
// DSR TYPE METADATA
// =============================================================================
export interface DSRTypeInfo {
type: DSRType
article: string
label: string
labelShort: string
description: string
defaultDeadlineDays: number
maxExtensionMonths: number
color: string
bgColor: string
processDocument?: string // Reference to process document
}
export const DSR_TYPE_INFO: Record<DSRType, DSRTypeInfo> = {
access: {
type: 'access',
article: 'Art. 15',
label: 'Auskunftsrecht',
labelShort: 'Auskunft',
description: 'Recht auf Auskunft ueber gespeicherte personenbezogene Daten',
defaultDeadlineDays: 30,
maxExtensionMonths: 2,
color: 'text-blue-700',
bgColor: 'bg-blue-100',
processDocument: 'Prozessbeschreibung Art. 15 DSGVO_v02.pdf'
},
rectification: {
type: 'rectification',
article: 'Art. 16',
label: 'Berichtigungsrecht',
labelShort: 'Berichtigung',
description: 'Recht auf Berichtigung unrichtiger personenbezogener Daten',
defaultDeadlineDays: 14,
maxExtensionMonths: 2,
color: 'text-yellow-700',
bgColor: 'bg-yellow-100',
processDocument: 'Prozessbeschriebung Art. 16 DSGVO_v02.pdf'
},
erasure: {
type: 'erasure',
article: 'Art. 17',
label: 'Loeschungsrecht',
labelShort: 'Loeschung',
description: 'Recht auf Loeschung personenbezogener Daten ("Recht auf Vergessenwerden")',
defaultDeadlineDays: 14,
maxExtensionMonths: 2,
color: 'text-red-700',
bgColor: 'bg-red-100',
processDocument: 'Prozessbeschreibung Art. 17 DSGVO_v03.pdf'
},
restriction: {
type: 'restriction',
article: 'Art. 18',
label: 'Einschraenkungsrecht',
labelShort: 'Einschraenkung',
description: 'Recht auf Einschraenkung der Verarbeitung',
defaultDeadlineDays: 14,
maxExtensionMonths: 2,
color: 'text-orange-700',
bgColor: 'bg-orange-100',
processDocument: 'Prozessbeschreibung Art. 18 DSGVO_v01.pdf'
},
portability: {
type: 'portability',
article: 'Art. 20',
label: 'Datenuebertragbarkeit',
labelShort: 'Uebertragung',
description: 'Recht auf Datenuebertragbarkeit in maschinenlesbarem Format',
defaultDeadlineDays: 30,
maxExtensionMonths: 2,
color: 'text-purple-700',
bgColor: 'bg-purple-100',
processDocument: 'Prozessbeschreibung Art. 20 DSGVO_v02.pdf'
},
objection: {
type: 'objection',
article: 'Art. 21',
label: 'Widerspruchsrecht',
labelShort: 'Widerspruch',
description: 'Recht auf Widerspruch gegen die Verarbeitung',
defaultDeadlineDays: 30,
maxExtensionMonths: 0, // No extension allowed for objections
color: 'text-gray-700',
bgColor: 'bg-gray-100'
}
}
export const DSR_STATUS_INFO: Record<DSRStatus, { label: string; color: string; bgColor: string; borderColor: string }> = {
intake: {
label: 'Eingang',
color: 'text-blue-700',
bgColor: 'bg-blue-100',
borderColor: 'border-blue-200'
},
identity_verification: {
label: 'ID-Pruefung',
color: 'text-yellow-700',
bgColor: 'bg-yellow-100',
borderColor: 'border-yellow-200'
},
processing: {
label: 'In Bearbeitung',
color: 'text-purple-700',
bgColor: 'bg-purple-100',
borderColor: 'border-purple-200'
},
completed: {
label: 'Abgeschlossen',
color: 'text-green-700',
bgColor: 'bg-green-100',
borderColor: 'border-green-200'
},
rejected: {
label: 'Abgelehnt',
color: 'text-red-700',
bgColor: 'bg-red-100',
borderColor: 'border-red-200'
},
cancelled: {
label: 'Storniert',
color: 'text-gray-700',
bgColor: 'bg-gray-100',
borderColor: 'border-gray-200'
}
}
// =============================================================================
// MAIN INTERFACES
// =============================================================================
export interface DSRRequester {
name: string
email: string
phone?: string
address?: string
customerId?: string // If existing customer
}
export interface DSRIdentityVerification {
verified: boolean
method?: IdentityVerificationMethod
verifiedAt?: string
verifiedBy?: string
notes?: string
documentRef?: string // Reference to uploaded ID document
}
export interface DSRAssignment {
assignedTo: string | null
assignedAt?: string
assignedBy?: string
}
export interface DSRDeadline {
originalDeadline: string
currentDeadline: string
extended: boolean
extensionReason?: string
extensionApprovedBy?: string
extensionApprovedAt?: string
}
export interface DSRRequest {
id: string
referenceNumber: string // e.g., "DSR-2025-000042"
type: DSRType
status: DSRStatus
priority: DSRPriority
// Requester info
requester: DSRRequester
// Request details
source: DSRSource
sourceDetails?: string // e.g., "Kontaktformular auf website.de"
requestText?: string // Original request text
// Dates
receivedAt: string
deadline: DSRDeadline
completedAt?: string
// Verification
identityVerification: DSRIdentityVerification
// Assignment
assignment: DSRAssignment
// Processing
notes?: string
internalNotes?: string
// Type-specific data
erasureChecklist?: DSRErasureChecklist // For Art. 17
dataExport?: DSRDataExport // For Art. 15, 20
rectificationDetails?: DSRRectificationDetails // For Art. 16
objectionDetails?: DSRObjectionDetails // For Art. 21
// Audit
createdAt: string
createdBy: string
updatedAt: string
updatedBy?: string
// Metadata
tenantId: string
}
// =============================================================================
// TYPE-SPECIFIC INTERFACES
// =============================================================================
// Art. 17(3) Erasure Exceptions Checklist
export interface DSRErasureChecklistItem {
id: string
article: string // e.g., "17(3)(a)"
label: string
description: string
checked: boolean
applies: boolean
notes?: string
}
export interface DSRErasureChecklist {
items: DSRErasureChecklistItem[]
canProceedWithErasure: boolean
reviewedBy?: string
reviewedAt?: string
}
export const ERASURE_EXCEPTIONS: Omit<DSRErasureChecklistItem, 'checked' | 'applies' | 'notes'>[] = [
{
id: 'art17_3_a',
article: '17(3)(a)',
label: 'Meinungs- und Informationsfreiheit',
description: 'Ausuebung des Rechts auf freie Meinungsaeusserung und Information'
},
{
id: 'art17_3_b',
article: '17(3)(b)',
label: 'Rechtliche Verpflichtung',
description: 'Erfuellung einer rechtlichen Verpflichtung (z.B. Aufbewahrungspflichten)'
},
{
id: 'art17_3_c',
article: '17(3)(c)',
label: 'Oeffentliches Interesse',
description: 'Gruende des oeffentlichen Interesses im Bereich Gesundheit'
},
{
id: 'art17_3_d',
article: '17(3)(d)',
label: 'Archivzwecke',
description: 'Archivzwecke, wissenschaftliche/historische Forschung, Statistik'
},
{
id: 'art17_3_e',
article: '17(3)(e)',
label: 'Rechtsansprueche',
description: 'Geltendmachung, Ausuebung oder Verteidigung von Rechtsanspruechen'
}
]
// Data Export for Art. 15, 20
export interface DSRDataExport {
format: 'json' | 'csv' | 'xml' | 'pdf'
generatedAt?: string
generatedBy?: string
fileUrl?: string
fileName?: string
fileSize?: number
includesThirdPartyData: boolean
anonymizedFields?: string[]
transferMethod?: 'download' | 'email' | 'third_party' // For Art. 20 transfer
transferRecipient?: string // For Art. 20 transfer to another controller
}
// Rectification Details for Art. 16
export interface DSRRectificationDetails {
fieldsToCorrect: {
field: string
currentValue: string
requestedValue: string
corrected: boolean
correctedAt?: string
correctedBy?: string
}[]
}
// Objection Details for Art. 21
export interface DSRObjectionDetails {
processingPurpose: string
legalBasis: string
objectionGrounds: string
decision: 'accepted' | 'rejected' | 'pending'
decisionReason?: string
decisionBy?: string
decisionAt?: string
}
// =============================================================================
// COMMUNICATION
// =============================================================================
export interface DSRCommunication {
id: string
dsrId: string
type: CommunicationType
channel: CommunicationChannel
subject?: string
content: string
templateUsed?: string // Reference to email template
attachments?: {
name: string
url: string
size: number
type: string
}[]
sentAt?: string
sentBy?: string
receivedAt?: string
createdAt: string
createdBy: string
}
// =============================================================================
// AUDIT LOG
// =============================================================================
export interface DSRAuditEntry {
id: string
dsrId: string
action: string // e.g., "status_changed", "identity_verified", "assigned"
previousValue?: string
newValue?: string
performedBy: string
performedAt: string
notes?: string
}
// =============================================================================
// EMAIL TEMPLATES
// =============================================================================
export interface DSREmailTemplate {
id: string
name: string
subject: string
body: string
type: DSRType | 'general'
stage: DSRStatus | 'identity_request' | 'deadline_extension' | 'completion'
language: 'de' | 'en'
variables: string[] // e.g., ["requesterName", "referenceNumber", "deadline"]
}
export const DSR_EMAIL_TEMPLATES: DSREmailTemplate[] = [
{
id: 'intake_confirmation',
name: 'Eingangsbestaetigung',
subject: 'Bestaetigung Ihrer Anfrage - {{referenceNumber}}',
body: `Sehr geehrte(r) {{requesterName}},
wir bestaetigen den Eingang Ihrer Anfrage vom {{receivedDate}}.
Referenznummer: {{referenceNumber}}
Art der Anfrage: {{requestType}}
Wir werden Ihre Anfrage innerhalb der gesetzlichen Frist von {{deadline}} bearbeiten.
Mit freundlichen Gruessen
{{senderName}}
Datenschutzbeauftragter`,
type: 'general',
stage: 'intake',
language: 'de',
variables: ['requesterName', 'receivedDate', 'referenceNumber', 'requestType', 'deadline', 'senderName']
},
{
id: 'identity_request',
name: 'Identitaetsanfrage',
subject: 'Identitaetspruefung erforderlich - {{referenceNumber}}',
body: `Sehr geehrte(r) {{requesterName}},
um Ihre Anfrage bearbeiten zu koennen, benoetigen wir einen Nachweis Ihrer Identitaet.
Bitte senden Sie uns eines der folgenden Dokumente:
- Kopie Ihres Personalausweises (Vorder- und Rueckseite)
- Kopie Ihres Reisepasses
Ihre Daten werden ausschliesslich zur Identitaetspruefung verwendet und anschliessend geloescht.
Mit freundlichen Gruessen
{{senderName}}
Datenschutzbeauftragter`,
type: 'general',
stage: 'identity_request',
language: 'de',
variables: ['requesterName', 'referenceNumber', 'senderName']
}
]
// =============================================================================
// API TYPES
// =============================================================================
export interface DSRFilters {
status?: DSRStatus | DSRStatus[]
type?: DSRType | DSRType[]
priority?: DSRPriority
assignedTo?: string
overdue?: boolean
search?: string
dateFrom?: string
dateTo?: string
}
export interface DSRListResponse {
requests: DSRRequest[]
total: number
page: number
pageSize: number
}
export interface DSRCreateRequest {
type: DSRType
requester: DSRRequester
source: DSRSource
sourceDetails?: string
requestText?: string
priority?: DSRPriority
}
export interface DSRUpdateRequest {
status?: DSRStatus
priority?: DSRPriority
notes?: string
internalNotes?: string
assignment?: DSRAssignment
}
export interface DSRVerifyIdentityRequest {
method: IdentityVerificationMethod
notes?: string
documentRef?: string
}
export interface DSRCompleteRequest {
completionNotes?: string
dataExport?: DSRDataExport
}
export interface DSRRejectRequest {
reason: string
legalBasis?: string // e.g., Art. 17(3) exception
}
export interface DSRExtendDeadlineRequest {
extensionMonths: 1 | 2
reason: string
}
export interface DSRSendCommunicationRequest {
type: CommunicationType
channel: CommunicationChannel
subject?: string
content: string
templateId?: string
}
// =============================================================================
// STATISTICS
// =============================================================================
export interface DSRStatistics {
total: number
byStatus: Record<DSRStatus, number>
byType: Record<DSRType, number>
overdue: number
dueThisWeek: number
averageProcessingDays: number
completedThisMonth: number
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
export function getDaysRemaining(deadline: string): number {
const deadlineDate = new Date(deadline)
const now = new Date()
const diff = deadlineDate.getTime() - now.getTime()
return Math.ceil(diff / (1000 * 60 * 60 * 24))
}
export function isOverdue(request: DSRRequest): boolean {
if (request.status === 'completed' || request.status === 'rejected' || request.status === 'cancelled') {
return false
}
return getDaysRemaining(request.deadline.currentDeadline) < 0
}
export function isUrgent(request: DSRRequest, thresholdDays: number = 7): boolean {
if (request.status === 'completed' || request.status === 'rejected' || request.status === 'cancelled') {
return false
}
const daysRemaining = getDaysRemaining(request.deadline.currentDeadline)
return daysRemaining >= 0 && daysRemaining <= thresholdDays
}
export function generateReferenceNumber(year: number, sequence: number): string {
return `DSR-${year}-${String(sequence).padStart(6, '0')}`
}
export function getTypeInfo(type: DSRType): DSRTypeInfo {
return DSR_TYPE_INFO[type]
}
export function getStatusInfo(status: DSRStatus) {
return DSR_STATUS_INFO[status]
}