refactor(admin): split evidence, import, portfolio pages

Extract components and hooks from oversized pages into colocated
_components/ and _hooks/ subdirectories to enforce the 500-LOC hard cap.
page.tsx files reduced to 205, 121, and 136 LOC respectively.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-16 13:07:04 +02:00
parent 9096aad693
commit 7907b3f25b
42 changed files with 3568 additions and 3591 deletions

View File

@@ -0,0 +1,91 @@
'use client'
import { GCIResult, GCIHistoryResponse, WeightProfile, MATURITY_INFO, getScoreRingColor } from '@/lib/sdk/gci/types'
import { ScoreCircle, MaturityBadge, AreaScoreBar } from './GCIHelpers'
export function OverviewTab({ gci, history, profiles, selectedProfile, onProfileChange }: {
gci: GCIResult
history: GCIHistoryResponse | null
profiles: WeightProfile[]
selectedProfile: string
onProfileChange: (p: string) => void
}) {
return (
<div className="space-y-6">
{profiles.length > 0 && (
<div className="flex items-center gap-3">
<label className="text-sm font-medium text-gray-700">Gewichtungsprofil:</label>
<select
value={selectedProfile}
onChange={e => onProfileChange(e.target.value)}
className="rounded-md border-gray-300 shadow-sm text-sm focus:border-purple-500 focus:ring-purple-500"
>
{profiles.map(p => (
<option key={p.id} value={p.id}>{p.name}</option>
))}
</select>
</div>
)}
<div className="bg-white rounded-xl border border-gray-200 p-6">
<div className="flex flex-col md:flex-row items-center gap-8">
<ScoreCircle score={gci.gci_score} label="GCI Score" />
<div className="flex-1 space-y-4">
<div>
<h3 className="text-lg font-semibold text-gray-900">Gesamt-Compliance-Index</h3>
<div className="flex items-center gap-3 mt-2">
<MaturityBadge level={gci.maturity_level} />
<span className="text-sm text-gray-500">
Berechnet: {new Date(gci.calculated_at).toLocaleString('de-DE')}
</span>
</div>
</div>
<p className="text-sm text-gray-600">{MATURITY_INFO[gci.maturity_level]?.description || ''}</p>
</div>
</div>
</div>
<div className="bg-white rounded-xl border border-gray-200 p-6">
<h3 className="text-base font-semibold text-gray-900 mb-4">Regulierungsbereiche</h3>
<div className="space-y-4">
{gci.area_scores.map(area => (
<AreaScoreBar key={area.regulation_id} name={area.regulation_name} score={area.score} weight={area.weight} />
))}
</div>
</div>
{history && history.snapshots.length > 0 && (
<div className="bg-white rounded-xl border border-gray-200 p-6">
<h3 className="text-base font-semibold text-gray-900 mb-4">Verlauf</h3>
<div className="flex items-end gap-2 h-32">
{history.snapshots.map((snap, i) => (
<div key={i} className="flex-1 flex flex-col items-center gap-1">
<span className="text-xs text-gray-500">{snap.score.toFixed(0)}</span>
<div
className="w-full rounded-t transition-all duration-500"
style={{ height: `${(snap.score / 100) * 100}%`, backgroundColor: getScoreRingColor(snap.score), minHeight: '4px' }}
/>
<span className="text-[10px] text-gray-400">
{new Date(snap.calculated_at).toLocaleDateString('de-DE', { month: 'short' })}
</span>
</div>
))}
</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-white rounded-xl border border-gray-200 p-4">
<div className="text-sm text-gray-500">Kritikalitaets-Multiplikator</div>
<div className="text-2xl font-bold text-gray-900">{gci.criticality_multiplier.toFixed(2)}x</div>
</div>
<div className="bg-white rounded-xl border border-gray-200 p-4">
<div className="text-sm text-gray-500">Incident-Korrektur</div>
<div className={`text-2xl font-bold ${gci.incident_adjustment < 0 ? 'text-red-600' : 'text-green-600'}`}>
{gci.incident_adjustment > 0 ? '+' : ''}{gci.incident_adjustment.toFixed(1)}
</div>
</div>
</div>
</div>
)
}