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:
76
admin-compliance/app/sdk/gci/_components/ISOTab.tsx
Normal file
76
admin-compliance/app/sdk/gci/_components/ISOTab.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
'use client'
|
||||
|
||||
import { ISOGapAnalysis } from '@/lib/sdk/gci/types'
|
||||
import { ScoreCircle, LoadingSpinner } from './GCIHelpers'
|
||||
|
||||
export function ISOTab({ iso }: { iso: ISOGapAnalysis | null }) {
|
||||
if (!iso) return <LoadingSpinner />
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-6">
|
||||
<ScoreCircle score={iso.coverage_percent} size={120} label="Abdeckung" />
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-gray-900">ISO 27001:2022 Gap-Analyse</h3>
|
||||
<div className="grid grid-cols-3 gap-4 mt-3">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-green-600">{iso.covered_full}</div>
|
||||
<div className="text-xs text-gray-500">Voll abgedeckt</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-yellow-600">{iso.covered_partial}</div>
|
||||
<div className="text-xs text-gray-500">Teilweise</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-red-600">{iso.not_covered}</div>
|
||||
<div className="text-xs text-gray-500">Nicht abgedeckt</div>
|
||||
</div>
|
||||
</div>
|
||||
</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">Kategorien</h3>
|
||||
<div className="space-y-3">
|
||||
{iso.category_summaries.map(cat => (
|
||||
<div key={cat.category_id} className="space-y-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="font-medium text-gray-700">{cat.category_id}: {cat.category_name}</span>
|
||||
<span className="text-gray-500">{cat.covered_full}/{cat.total_controls} Controls</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-3 flex overflow-hidden">
|
||||
<div className="h-3 bg-green-500" style={{ width: `${(cat.covered_full / cat.total_controls) * 100}%` }} />
|
||||
<div className="h-3 bg-yellow-500" style={{ width: `${(cat.covered_partial / cat.total_controls) * 100}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{iso.gaps && iso.gaps.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">Offene Gaps ({iso.gaps.length})</h3>
|
||||
<div className="space-y-2 max-h-96 overflow-y-auto">
|
||||
{iso.gaps.map(gap => (
|
||||
<div key={gap.control_id} className="flex items-start gap-3 p-3 border border-gray-100 rounded-lg hover:bg-gray-50">
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${
|
||||
gap.priority === 'high' ? 'bg-red-100 text-red-700' :
|
||||
gap.priority === 'medium' ? 'bg-yellow-100 text-yellow-700' :
|
||||
'bg-gray-100 text-gray-700'
|
||||
}`}>
|
||||
{gap.priority}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium text-gray-900">{gap.control_id}: {gap.control_name}</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">{gap.recommendation}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user