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>
77 lines
3.6 KiB
TypeScript
77 lines
3.6 KiB
TypeScript
'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>
|
|
)
|
|
}
|