'use client' import React, { useState, useEffect } 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 { 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 // ============================================================================= 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) } } if (isLoading) { return (
) } if (!request) { return (

Anfrage nicht gefunden

Die angeforderte DSR-Anfrage existiert nicht oder wurde geloescht.

Zurueck zur Uebersicht
) } const typeInfo = DSR_TYPE_INFO[request.type] const overdue = isOverdue(request) const urgent = isUrgent(request) return (
{/* Header */}
{request.referenceNumber} {typeInfo.article} {typeInfo.label}

{request.requester.name}

{/* Workflow Stepper */}
{/* Main Content: 2/3 + 1/3 Layout */}
{/* Left Column - 2/3 */}
{/* Content Tabs */}
{/* 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}
)}
)} {/* 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 */}
{/* Identity Modal */} setShowIdentityModal(false)} onVerify={handleVerifyIdentity} requesterName={request.requester.name} requesterEmail={request.requester.email} />
) }