Split the 854-line DSR detail page into colocated components under _components/ and a data-loading hook under _hooks/. No behavior changes. page.tsx is now 172 LOC, all extracted files under 300 LOC. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
173 lines
6.0 KiB
TypeScript
173 lines
6.0 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState } from 'react'
|
|
import Link from 'next/link'
|
|
import { useParams } from 'next/navigation'
|
|
import { DSR_TYPE_INFO, isOverdue, isUrgent } from '@/lib/sdk/dsr/types'
|
|
import {
|
|
DSRWorkflowStepper,
|
|
DSRIdentityModal,
|
|
DSRCommunicationLog,
|
|
} from '@/components/sdk/dsr'
|
|
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 requestId = params.requestId as string
|
|
|
|
const [showIdentityModal, setShowIdentityModal] = useState(false)
|
|
const [activeContentTab, setActiveContentTab] = useState<'details' | 'communication' | 'type-specific'>('details')
|
|
|
|
const {
|
|
request,
|
|
setRequest,
|
|
communications,
|
|
history,
|
|
exceptionChecks,
|
|
isLoading,
|
|
handleVerifyIdentity,
|
|
handleAssign,
|
|
handleExtendDeadline,
|
|
handleComplete,
|
|
handleReject,
|
|
handleSendCommunication,
|
|
handleExceptionCheckChange,
|
|
} = useDSRDetail(requestId)
|
|
|
|
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">
|
|
<DSRHeader request={request} />
|
|
|
|
{/* 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">
|
|
{activeContentTab === 'details' && (
|
|
<DSRDetailsTab
|
|
request={request}
|
|
onShowIdentityModal={() => setShowIdentityModal(true)}
|
|
/>
|
|
)}
|
|
|
|
{activeContentTab === 'communication' && (
|
|
<DSRCommunicationLog
|
|
communications={communications}
|
|
onSendMessage={handleSendCommunication}
|
|
/>
|
|
)}
|
|
|
|
{activeContentTab === 'type-specific' && (
|
|
<DSRTypeSpecificTab
|
|
request={request}
|
|
setRequest={setRequest}
|
|
exceptionChecks={exceptionChecks}
|
|
onExceptionCheckChange={handleExceptionCheckChange}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Column - 1/3 Sidebar */}
|
|
<DSRSidebar
|
|
request={request}
|
|
history={history}
|
|
onVerifyIdentity={() => setShowIdentityModal(true)}
|
|
onExtendDeadline={handleExtendDeadline}
|
|
onComplete={handleComplete}
|
|
onReject={handleReject}
|
|
onAssign={handleAssign}
|
|
/>
|
|
</div>
|
|
|
|
{/* Identity Modal */}
|
|
<DSRIdentityModal
|
|
isOpen={showIdentityModal}
|
|
onClose={() => setShowIdentityModal(false)}
|
|
onVerify={handleVerifyIdentity}
|
|
requesterName={request.requester.name}
|
|
requesterEmail={request.requester.email}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|