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>
57 lines
2.5 KiB
TypeScript
57 lines
2.5 KiB
TypeScript
'use client'
|
|
|
|
import { NIS2Score, getScoreColor, getScoreRingColor } from '@/lib/sdk/gci/types'
|
|
import { ScoreCircle, AreaScoreBar, LoadingSpinner } from './GCIHelpers'
|
|
|
|
export function NIS2Tab({ nis2 }: { nis2: NIS2Score | null }) {
|
|
if (!nis2) 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={nis2.overall_score} size={120} label="NIS2" />
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-gray-900">NIS2 Compliance Score</h3>
|
|
<p className="text-sm text-gray-500 mt-1">Network and Information Security Directive 2 (EU 2022/2555)</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">NIS2 Bereiche</h3>
|
|
<div className="space-y-3">
|
|
{nis2.areas.map(area => (
|
|
<AreaScoreBar key={area.area_id} name={area.area_name} score={area.score} weight={area.weight} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{nis2.role_scores && nis2.role_scores.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">Rollen-Compliance</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
{nis2.role_scores.map(role => (
|
|
<div key={role.role_id} className="border border-gray-200 rounded-lg p-3">
|
|
<div className="font-medium text-gray-900 text-sm">{role.role_name}</div>
|
|
<div className="flex items-center justify-between mt-2">
|
|
<span className={`text-lg font-bold ${getScoreColor(role.completion_rate * 100)}`}>
|
|
{(role.completion_rate * 100).toFixed(0)}%
|
|
</span>
|
|
<span className="text-xs text-gray-500">{role.modules_completed}/{role.modules_required} Module</span>
|
|
</div>
|
|
<div className="w-full bg-gray-200 rounded-full h-1.5 mt-2">
|
|
<div
|
|
className="h-1.5 rounded-full"
|
|
style={{ width: `${Math.min(role.completion_rate * 100, 100)}%`, backgroundColor: getScoreRingColor(role.completion_rate * 100) }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|