diff --git a/admin-compliance/app/sdk/dsr/[requestId]/_components/ActionButtons.tsx b/admin-compliance/app/sdk/dsr/[requestId]/_components/ActionButtons.tsx
new file mode 100644
index 0000000..9521b37
--- /dev/null
+++ b/admin-compliance/app/sdk/dsr/[requestId]/_components/ActionButtons.tsx
@@ -0,0 +1,75 @@
+'use client'
+
+import React from 'react'
+import { DSRRequest } from '@/lib/sdk/dsr/types'
+
+export function ActionButtons({
+ request,
+ onVerifyIdentity,
+ onExtendDeadline,
+ onComplete,
+ onReject,
+ onAssign
+}: {
+ request: DSRRequest
+ onVerifyIdentity: () => void
+ onExtendDeadline: () => void
+ onComplete: () => void
+ onReject: () => void
+ onAssign: () => void
+}) {
+ const isTerminal = request.status === 'completed' || request.status === 'rejected' || request.status === 'cancelled'
+
+ if (isTerminal) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+ {!request.identityVerification.verified && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/dsr/[requestId]/_components/AuditLog.tsx b/admin-compliance/app/sdk/dsr/[requestId]/_components/AuditLog.tsx
new file mode 100644
index 0000000..f4b0266
--- /dev/null
+++ b/admin-compliance/app/sdk/dsr/[requestId]/_components/AuditLog.tsx
@@ -0,0 +1,44 @@
+'use client'
+
+import React from 'react'
+
+export function AuditLog({ history }: { history: any[] }) {
+ return (
+
+
Aktivitaeten
+
+ {history.length === 0 && (
+
Keine Eintraege
+ )}
+ {history.map((entry, idx) => (
+
+
+
+
+ {entry.previous_status
+ ? `${entry.previous_status} → ${entry.new_status}`
+ : entry.new_status
+ }
+ {entry.comment && `: ${entry.comment}`}
+
+
+ {entry.created_at
+ ? new Date(entry.created_at).toLocaleDateString('de-DE', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+ })
+ : ''
+ }
+ {' - '}
+ {entry.changed_by || 'System'}
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRDetailsTab.tsx b/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRDetailsTab.tsx
new file mode 100644
index 0000000..f05035a
--- /dev/null
+++ b/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRDetailsTab.tsx
@@ -0,0 +1,120 @@
+'use client'
+
+import React from 'react'
+import { DSRRequest } from '@/lib/sdk/dsr/types'
+
+export function DSRDetailsTab({
+ request,
+ onShowIdentityModal,
+}: {
+ request: DSRRequest
+ onShowIdentityModal: () => void
+}) {
+ return (
+
+ {/* Request Info */}
+
+
+
Antragsteller
+
+
{request.requester.name}
+
{request.requester.email}
+ {request.requester.phone && (
+
{request.requester.phone}
+ )}
+
+
+
+
Eingereicht
+
+
+ {new Date(request.receivedAt).toLocaleDateString('de-DE', {
+ day: '2-digit',
+ month: 'long',
+ year: 'numeric'
+ })}
+
+
+ Quelle: {request.source === 'web_form' ? 'Kontaktformular' :
+ request.source === 'email' ? 'E-Mail' :
+ request.source === 'letter' ? 'Brief' :
+ request.source === 'phone' ? 'Telefon' : request.source}
+
+
+
+
+
+ {/* Identity Verification */}
+
+
+
+ {request.identityVerification.verified ? (
+
+ ) : (
+
+ )}
+
+
+
+ {request.identityVerification.verified
+ ? 'Identitaet verifiziert'
+ : 'Identitaetspruefung ausstehend'
+ }
+
+ {request.identityVerification.verified && (
+
+ Methode: {request.identityVerification.method === 'id_document' ? 'Ausweisdokument' :
+ request.identityVerification.method === 'email' ? 'E-Mail' :
+ request.identityVerification.method === 'existing_account' ? 'Bestehendes Konto' :
+ request.identityVerification.method}
+ {' | '}
+ {new Date(request.identityVerification.verifiedAt!).toLocaleDateString('de-DE')}
+
+ )}
+
+ {!request.identityVerification.verified && (
+
+ )}
+
+
+
+ {/* Request Text */}
+ {request.requestText && (
+
+
Anfragetext
+
+ {request.requestText}
+
+
+ )}
+
+ {/* Notes */}
+ {request.notes && (
+
+
Notizen
+
+ {request.notes}
+
+
+ )}
+
+ )
+}
diff --git a/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRHeader.tsx b/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRHeader.tsx
new file mode 100644
index 0000000..988ae94
--- /dev/null
+++ b/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRHeader.tsx
@@ -0,0 +1,41 @@
+'use client'
+
+import React from 'react'
+import Link from 'next/link'
+import { DSRRequest, DSR_TYPE_INFO } from '@/lib/sdk/dsr/types'
+
+export function DSRHeader({ request }: { request: DSRRequest }) {
+ const typeInfo = DSR_TYPE_INFO[request.type]
+
+ return (
+
+
+
+
+
+
+
+ {request.referenceNumber}
+
+ {typeInfo.article} {typeInfo.label}
+
+
+
+ {request.requester.name}
+
+
+
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRSidebar.tsx b/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRSidebar.tsx
new file mode 100644
index 0000000..fa89d04
--- /dev/null
+++ b/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRSidebar.tsx
@@ -0,0 +1,85 @@
+'use client'
+
+import React from 'react'
+import { DSRRequest } from '@/lib/sdk/dsr/types'
+import { StatusBadge } from './StatusBadge'
+import { DeadlineDisplay } from './DeadlineDisplay'
+import { ActionButtons } from './ActionButtons'
+import { AuditLog } from './AuditLog'
+
+export function DSRSidebar({
+ request,
+ history,
+ onVerifyIdentity,
+ onExtendDeadline,
+ onComplete,
+ onReject,
+ onAssign,
+}: {
+ request: DSRRequest
+ history: any[]
+ onVerifyIdentity: () => void
+ onExtendDeadline: () => void
+ onComplete: () => void
+ onReject: () => void
+ onAssign: () => void
+}) {
+ return (
+
+ {/* Status Card */}
+
+
+
Status
+
+
+
+
+
+
+
+ {/* Priority */}
+
+
Prioritaet
+
+ {request.priority === 'critical' ? 'Kritisch' :
+ request.priority === 'high' ? 'Hoch' :
+ request.priority === 'normal' ? 'Normal' : 'Niedrig'}
+
+
+
+ {/* Assignment */}
+
+
Zugewiesen an
+
+ {request.assignment.assignedTo || 'Nicht zugewiesen'}
+
+
+
+
+ {/* Actions Card */}
+
+
+ {/* Audit Log Card */}
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRTypeSpecificTab.tsx b/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRTypeSpecificTab.tsx
new file mode 100644
index 0000000..699c4b4
--- /dev/null
+++ b/admin-compliance/app/sdk/dsr/[requestId]/_components/DSRTypeSpecificTab.tsx
@@ -0,0 +1,183 @@
+'use client'
+
+import React from 'react'
+import { DSRRequest } from '@/lib/sdk/dsr/types'
+import {
+ DSRErasureChecklistComponent,
+ DSRDataExportComponent
+} from '@/components/sdk/dsr'
+
+export function DSRTypeSpecificTab({
+ request,
+ setRequest,
+ exceptionChecks,
+ onExceptionCheckChange,
+}: {
+ request: DSRRequest
+ setRequest: (r: DSRRequest) => void
+ exceptionChecks: any[]
+ onExceptionCheckChange: (checkId: string, applies: boolean, notes?: string) => void
+}) {
+ return (
+
+ {/* Art. 17 - Erasure */}
+ {request.type === 'erasure' && (
+
+
setRequest({ ...request, erasureChecklist: checklist })}
+ />
+
+ {/* Art. 17(3) Exception Checks from Backend */}
+ {exceptionChecks.length > 0 && (
+
+
Art. 17(3) Ausnahmepruefung
+
+ Pruefen Sie, ob eine der gesetzlichen Ausnahmen zur Loeschpflicht greift.
+
+
+ {exceptionChecks.map((check) => (
+
+
+
onExceptionCheckChange(check.id, e.target.checked, check.notes)}
+ className="mt-1 h-4 w-4 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
+ />
+
+
+ {check.article}: {check.label}
+
+
{check.description}
+ {check.checked_by && (
+
+ Geprueft von {check.checked_by} am{' '}
+ {check.checked_at ? new Date(check.checked_at).toLocaleDateString('de-DE') : '-'}
+
+ )}
+
+
+
+ ))}
+
+
+ )}
+
+ )}
+
+ {/* Art. 15/20 - Data Export */}
+ {(request.type === 'access' || request.type === 'portability') && (
+
{
+ // Mock generation
+ setRequest({
+ ...request,
+ dataExport: {
+ format,
+ generatedAt: new Date().toISOString(),
+ generatedBy: 'Current User',
+ fileName: `datenexport_${request.referenceNumber}.${format}`,
+ fileSize: 125000,
+ includesThirdPartyData: true
+ }
+ })
+ }}
+ />
+ )}
+
+ {/* Art. 16 - Rectification */}
+ {request.type === 'rectification' && request.rectificationDetails && (
+
+
Zu korrigierende Daten
+
+ {request.rectificationDetails.fieldsToCorrect.map((field, idx) => (
+
+
{field.field}
+
+
+
Aktueller Wert
+
{field.currentValue}
+
+
+
Angeforderter Wert
+
{field.requestedValue}
+
+
+ {field.corrected && (
+
+ Korrigiert am {new Date(field.correctedAt!).toLocaleDateString('de-DE')}
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {/* Art. 21 - Objection */}
+ {request.type === 'objection' && request.objectionDetails && (
+
+
Widerspruchsdetails
+
+
+
Verarbeitungszweck
+
{request.objectionDetails.processingPurpose}
+
+
+
Rechtsgrundlage
+
{request.objectionDetails.legalBasis}
+
+
+
+
Widerspruchsgruende
+
{request.objectionDetails.objectionGrounds}
+
+ {request.objectionDetails.decision !== 'pending' && (
+
+
+ Widerspruch {request.objectionDetails.decision === 'accepted' ? 'angenommen' : 'abgelehnt'}
+
+ {request.objectionDetails.decisionReason && (
+
+ {request.objectionDetails.decisionReason}
+
+ )}
+
+ )}
+
+ )}
+
+ {/* Default for restriction */}
+ {request.type === 'restriction' && (
+
+
+
+
+
Einschraenkung der Verarbeitung
+
+ Markieren Sie die betroffenen Daten im System als eingeschraenkt.
+ Die Daten duerfen nur noch gespeichert, aber nicht mehr verarbeitet werden.
+
+
+
+
+ )}
+
+ )
+}
diff --git a/admin-compliance/app/sdk/dsr/[requestId]/_components/DeadlineDisplay.tsx b/admin-compliance/app/sdk/dsr/[requestId]/_components/DeadlineDisplay.tsx
new file mode 100644
index 0000000..495452d
--- /dev/null
+++ b/admin-compliance/app/sdk/dsr/[requestId]/_components/DeadlineDisplay.tsx
@@ -0,0 +1,45 @@
+'use client'
+
+import React from 'react'
+import { DSRRequest, getDaysRemaining, isOverdue, isUrgent } from '@/lib/sdk/dsr/types'
+
+export function DeadlineDisplay({ request }: { request: DSRRequest }) {
+ const daysRemaining = getDaysRemaining(request.deadline.currentDeadline)
+ const overdue = isOverdue(request)
+ const urgent = isUrgent(request)
+ const isTerminal = request.status === 'completed' || request.status === 'rejected' || request.status === 'cancelled'
+
+ if (isTerminal) {
+ return (
+
+
Abgeschlossen am
+
+ {request.completedAt
+ ? new Date(request.completedAt).toLocaleDateString('de-DE')
+ : '-'
+ }
+
+
+ )
+ }
+
+ return (
+
+
Frist
+
+ {overdue
+ ? `${Math.abs(daysRemaining)} Tage ueberfaellig`
+ : `${daysRemaining} Tage`
+ }
+
+
+ bis {new Date(request.deadline.currentDeadline).toLocaleDateString('de-DE')}
+
+ {request.deadline.extended && (
+
+ (Verlaengert)
+
+ )}
+
+ )
+}
diff --git a/admin-compliance/app/sdk/dsr/[requestId]/_components/StatusBadge.tsx b/admin-compliance/app/sdk/dsr/[requestId]/_components/StatusBadge.tsx
new file mode 100644
index 0000000..a51bb9e
--- /dev/null
+++ b/admin-compliance/app/sdk/dsr/[requestId]/_components/StatusBadge.tsx
@@ -0,0 +1,15 @@
+'use client'
+
+import React from 'react'
+import { DSR_STATUS_INFO } from '@/lib/sdk/dsr/types'
+
+export function StatusBadge({ status }: { status: string }) {
+ const info = DSR_STATUS_INFO[status as keyof typeof DSR_STATUS_INFO]
+ if (!info) return null
+
+ return (
+
+ {info.label}
+
+ )
+}
diff --git a/admin-compliance/app/sdk/dsr/[requestId]/_hooks/useDSRDetail.ts b/admin-compliance/app/sdk/dsr/[requestId]/_hooks/useDSRDetail.ts
new file mode 100644
index 0000000..6ee3132
--- /dev/null
+++ b/admin-compliance/app/sdk/dsr/[requestId]/_hooks/useDSRDetail.ts
@@ -0,0 +1,204 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import {
+ DSRRequest,
+ DSRCommunication,
+ DSRVerifyIdentityRequest,
+} from '@/lib/sdk/dsr/types'
+import {
+ fetchSDKDSR,
+ verifyDSRIdentity,
+ assignDSR,
+ extendDSRDeadline,
+ completeDSR,
+ rejectDSR,
+ fetchDSRCommunications,
+ sendDSRCommunication,
+ fetchDSRExceptionChecks,
+ initDSRExceptionChecks,
+ updateDSRExceptionCheck,
+ fetchDSRHistory,
+} from '@/lib/sdk/dsr/api'
+
+export function useDSRDetail(requestId: string) {
+ const [request, setRequest] = useState(null)
+ const [communications, setCommunications] = useState([])
+ const [history, setHistory] = useState([])
+ const [exceptionChecks, setExceptionChecks] = useState([])
+ const [isLoading, setIsLoading] = useState(true)
+
+ // Load data from SDK backend
+ useEffect(() => {
+ const loadData = async () => {
+ setIsLoading(true)
+ try {
+ const found = await fetchSDKDSR(requestId)
+ if (found) {
+ setRequest(found)
+ // Load communications, history, and exception checks in parallel
+ const [comms, hist] = await Promise.all([
+ fetchDSRCommunications(requestId).catch(() => []),
+ fetchDSRHistory(requestId).catch(() => []),
+ ])
+ setCommunications(comms.map((c: any) => ({
+ id: c.id,
+ dsrId: c.dsr_id,
+ type: c.communication_type,
+ channel: c.channel,
+ subject: c.subject,
+ content: c.content,
+ createdAt: c.created_at,
+ createdBy: c.created_by,
+ sentAt: c.sent_at,
+ sentBy: c.sent_by,
+ })))
+ setHistory(hist)
+
+ // Load exception checks for erasure requests
+ if (found.type === 'erasure') {
+ try {
+ const checks = await fetchDSRExceptionChecks(requestId)
+ if (checks.length === 0) {
+ // Auto-initialize if none exist
+ const initialized = await initDSRExceptionChecks(requestId)
+ setExceptionChecks(initialized)
+ } else {
+ setExceptionChecks(checks)
+ }
+ } catch {
+ setExceptionChecks([])
+ }
+ }
+ }
+ } catch (error) {
+ console.error('Failed to load DSR:', error)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+ loadData()
+ }, [requestId])
+
+ const handleVerifyIdentity = async (verification: DSRVerifyIdentityRequest) => {
+ if (!request) return
+ try {
+ const updated = await verifyDSRIdentity(request.id, {
+ method: verification.method,
+ notes: verification.notes,
+ document_ref: verification.documentRef,
+ })
+ setRequest(updated)
+ fetchDSRHistory(requestId).then(setHistory).catch(() => {})
+ } catch (err) {
+ console.error('Failed to verify identity:', err)
+ }
+ }
+
+ const handleAssign = async () => {
+ if (!request) return
+ const assignee = prompt('Zuweisen an (Name/ID):')
+ if (!assignee) return
+ try {
+ const updated = await assignDSR(request.id, assignee)
+ setRequest(updated)
+ fetchDSRHistory(requestId).then(setHistory).catch(() => {})
+ } catch (err) {
+ console.error('Failed to assign:', err)
+ }
+ }
+
+ const handleExtendDeadline = async () => {
+ if (!request) return
+ const reason = prompt('Grund fuer die Fristverlaengerung:')
+ if (!reason) return
+ const daysStr = prompt('Um wie viele Tage verlaengern? (Standard: 60)', '60')
+ const days = parseInt(daysStr || '60', 10) || 60
+ try {
+ const updated = await extendDSRDeadline(request.id, reason, days)
+ setRequest(updated)
+ fetchDSRHistory(requestId).then(setHistory).catch(() => {})
+ } catch (err) {
+ console.error('Failed to extend deadline:', err)
+ }
+ }
+
+ const handleComplete = async () => {
+ if (!request) return
+ const summary = prompt('Zusammenfassung der Bearbeitung:')
+ if (summary === null) return
+ try {
+ const updated = await completeDSR(request.id, summary || undefined)
+ setRequest(updated)
+ fetchDSRHistory(requestId).then(setHistory).catch(() => {})
+ } catch (err) {
+ console.error('Failed to complete:', err)
+ }
+ }
+
+ const handleReject = async () => {
+ if (!request) return
+ const reason = prompt('Ablehnungsgrund:')
+ if (!reason) return
+ const legalBasis = prompt('Rechtsgrundlage (optional):')
+ try {
+ const updated = await rejectDSR(request.id, reason, legalBasis || undefined)
+ setRequest(updated)
+ fetchDSRHistory(requestId).then(setHistory).catch(() => {})
+ } catch (err) {
+ console.error('Failed to reject:', err)
+ }
+ }
+
+ const handleSendCommunication = async (message: any) => {
+ if (!request) return
+ try {
+ const result = await sendDSRCommunication(requestId, {
+ communication_type: message.type || 'outgoing',
+ channel: message.channel || 'email',
+ subject: message.subject,
+ content: message.content,
+ })
+ const newComm: DSRCommunication = {
+ id: result.id,
+ dsrId: result.dsr_id,
+ type: result.communication_type,
+ channel: result.channel,
+ subject: result.subject,
+ content: result.content,
+ createdAt: result.created_at,
+ createdBy: result.created_by || 'Current User',
+ sentAt: result.sent_at,
+ sentBy: result.sent_by,
+ }
+ setCommunications(prev => [newComm, ...prev])
+ } catch (err) {
+ console.error('Failed to send communication:', err)
+ }
+ }
+
+ const handleExceptionCheckChange = async (checkId: string, applies: boolean, notes?: string) => {
+ try {
+ const updated = await updateDSRExceptionCheck(requestId, checkId, { applies, notes })
+ setExceptionChecks(prev => prev.map(c => c.id === checkId ? updated : c))
+ } catch (err) {
+ console.error('Failed to update exception check:', err)
+ }
+ }
+
+ return {
+ request,
+ setRequest,
+ communications,
+ history,
+ exceptionChecks,
+ isLoading,
+ handleVerifyIdentity,
+ handleAssign,
+ handleExtendDeadline,
+ handleComplete,
+ handleReject,
+ handleSendCommunication,
+ handleExceptionCheckChange,
+ }
+}
diff --git a/admin-compliance/app/sdk/dsr/[requestId]/page.tsx b/admin-compliance/app/sdk/dsr/[requestId]/page.tsx
index 9d6721d..dd461a4 100644
--- a/admin-compliance/app/sdk/dsr/[requestId]/page.tsx
+++ b/admin-compliance/app/sdk/dsr/[requestId]/page.tsx
@@ -1,390 +1,42 @@
'use client'
-import React, { useState, useEffect } from 'react'
+import React, { useState } from 'react'
import Link from 'next/link'
-import { useParams, useRouter } from 'next/navigation'
-import {
- DSRRequest,
- DSR_TYPE_INFO,
- DSR_STATUS_INFO,
- getDaysRemaining,
- isOverdue,
- isUrgent,
- DSRCommunication,
- DSRVerifyIdentityRequest
-} from '@/lib/sdk/dsr/types'
-import {
- fetchSDKDSR,
- updateSDKDSRStatus,
- verifyDSRIdentity,
- assignDSR,
- extendDSRDeadline,
- completeDSR,
- rejectDSR,
- fetchDSRCommunications,
- sendDSRCommunication,
- fetchDSRExceptionChecks,
- initDSRExceptionChecks,
- updateDSRExceptionCheck,
- fetchDSRHistory,
-} from '@/lib/sdk/dsr/api'
+import { useParams } from 'next/navigation'
+import { DSR_TYPE_INFO, isOverdue, isUrgent } from '@/lib/sdk/dsr/types'
import {
DSRWorkflowStepper,
DSRIdentityModal,
DSRCommunicationLog,
- DSRErasureChecklistComponent,
- DSRDataExportComponent
} from '@/components/sdk/dsr'
-
-// =============================================================================
-// COMPONENTS
-// =============================================================================
-
-function StatusBadge({ status }: { status: string }) {
- const info = DSR_STATUS_INFO[status as keyof typeof DSR_STATUS_INFO]
- if (!info) return null
-
- return (
-
- {info.label}
-
- )
-}
-
-function DeadlineDisplay({ request }: { request: DSRRequest }) {
- const daysRemaining = getDaysRemaining(request.deadline.currentDeadline)
- const overdue = isOverdue(request)
- const urgent = isUrgent(request)
- const isTerminal = request.status === 'completed' || request.status === 'rejected' || request.status === 'cancelled'
-
- if (isTerminal) {
- return (
-
-
Abgeschlossen am
-
- {request.completedAt
- ? new Date(request.completedAt).toLocaleDateString('de-DE')
- : '-'
- }
-
-
- )
- }
-
- return (
-
-
Frist
-
- {overdue
- ? `${Math.abs(daysRemaining)} Tage ueberfaellig`
- : `${daysRemaining} Tage`
- }
-
-
- bis {new Date(request.deadline.currentDeadline).toLocaleDateString('de-DE')}
-
- {request.deadline.extended && (
-
- (Verlaengert)
-
- )}
-
- )
-}
-
-function ActionButtons({
- request,
- onVerifyIdentity,
- onExtendDeadline,
- onComplete,
- onReject,
- onAssign
-}: {
- request: DSRRequest
- onVerifyIdentity: () => void
- onExtendDeadline: () => void
- onComplete: () => void
- onReject: () => void
- onAssign: () => void
-}) {
- const isTerminal = request.status === 'completed' || request.status === 'rejected' || request.status === 'cancelled'
-
- if (isTerminal) {
- return (
-
-
-
- )
- }
-
- return (
-
- {!request.identityVerification.verified && (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-function AuditLog({ history }: { history: any[] }) {
- return (
-
-
Aktivitaeten
-
- {history.length === 0 && (
-
Keine Eintraege
- )}
- {history.map((entry, idx) => (
-
-
-
-
- {entry.previous_status
- ? `${entry.previous_status} → ${entry.new_status}`
- : entry.new_status
- }
- {entry.comment && `: ${entry.comment}`}
-
-
- {entry.created_at
- ? new Date(entry.created_at).toLocaleDateString('de-DE', {
- day: '2-digit',
- month: '2-digit',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit'
- })
- : ''
- }
- {' - '}
- {entry.changed_by || 'System'}
-
-
-
- ))}
-
-
- )
-}
-
-// =============================================================================
-// MAIN PAGE
-// =============================================================================
+import { DSRHeader } from './_components/DSRHeader'
+import { DSRDetailsTab } from './_components/DSRDetailsTab'
+import { DSRTypeSpecificTab } from './_components/DSRTypeSpecificTab'
+import { DSRSidebar } from './_components/DSRSidebar'
+import { useDSRDetail } from './_hooks/useDSRDetail'
export default function DSRDetailPage() {
const params = useParams()
- const router = useRouter()
const requestId = params.requestId as string
- const [request, setRequest] = useState(null)
- const [communications, setCommunications] = useState([])
- const [history, setHistory] = useState([])
- const [exceptionChecks, setExceptionChecks] = useState([])
- const [isLoading, setIsLoading] = useState(true)
const [showIdentityModal, setShowIdentityModal] = useState(false)
const [activeContentTab, setActiveContentTab] = useState<'details' | 'communication' | 'type-specific'>('details')
- const reloadRequest = async () => {
- const found = await fetchSDKDSR(requestId)
- if (found) setRequest(found)
- }
-
- // Load data from SDK backend
- useEffect(() => {
- const loadData = async () => {
- setIsLoading(true)
- try {
- const found = await fetchSDKDSR(requestId)
- if (found) {
- setRequest(found)
- // Load communications, history, and exception checks in parallel
- const [comms, hist] = await Promise.all([
- fetchDSRCommunications(requestId).catch(() => []),
- fetchDSRHistory(requestId).catch(() => []),
- ])
- setCommunications(comms.map((c: any) => ({
- id: c.id,
- dsrId: c.dsr_id,
- type: c.communication_type,
- channel: c.channel,
- subject: c.subject,
- content: c.content,
- createdAt: c.created_at,
- createdBy: c.created_by,
- sentAt: c.sent_at,
- sentBy: c.sent_by,
- })))
- setHistory(hist)
-
- // Load exception checks for erasure requests
- if (found.type === 'erasure') {
- try {
- const checks = await fetchDSRExceptionChecks(requestId)
- if (checks.length === 0) {
- // Auto-initialize if none exist
- const initialized = await initDSRExceptionChecks(requestId)
- setExceptionChecks(initialized)
- } else {
- setExceptionChecks(checks)
- }
- } catch {
- setExceptionChecks([])
- }
- }
- }
- } catch (error) {
- console.error('Failed to load DSR:', error)
- } finally {
- setIsLoading(false)
- }
- }
- loadData()
- }, [requestId])
-
- const handleVerifyIdentity = async (verification: DSRVerifyIdentityRequest) => {
- if (!request) return
- try {
- const updated = await verifyDSRIdentity(request.id, {
- method: verification.method,
- notes: verification.notes,
- document_ref: verification.documentRef,
- })
- setRequest(updated)
- // Reload history
- fetchDSRHistory(requestId).then(setHistory).catch(() => {})
- } catch (err) {
- console.error('Failed to verify identity:', err)
- }
- }
-
- const handleAssign = async () => {
- if (!request) return
- const assignee = prompt('Zuweisen an (Name/ID):')
- if (!assignee) return
- try {
- const updated = await assignDSR(request.id, assignee)
- setRequest(updated)
- fetchDSRHistory(requestId).then(setHistory).catch(() => {})
- } catch (err) {
- console.error('Failed to assign:', err)
- }
- }
-
- const handleExtendDeadline = async () => {
- if (!request) return
- const reason = prompt('Grund fuer die Fristverlaengerung:')
- if (!reason) return
- const daysStr = prompt('Um wie viele Tage verlaengern? (Standard: 60)', '60')
- const days = parseInt(daysStr || '60', 10) || 60
- try {
- const updated = await extendDSRDeadline(request.id, reason, days)
- setRequest(updated)
- fetchDSRHistory(requestId).then(setHistory).catch(() => {})
- } catch (err) {
- console.error('Failed to extend deadline:', err)
- }
- }
-
- const handleComplete = async () => {
- if (!request) return
- const summary = prompt('Zusammenfassung der Bearbeitung:')
- if (summary === null) return
- try {
- const updated = await completeDSR(request.id, summary || undefined)
- setRequest(updated)
- fetchDSRHistory(requestId).then(setHistory).catch(() => {})
- } catch (err) {
- console.error('Failed to complete:', err)
- }
- }
-
- const handleReject = async () => {
- if (!request) return
- const reason = prompt('Ablehnungsgrund:')
- if (!reason) return
- const legalBasis = prompt('Rechtsgrundlage (optional):')
- try {
- const updated = await rejectDSR(request.id, reason, legalBasis || undefined)
- setRequest(updated)
- fetchDSRHistory(requestId).then(setHistory).catch(() => {})
- } catch (err) {
- console.error('Failed to reject:', err)
- }
- }
-
- const handleSendCommunication = async (message: any) => {
- if (!request) return
- try {
- const result = await sendDSRCommunication(requestId, {
- communication_type: message.type || 'outgoing',
- channel: message.channel || 'email',
- subject: message.subject,
- content: message.content,
- })
- // Map backend response to frontend format
- const newComm: DSRCommunication = {
- id: result.id,
- dsrId: result.dsr_id,
- type: result.communication_type,
- channel: result.channel,
- subject: result.subject,
- content: result.content,
- createdAt: result.created_at,
- createdBy: result.created_by || 'Current User',
- sentAt: result.sent_at,
- sentBy: result.sent_by,
- }
- setCommunications(prev => [newComm, ...prev])
- } catch (err) {
- console.error('Failed to send communication:', err)
- }
- }
-
- const handleExceptionCheckChange = async (checkId: string, applies: boolean, notes?: string) => {
- try {
- const updated = await updateDSRExceptionCheck(requestId, checkId, { applies, notes })
- setExceptionChecks(prev => prev.map(c => c.id === checkId ? updated : c))
- } catch (err) {
- console.error('Failed to update exception check:', err)
- }
- }
+ const {
+ request,
+ setRequest,
+ communications,
+ history,
+ exceptionChecks,
+ isLoading,
+ handleVerifyIdentity,
+ handleAssign,
+ handleExtendDeadline,
+ handleComplete,
+ handleReject,
+ handleSendCommunication,
+ handleExceptionCheckChange,
+ } = useDSRDetail(requestId)
if (isLoading) {
return (
@@ -428,36 +80,7 @@ export default function DSRDetailPage() {
return (
- {/* Header */}
-
-
-
-
-
-
-
- {request.referenceNumber}
-
- {typeInfo.article} {typeInfo.label}
-
-
-
- {request.requester.name}
-
-
-
-
-
+
{/* Workflow Stepper */}
- {/* Details Tab */}
{activeContentTab === 'details' && (
-
- {/* Request Info */}
-
-
-
Antragsteller
-
-
{request.requester.name}
-
{request.requester.email}
- {request.requester.phone && (
-
{request.requester.phone}
- )}
-
-
-
-
Eingereicht
-
-
- {new Date(request.receivedAt).toLocaleDateString('de-DE', {
- day: '2-digit',
- month: 'long',
- year: 'numeric'
- })}
-
-
- Quelle: {request.source === 'web_form' ? 'Kontaktformular' :
- request.source === 'email' ? 'E-Mail' :
- request.source === 'letter' ? 'Brief' :
- request.source === 'phone' ? 'Telefon' : request.source}
-
-
-
-
-
- {/* Identity Verification */}
-
-
-
- {request.identityVerification.verified ? (
-
- ) : (
-
- )}
-
-
-
- {request.identityVerification.verified
- ? 'Identitaet verifiziert'
- : 'Identitaetspruefung ausstehend'
- }
-
- {request.identityVerification.verified && (
-
- Methode: {request.identityVerification.method === 'id_document' ? 'Ausweisdokument' :
- request.identityVerification.method === 'email' ? 'E-Mail' :
- request.identityVerification.method === 'existing_account' ? 'Bestehendes Konto' :
- request.identityVerification.method}
- {' | '}
- {new Date(request.identityVerification.verifiedAt!).toLocaleDateString('de-DE')}
-
- )}
-
- {!request.identityVerification.verified && (
-
- )}
-
-
-
- {/* Request Text */}
- {request.requestText && (
-
-
Anfragetext
-
- {request.requestText}
-
-
- )}
-
- {/* Notes */}
- {request.notes && (
-
-
Notizen
-
- {request.notes}
-
-
- )}
-
+
setShowIdentityModal(true)}
+ />
)}
- {/* Communication Tab */}
{activeContentTab === 'communication' && (
)}
- {/* Type-Specific Tab */}
{activeContentTab === 'type-specific' && (
-
- {/* Art. 17 - Erasure */}
- {request.type === 'erasure' && (
-
-
setRequest({ ...request, erasureChecklist: checklist })}
- />
-
- {/* Art. 17(3) Exception Checks from Backend */}
- {exceptionChecks.length > 0 && (
-
-
Art. 17(3) Ausnahmepruefung
-
- Pruefen Sie, ob eine der gesetzlichen Ausnahmen zur Loeschpflicht greift.
-
-
- {exceptionChecks.map((check) => (
-
-
-
handleExceptionCheckChange(check.id, e.target.checked, check.notes)}
- className="mt-1 h-4 w-4 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
- />
-
-
- {check.article}: {check.label}
-
-
{check.description}
- {check.checked_by && (
-
- Geprueft von {check.checked_by} am{' '}
- {check.checked_at ? new Date(check.checked_at).toLocaleDateString('de-DE') : '-'}
-
- )}
-
-
-
- ))}
-
-
- )}
-
- )}
-
- {/* Art. 15/20 - Data Export */}
- {(request.type === 'access' || request.type === 'portability') && (
-
{
- // Mock generation
- setRequest({
- ...request,
- dataExport: {
- format,
- generatedAt: new Date().toISOString(),
- generatedBy: 'Current User',
- fileName: `datenexport_${request.referenceNumber}.${format}`,
- fileSize: 125000,
- includesThirdPartyData: true
- }
- })
- }}
- />
- )}
-
- {/* Art. 16 - Rectification */}
- {request.type === 'rectification' && request.rectificationDetails && (
-
-
Zu korrigierende Daten
-
- {request.rectificationDetails.fieldsToCorrect.map((field, idx) => (
-
-
{field.field}
-
-
-
Aktueller Wert
-
{field.currentValue}
-
-
-
Angeforderter Wert
-
{field.requestedValue}
-
-
- {field.corrected && (
-
- Korrigiert am {new Date(field.correctedAt!).toLocaleDateString('de-DE')}
-
- )}
-
- ))}
-
-
- )}
-
- {/* Art. 21 - Objection */}
- {request.type === 'objection' && request.objectionDetails && (
-
-
Widerspruchsdetails
-
-
-
Verarbeitungszweck
-
{request.objectionDetails.processingPurpose}
-
-
-
Rechtsgrundlage
-
{request.objectionDetails.legalBasis}
-
-
-
-
Widerspruchsgruende
-
{request.objectionDetails.objectionGrounds}
-
- {request.objectionDetails.decision !== 'pending' && (
-
-
- Widerspruch {request.objectionDetails.decision === 'accepted' ? 'angenommen' : 'abgelehnt'}
-
- {request.objectionDetails.decisionReason && (
-
- {request.objectionDetails.decisionReason}
-
- )}
-
- )}
-
- )}
-
- {/* Default for restriction */}
- {request.type === 'restriction' && (
-
-
-
-
-
Einschraenkung der Verarbeitung
-
- Markieren Sie die betroffenen Daten im System als eingeschraenkt.
- Die Daten duerfen nur noch gespeichert, aber nicht mehr verarbeitet werden.
-
-
-
-
- )}
-
+
)}
{/* Right Column - 1/3 Sidebar */}
-
- {/* Status Card */}
-
-
-
Status
-
-
-
-
-
-
-
- {/* Priority */}
-
-
Prioritaet
-
- {request.priority === 'critical' ? 'Kritisch' :
- request.priority === 'high' ? 'Hoch' :
- request.priority === 'normal' ? 'Normal' : 'Niedrig'}
-
-
-
- {/* Assignment */}
-
-
Zugewiesen an
-
- {request.assignment.assignedTo || 'Nicht zugewiesen'}
-
-
-
-
- {/* Actions Card */}
-
-
Aktionen
-
setShowIdentityModal(true)}
- onExtendDeadline={handleExtendDeadline}
- onComplete={handleComplete}
- onReject={handleReject}
- onAssign={handleAssign}
- />
-
-
- {/* Audit Log Card */}
-
-
+ setShowIdentityModal(true)}
+ onExtendDeadline={handleExtendDeadline}
+ onComplete={handleComplete}
+ onReject={handleReject}
+ onAssign={handleAssign}
+ />
{/* Identity Modal */}