Extract TabNavigation, StatCard, RequestCard, FilterBar, DSRCreateModal, DSRDetailPanel, DSRHeaderActions, and banner components (LoadingSpinner, SettingsTab, OverdueAlert, DeadlineInfoBox, EmptyState) into _components/ so page.tsx drops from 1019 to 247 LOC (under the 300 soft target). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
248 lines
8.1 KiB
TypeScript
248 lines
8.1 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect, useMemo, useCallback } from 'react'
|
|
import { useSDK } from '@/lib/sdk'
|
|
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
|
|
import {
|
|
DSRRequest,
|
|
DSRType,
|
|
DSRStatus,
|
|
DSRStatistics,
|
|
getDaysRemaining,
|
|
isOverdue,
|
|
} from '@/lib/sdk/dsr/types'
|
|
import { fetchSDKDSRList } from '@/lib/sdk/dsr/api'
|
|
import type { Tab, TabId } from './_types'
|
|
import { TabNavigation } from './_components/TabNavigation'
|
|
import { StatCard } from './_components/StatCard'
|
|
import { RequestCard } from './_components/RequestCard'
|
|
import { FilterBar } from './_components/FilterBar'
|
|
import { DSRCreateModal } from './_components/DSRCreateModal'
|
|
import { DSRDetailPanel } from './_components/DSRDetailPanel'
|
|
import { DSRHeaderActions } from './_components/DSRHeaderActions'
|
|
import {
|
|
LoadingSpinner,
|
|
SettingsTab,
|
|
OverdueAlert,
|
|
DeadlineInfoBox,
|
|
EmptyState,
|
|
} from './_components/DSRBanners'
|
|
|
|
// =============================================================================
|
|
// MAIN PAGE
|
|
// =============================================================================
|
|
|
|
export default function DSRPage() {
|
|
const { state } = useSDK()
|
|
const [activeTab, setActiveTab] = useState<TabId>('overview')
|
|
const [requests, setRequests] = useState<DSRRequest[]>([])
|
|
const [statistics, setStatistics] = useState<DSRStatistics | null>(null)
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
const [showCreateModal, setShowCreateModal] = useState(false)
|
|
const [selectedRequest, setSelectedRequest] = useState<DSRRequest | null>(null)
|
|
|
|
// Filters
|
|
const [selectedType, setSelectedType] = useState<DSRType | 'all'>('all')
|
|
const [selectedStatus, setSelectedStatus] = useState<DSRStatus | 'all'>('all')
|
|
const [selectedPriority, setSelectedPriority] = useState<string>('all')
|
|
|
|
// Load data from SDK backend
|
|
const loadData = useCallback(async () => {
|
|
setIsLoading(true)
|
|
try {
|
|
const { requests: dsrRequests, statistics: dsrStats } = await fetchSDKDSRList()
|
|
setRequests(dsrRequests)
|
|
setStatistics(dsrStats)
|
|
} catch (error) {
|
|
console.error('Failed to load DSR data:', error)
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
loadData()
|
|
}, [loadData])
|
|
|
|
// Calculate tab counts
|
|
const tabCounts = useMemo(() => {
|
|
return {
|
|
intake: requests.filter(r => r.status === 'intake' || r.status === 'identity_verification').length,
|
|
processing: requests.filter(r => r.status === 'processing').length,
|
|
completed: requests.filter(r => r.status === 'completed' || r.status === 'rejected' || r.status === 'cancelled').length,
|
|
overdue: requests.filter(r => isOverdue(r)).length
|
|
}
|
|
}, [requests])
|
|
|
|
// Filter requests based on active tab and filters
|
|
const filteredRequests = useMemo(() => {
|
|
let filtered = [...requests]
|
|
|
|
// Tab-based filtering
|
|
if (activeTab === 'intake') {
|
|
filtered = filtered.filter(r => r.status === 'intake' || r.status === 'identity_verification')
|
|
} else if (activeTab === 'processing') {
|
|
filtered = filtered.filter(r => r.status === 'processing')
|
|
} else if (activeTab === 'completed') {
|
|
filtered = filtered.filter(r => r.status === 'completed' || r.status === 'rejected' || r.status === 'cancelled')
|
|
}
|
|
|
|
// Type filter
|
|
if (selectedType !== 'all') {
|
|
filtered = filtered.filter(r => r.type === selectedType)
|
|
}
|
|
|
|
// Status filter
|
|
if (selectedStatus !== 'all') {
|
|
filtered = filtered.filter(r => r.status === selectedStatus)
|
|
}
|
|
|
|
// Priority filter
|
|
if (selectedPriority !== 'all') {
|
|
filtered = filtered.filter(r => r.priority === selectedPriority)
|
|
}
|
|
|
|
// Sort by urgency
|
|
return filtered.sort((a, b) => {
|
|
const getUrgency = (r: DSRRequest) => {
|
|
if (r.status === 'completed' || r.status === 'rejected' || r.status === 'cancelled') return 100
|
|
const days = getDaysRemaining(r.deadline.currentDeadline)
|
|
if (days < 0) return -100 + days // Overdue items first
|
|
return days
|
|
}
|
|
return getUrgency(a) - getUrgency(b)
|
|
})
|
|
}, [requests, activeTab, selectedType, selectedStatus, selectedPriority])
|
|
|
|
const tabs: Tab[] = [
|
|
{ id: 'overview', label: 'Uebersicht' },
|
|
{ id: 'intake', label: 'Eingang', count: tabCounts.intake, countColor: 'bg-blue-100 text-blue-600' },
|
|
{ id: 'processing', label: 'In Bearbeitung', count: tabCounts.processing, countColor: 'bg-yellow-100 text-yellow-600' },
|
|
{ id: 'completed', label: 'Abgeschlossen', count: tabCounts.completed, countColor: 'bg-green-100 text-green-600' },
|
|
{ id: 'settings', label: 'Einstellungen' }
|
|
]
|
|
|
|
const stepInfo = STEP_EXPLANATIONS['dsr']
|
|
|
|
const clearFilters = () => {
|
|
setSelectedType('all')
|
|
setSelectedStatus('all')
|
|
setSelectedPriority('all')
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Step Header */}
|
|
<StepHeader
|
|
stepId="dsr"
|
|
title={stepInfo.title}
|
|
description={stepInfo.description}
|
|
explanation={stepInfo.explanation}
|
|
tips={stepInfo.tips}
|
|
>
|
|
<DSRHeaderActions onOpenCreate={() => setShowCreateModal(true)} />
|
|
</StepHeader>
|
|
|
|
{/* Tab Navigation */}
|
|
<TabNavigation
|
|
tabs={tabs}
|
|
activeTab={activeTab}
|
|
onTabChange={setActiveTab}
|
|
/>
|
|
|
|
{/* Loading State */}
|
|
{isLoading ? (
|
|
<LoadingSpinner />
|
|
) : activeTab === 'settings' ? (
|
|
<SettingsTab />
|
|
) : (
|
|
<>
|
|
{/* Statistics (Overview Tab) */}
|
|
{activeTab === 'overview' && statistics && (
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<StatCard label="Gesamt" value={statistics.total} color="gray" />
|
|
<StatCard
|
|
label="Neue Anfragen"
|
|
value={statistics.byStatus.intake + statistics.byStatus.identity_verification}
|
|
color="blue"
|
|
/>
|
|
<StatCard
|
|
label="In Bearbeitung"
|
|
value={statistics.byStatus.processing}
|
|
color="yellow"
|
|
/>
|
|
<StatCard
|
|
label="Ueberfaellig"
|
|
value={tabCounts.overdue}
|
|
color={tabCounts.overdue > 0 ? 'red' : 'green'}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Overdue Alert */}
|
|
{tabCounts.overdue > 0 && (
|
|
<OverdueAlert
|
|
overdueCount={tabCounts.overdue}
|
|
onShowOverdue={() => {
|
|
setActiveTab('overview')
|
|
setSelectedStatus('all')
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{/* Info Box (Overview Tab) */}
|
|
{activeTab === 'overview' && <DeadlineInfoBox />}
|
|
|
|
{/* Filters */}
|
|
<FilterBar
|
|
selectedType={selectedType}
|
|
selectedStatus={selectedStatus}
|
|
selectedPriority={selectedPriority}
|
|
onTypeChange={setSelectedType}
|
|
onStatusChange={setSelectedStatus}
|
|
onPriorityChange={setSelectedPriority}
|
|
onClear={clearFilters}
|
|
/>
|
|
|
|
{/* Requests List */}
|
|
<div className="space-y-4">
|
|
{filteredRequests.map(request => (
|
|
<RequestCard
|
|
key={request.id}
|
|
request={request}
|
|
onClick={() => setSelectedRequest(request)}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{/* Empty State */}
|
|
{filteredRequests.length === 0 && (
|
|
<EmptyState
|
|
selectedType={selectedType}
|
|
selectedStatus={selectedStatus}
|
|
selectedPriority={selectedPriority}
|
|
onClearFilters={clearFilters}
|
|
onOpenCreate={() => setShowCreateModal(true)}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* Modals */}
|
|
{showCreateModal && (
|
|
<DSRCreateModal
|
|
onClose={() => setShowCreateModal(false)}
|
|
onSuccess={() => { setShowCreateModal(false); loadData() }}
|
|
/>
|
|
)}
|
|
{selectedRequest && (
|
|
<DSRDetailPanel
|
|
request={selectedRequest}
|
|
onClose={() => setSelectedRequest(null)}
|
|
onUpdated={() => { setSelectedRequest(null); loadData() }}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|