Files
breakpilot-compliance/admin-compliance/app/sdk/dsr/page.tsx
Sharang Parnerkar 4921d1c052 refactor(admin): split dsr page.tsx into colocated components
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>
2026-04-14 22:55:12 +02:00

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>
)
}