This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/admin-v2/app/(sdk)/sdk/dsr/[requestId]/page.tsx
Benjamin Admin 4ed290ccf3 feat(sdk): Replace mock data with real API calls in consent, DSR, and consent-management
- /sdk/consent: Replace hardcoded mockDocuments with GET /api/admin/consent/documents
- /sdk/dsr: Replace createMockDSRList with fetchSDKDSRList via /api/sdk/v1/dsgvo/dsr
- /sdk/dsr/new: Replace console.log mock with real POST to create DSR requests
- /sdk/dsr/[requestId]: Replace mock lookup with real GET/PUT for DSR details and status updates
- /sdk/consent-management: Add real stats, GDPR process counts, and email template editor
- lib/sdk/dsr/api.ts: Add transformBackendDSR adapter (flat backend → nested frontend types)

Prepares for removal of /dsgvo and /compliance pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 12:47:19 +01:00

750 lines
30 KiB
TypeScript

'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 } from '@/lib/sdk/dsr/api'
import {
DSRWorkflowStepper,
DSRIdentityModal,
DSRCommunicationLog,
DSRErasureChecklistComponent,
DSRDataExportComponent
} from '@/components/sdk/dsr'
// =============================================================================
// MOCK COMMUNICATIONS
// =============================================================================
const mockCommunications: DSRCommunication[] = [
{
id: 'comm-001',
dsrId: 'dsr-001',
type: 'outgoing',
channel: 'email',
subject: 'Eingangsbestaetigung Ihrer Anfrage',
content: 'Sehr geehrte(r) Antragsteller(in),\n\nwir bestaetigen den Eingang Ihrer Anfrage und werden diese innerhalb der gesetzlichen Frist bearbeiten.\n\nMit freundlichen Gruessen\nIhr Datenschutz-Team',
sentAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
sentBy: 'System',
createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'System'
},
{
id: 'comm-002',
dsrId: 'dsr-001',
type: 'outgoing',
channel: 'email',
subject: 'Identitaetspruefung erforderlich',
content: 'Sehr geehrte(r) Antragsteller(in),\n\nbitte senden Sie uns zur Bearbeitung Ihrer Anfrage einen Identitaetsnachweis zu.\n\nMit freundlichen Gruessen',
sentAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
sentBy: 'DSB Mueller',
createdAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'DSB Mueller'
}
]
// =============================================================================
// COMPONENTS
// =============================================================================
function StatusBadge({ status }: { status: string }) {
const info = DSR_STATUS_INFO[status as keyof typeof DSR_STATUS_INFO]
if (!info) return null
return (
<span className={`px-3 py-1.5 text-sm font-medium rounded-lg ${info.bgColor} ${info.color} border ${info.borderColor}`}>
{info.label}
</span>
)
}
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 (
<div className="text-gray-500">
<div className="text-sm">Abgeschlossen am</div>
<div className="text-lg font-semibold">
{request.completedAt
? new Date(request.completedAt).toLocaleDateString('de-DE')
: '-'
}
</div>
</div>
)
}
return (
<div className={`${overdue ? 'text-red-600' : urgent ? 'text-orange-600' : 'text-gray-900'}`}>
<div className="text-sm">Frist</div>
<div className="text-2xl font-bold">
{overdue
? `${Math.abs(daysRemaining)} Tage ueberfaellig`
: `${daysRemaining} Tage`
}
</div>
<div className="text-xs text-gray-500 mt-1">
bis {new Date(request.deadline.currentDeadline).toLocaleDateString('de-DE')}
</div>
{request.deadline.extended && (
<div className="text-xs text-purple-600 mt-1">
(Verlaengert)
</div>
)}
</div>
)
}
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 (
<div className="space-y-2">
<button className="w-full px-4 py-2 text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors text-sm">
PDF exportieren
</button>
</div>
)
}
return (
<div className="space-y-2">
{!request.identityVerification.verified && (
<button
onClick={onVerifyIdentity}
className="w-full px-4 py-2 bg-yellow-500 text-white hover:bg-yellow-600 rounded-lg transition-colors text-sm font-medium"
>
Identitaet verifizieren
</button>
)}
<button
onClick={onAssign}
className="w-full px-4 py-2 text-purple-600 bg-purple-50 hover:bg-purple-100 rounded-lg transition-colors text-sm"
>
{request.assignment.assignedTo ? 'Neu zuweisen' : 'Zuweisen'}
</button>
<button
onClick={onExtendDeadline}
className="w-full px-4 py-2 text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors text-sm"
>
Frist verlaengern
</button>
<div className="border-t border-gray-200 pt-2 mt-2">
<button
onClick={onComplete}
className="w-full px-4 py-2 bg-green-600 text-white hover:bg-green-700 rounded-lg transition-colors text-sm font-medium"
>
Abschliessen
</button>
<button
onClick={onReject}
className="w-full mt-2 px-4 py-2 text-red-600 bg-red-50 hover:bg-red-100 rounded-lg transition-colors text-sm"
>
Ablehnen
</button>
</div>
</div>
)
}
function AuditLog({ request }: { request: DSRRequest }) {
type AuditEvent = { action: string; timestamp: string; user: string }
const events: AuditEvent[] = [
{ action: 'Erstellt', timestamp: request.createdAt, user: request.createdBy }
]
if (request.assignment.assignedAt) {
events.push({
action: `Zugewiesen an ${request.assignment.assignedTo}`,
timestamp: request.assignment.assignedAt,
user: request.assignment.assignedBy || 'System'
})
}
if (request.identityVerification.verifiedAt) {
events.push({
action: 'Identitaet verifiziert',
timestamp: request.identityVerification.verifiedAt,
user: request.identityVerification.verifiedBy || 'System'
})
}
if (request.completedAt) {
events.push({
action: request.status === 'rejected' ? 'Abgelehnt' : 'Abgeschlossen',
timestamp: request.completedAt,
user: request.updatedBy || 'System'
})
}
return (
<div className="space-y-3">
<h4 className="text-sm font-medium text-gray-700">Aktivitaeten</h4>
<div className="space-y-2">
{events.map((event, idx) => (
<div key={idx} className="flex items-start gap-2 text-xs">
<div className="w-1.5 h-1.5 rounded-full bg-gray-300 mt-1.5 flex-shrink-0" />
<div>
<div className="text-gray-900">{event.action}</div>
<div className="text-gray-500">
{new Date(event.timestamp).toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
{' - '}
{event.user}
</div>
</div>
</div>
))}
</div>
</div>
)
}
// =============================================================================
// MAIN PAGE
// =============================================================================
export default function DSRDetailPage() {
const params = useParams()
const router = useRouter()
const requestId = params.requestId as string
const [request, setRequest] = useState<DSRRequest | null>(null)
const [communications, setCommunications] = useState<DSRCommunication[]>([])
const [isLoading, setIsLoading] = useState(true)
const [showIdentityModal, setShowIdentityModal] = useState(false)
const [activeContentTab, setActiveContentTab] = useState<'details' | 'communication' | 'type-specific'>('details')
// Load data from SDK backend
useEffect(() => {
const loadData = async () => {
setIsLoading(true)
try {
const found = await fetchSDKDSR(requestId)
if (found) {
setRequest(found)
// Communications are loaded as mock for now (no backend API yet)
setCommunications(mockCommunications.filter(c => c.dsrId === requestId))
}
} catch (error) {
console.error('Failed to load DSR:', error)
} finally {
setIsLoading(false)
}
}
loadData()
}, [requestId])
const handleVerifyIdentity = async (verification: DSRVerifyIdentityRequest) => {
if (!request) return
try {
await updateSDKDSRStatus(request.id, 'verified')
setRequest({
...request,
identityVerification: {
verified: true,
method: verification.method,
verifiedAt: new Date().toISOString(),
verifiedBy: 'Current User',
notes: verification.notes
},
status: request.status === 'identity_verification' ? 'processing' : request.status
})
} catch (err) {
console.error('Failed to verify identity:', err)
// Still update locally as fallback
setRequest({
...request,
identityVerification: {
verified: true,
method: verification.method,
verifiedAt: new Date().toISOString(),
verifiedBy: 'Current User',
notes: verification.notes
},
status: request.status === 'identity_verification' ? 'processing' : request.status
})
}
}
const handleSendCommunication = async (message: any) => {
const newComm: DSRCommunication = {
id: `comm-${Date.now()}`,
dsrId: requestId,
...message,
createdAt: new Date().toISOString(),
createdBy: 'Current User',
sentAt: message.type === 'outgoing' ? new Date().toISOString() : undefined,
sentBy: message.type === 'outgoing' ? 'Current User' : undefined
}
setCommunications(prev => [newComm, ...prev])
}
if (isLoading) {
return (
<div className="flex items-center justify-center py-12">
<svg className="animate-spin w-8 h-8 text-purple-600" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
</div>
)
}
if (!request) {
return (
<div className="bg-white rounded-xl border border-gray-200 p-12 text-center">
<div className="w-16 h-16 mx-auto bg-red-100 rounded-full flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900">Anfrage nicht gefunden</h3>
<p className="mt-2 text-gray-500">
Die angeforderte DSR-Anfrage existiert nicht oder wurde geloescht.
</p>
<Link
href="/sdk/dsr"
className="mt-4 inline-flex items-center gap-2 px-4 py-2 text-purple-600 hover:bg-purple-50 rounded-lg transition-colors"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Zurueck zur Uebersicht
</Link>
</div>
)
}
const typeInfo = DSR_TYPE_INFO[request.type]
const overdue = isOverdue(request)
const urgent = isUrgent(request)
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Link
href="/sdk/dsr"
className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
</Link>
<div>
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500 font-mono">{request.referenceNumber}</span>
<span className={`px-2 py-1 text-xs rounded-full ${typeInfo.bgColor} ${typeInfo.color}`}>
{typeInfo.article} {typeInfo.label}
</span>
</div>
<h1 className="text-2xl font-bold text-gray-900 mt-1">
{request.requester.name}
</h1>
</div>
</div>
<button className="flex items-center gap-2 px-4 py-2 text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
Exportieren
</button>
</div>
{/* Workflow Stepper */}
<div className={`
bg-white rounded-xl border-2 p-6
${overdue ? 'border-red-200' : urgent ? 'border-orange-200' : 'border-gray-200'}
`}>
<DSRWorkflowStepper currentStatus={request.status} />
</div>
{/* Main Content: 2/3 + 1/3 Layout */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Column - 2/3 */}
<div className="lg:col-span-2 space-y-6">
{/* Content Tabs */}
<div className="bg-white rounded-xl border border-gray-200">
<div className="border-b border-gray-200">
<nav className="flex -mb-px">
{[
{ id: 'details', label: 'Details' },
{ id: 'communication', label: 'Kommunikation' },
{ id: 'type-specific', label: typeInfo.labelShort }
].map(tab => (
<button
key={tab.id}
onClick={() => setActiveContentTab(tab.id as any)}
className={`
px-6 py-4 text-sm font-medium border-b-2 transition-colors
${activeContentTab === tab.id
? 'border-purple-600 text-purple-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}
`}
>
{tab.label}
</button>
))}
</nav>
</div>
<div className="p-6">
{/* Details Tab */}
{activeContentTab === 'details' && (
<div className="space-y-6">
{/* Request Info */}
<div className="grid grid-cols-2 gap-6">
<div>
<h4 className="text-sm font-medium text-gray-500 mb-2">Antragsteller</h4>
<div className="space-y-2">
<div className="font-medium text-gray-900">{request.requester.name}</div>
<div className="text-sm text-gray-600">{request.requester.email}</div>
{request.requester.phone && (
<div className="text-sm text-gray-600">{request.requester.phone}</div>
)}
</div>
</div>
<div>
<h4 className="text-sm font-medium text-gray-500 mb-2">Eingereicht</h4>
<div className="space-y-2">
<div className="font-medium text-gray-900">
{new Date(request.receivedAt).toLocaleDateString('de-DE', {
day: '2-digit',
month: 'long',
year: 'numeric'
})}
</div>
<div className="text-sm text-gray-600">
Quelle: {request.source === 'web_form' ? 'Kontaktformular' :
request.source === 'email' ? 'E-Mail' :
request.source === 'letter' ? 'Brief' :
request.source === 'phone' ? 'Telefon' : request.source}
</div>
</div>
</div>
</div>
{/* Identity Verification */}
<div className={`
p-4 rounded-xl border
${request.identityVerification.verified
? 'bg-green-50 border-green-200'
: 'bg-yellow-50 border-yellow-200'
}
`}>
<div className="flex items-start gap-3">
<div className={`
w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0
${request.identityVerification.verified ? 'bg-green-100' : 'bg-yellow-100'}
`}>
{request.identityVerification.verified ? (
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
) : (
<svg className="w-4 h-4 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
)}
</div>
<div className="flex-1">
<div className={`font-medium ${request.identityVerification.verified ? 'text-green-800' : 'text-yellow-800'}`}>
{request.identityVerification.verified
? 'Identitaet verifiziert'
: 'Identitaetspruefung ausstehend'
}
</div>
{request.identityVerification.verified && (
<div className="text-sm text-green-700 mt-1">
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')}
</div>
)}
</div>
{!request.identityVerification.verified && (
<button
onClick={() => setShowIdentityModal(true)}
className="px-3 py-1.5 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 transition-colors text-sm"
>
Jetzt pruefen
</button>
)}
</div>
</div>
{/* Request Text */}
{request.requestText && (
<div>
<h4 className="text-sm font-medium text-gray-500 mb-2">Anfragetext</h4>
<div className="bg-gray-50 rounded-xl p-4 text-gray-700 whitespace-pre-wrap">
{request.requestText}
</div>
</div>
)}
{/* Notes */}
{request.notes && (
<div>
<h4 className="text-sm font-medium text-gray-500 mb-2">Notizen</h4>
<div className="bg-gray-50 rounded-xl p-4 text-gray-700">
{request.notes}
</div>
</div>
)}
</div>
)}
{/* Communication Tab */}
{activeContentTab === 'communication' && (
<DSRCommunicationLog
communications={communications}
onSendMessage={handleSendCommunication}
/>
)}
{/* Type-Specific Tab */}
{activeContentTab === 'type-specific' && (
<div>
{/* Art. 17 - Erasure */}
{request.type === 'erasure' && (
<DSRErasureChecklistComponent
checklist={request.erasureChecklist}
onChange={(checklist) => setRequest({ ...request, erasureChecklist: checklist })}
/>
)}
{/* Art. 15/20 - Data Export */}
{(request.type === 'access' || request.type === 'portability') && (
<DSRDataExportComponent
dsrId={request.id}
dsrType={request.type}
existingExport={request.dataExport}
onGenerate={async (format) => {
// 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 && (
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900">Zu korrigierende Daten</h3>
<div className="space-y-3">
{request.rectificationDetails.fieldsToCorrect.map((field, idx) => (
<div key={idx} className="bg-gray-50 rounded-xl p-4">
<div className="font-medium text-gray-900 mb-2">{field.field}</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<div className="text-gray-500 mb-1">Aktueller Wert</div>
<div className="text-red-600 line-through">{field.currentValue}</div>
</div>
<div>
<div className="text-gray-500 mb-1">Angeforderter Wert</div>
<div className="text-green-600">{field.requestedValue}</div>
</div>
</div>
{field.corrected && (
<div className="mt-2 text-xs text-green-600">
Korrigiert am {new Date(field.correctedAt!).toLocaleDateString('de-DE')}
</div>
)}
</div>
))}
</div>
</div>
)}
{/* Art. 21 - Objection */}
{request.type === 'objection' && request.objectionDetails && (
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900">Widerspruchsdetails</h3>
<div className="grid grid-cols-2 gap-4">
<div className="bg-gray-50 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Verarbeitungszweck</div>
<div className="font-medium">{request.objectionDetails.processingPurpose}</div>
</div>
<div className="bg-gray-50 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Rechtsgrundlage</div>
<div className="font-medium">{request.objectionDetails.legalBasis}</div>
</div>
</div>
<div className="bg-gray-50 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Widerspruchsgruende</div>
<div>{request.objectionDetails.objectionGrounds}</div>
</div>
{request.objectionDetails.decision !== 'pending' && (
<div className={`
rounded-xl p-4 border
${request.objectionDetails.decision === 'accepted'
? 'bg-green-50 border-green-200'
: 'bg-red-50 border-red-200'
}
`}>
<div className={`font-medium ${
request.objectionDetails.decision === 'accepted' ? 'text-green-800' : 'text-red-800'
}`}>
Widerspruch {request.objectionDetails.decision === 'accepted' ? 'angenommen' : 'abgelehnt'}
</div>
{request.objectionDetails.decisionReason && (
<div className={`text-sm mt-1 ${
request.objectionDetails.decision === 'accepted' ? 'text-green-700' : 'text-red-700'
}`}>
{request.objectionDetails.decisionReason}
</div>
)}
</div>
)}
</div>
)}
{/* Default for restriction */}
{request.type === 'restriction' && (
<div className="bg-blue-50 border border-blue-200 rounded-xl p-4">
<div className="flex items-start gap-3">
<svg className="w-5 h-5 text-blue-600 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<div className="font-medium text-blue-800">Einschraenkung der Verarbeitung</div>
<p className="text-sm text-blue-700 mt-1">
Markieren Sie die betroffenen Daten im System als eingeschraenkt.
Die Daten duerfen nur noch gespeichert, aber nicht mehr verarbeitet werden.
</p>
</div>
</div>
</div>
)}
</div>
)}
</div>
</div>
</div>
{/* Right Column - 1/3 Sidebar */}
<div className="space-y-6">
{/* Status Card */}
<div className="bg-white rounded-xl border border-gray-200 p-6 space-y-4">
<div className="flex items-center justify-between">
<h3 className="font-medium text-gray-900">Status</h3>
<StatusBadge status={request.status} />
</div>
<div className="border-t border-gray-100 pt-4">
<DeadlineDisplay request={request} />
</div>
{/* Priority */}
<div className="border-t border-gray-100 pt-4">
<div className="text-sm text-gray-500 mb-1">Prioritaet</div>
<div className={`
inline-flex px-2 py-1 text-sm font-medium rounded-lg
${request.priority === 'critical' ? 'bg-red-100 text-red-700' :
request.priority === 'high' ? 'bg-orange-100 text-orange-700' :
request.priority === 'normal' ? 'bg-gray-100 text-gray-700' :
'bg-blue-100 text-blue-700'
}
`}>
{request.priority === 'critical' ? 'Kritisch' :
request.priority === 'high' ? 'Hoch' :
request.priority === 'normal' ? 'Normal' : 'Niedrig'}
</div>
</div>
{/* Assignment */}
<div className="border-t border-gray-100 pt-4">
<div className="text-sm text-gray-500 mb-1">Zugewiesen an</div>
<div className="font-medium text-gray-900">
{request.assignment.assignedTo || 'Nicht zugewiesen'}
</div>
</div>
</div>
{/* Actions Card */}
<div className="bg-white rounded-xl border border-gray-200 p-6">
<h3 className="font-medium text-gray-900 mb-4">Aktionen</h3>
<ActionButtons
request={request}
onVerifyIdentity={() => setShowIdentityModal(true)}
onExtendDeadline={() => alert('Fristverlaengerung - Coming soon')}
onComplete={() => alert('Abschliessen - Coming soon')}
onReject={() => alert('Ablehnen - Coming soon')}
onAssign={() => alert('Zuweisen - Coming soon')}
/>
</div>
{/* Audit Log Card */}
<div className="bg-white rounded-xl border border-gray-200 p-6">
<AuditLog request={request} />
</div>
</div>
</div>
{/* Identity Modal */}
<DSRIdentityModal
isOpen={showIdentityModal}
onClose={() => setShowIdentityModal(false)}
onVerify={handleVerifyIdentity}
requesterName={request.requester.name}
requesterEmail={request.requester.email}
/>
</div>
)
}