'use client'
import React, { useState, useEffect, useCallback } from 'react'
import {
GCIResult,
GCIBreakdown,
GCIHistoryResponse,
GCIMatrixResponse,
NIS2Score,
ISOGapAnalysis,
WeightProfile,
MaturityLevel,
MATURITY_INFO,
getScoreColor,
getScoreRingColor,
} from '@/lib/sdk/gci/types'
import {
getGCIScore,
getGCIBreakdown,
getGCIHistory,
getGCIMatrix,
getNIS2Score,
getISOGapAnalysis,
getWeightProfiles,
} from '@/lib/sdk/gci/api'
// =============================================================================
// TYPES
// =============================================================================
type TabId = 'overview' | 'breakdown' | 'nis2' | 'iso' | 'matrix' | 'audit'
interface Tab {
id: TabId
label: string
}
const TABS: Tab[] = [
{ id: 'overview', label: 'Uebersicht' },
{ id: 'breakdown', label: 'Breakdown' },
{ id: 'nis2', label: 'NIS2' },
{ id: 'iso', label: 'ISO 27001' },
{ id: 'matrix', label: 'Matrix' },
{ id: 'audit', label: 'Audit Trail' },
]
// =============================================================================
// HELPER COMPONENTS
// =============================================================================
function TabNavigation({ tabs, activeTab, onTabChange }: { tabs: Tab[]; activeTab: TabId; onTabChange: (tab: TabId) => void }) {
return (
)
}
function ScoreCircle({ score, size = 144, label }: { score: number; size?: number; label?: string }) {
const radius = (size / 2) - 12
const circumference = 2 * Math.PI * radius
const strokeDashoffset = circumference - (score / 100) * circumference
return (
{score.toFixed(1)}
{label && {label}}
)
}
function MaturityBadge({ level }: { level: MaturityLevel }) {
const info = MATURITY_INFO[level] || MATURITY_INFO.HIGH_RISK
return (
{info.label}
)
}
function AreaScoreBar({ name, score, weight }: { name: string; score: number; weight: number }) {
return (
{name}
{score.toFixed(1)}%
Gewichtung: {(weight * 100).toFixed(0)}%
)
}
function LoadingSpinner() {
return (
)
}
function ErrorMessage({ message, onRetry }: { message: string; onRetry?: () => void }) {
return (
{message}
{onRetry && (
)}
)
}
// =============================================================================
// TAB: OVERVIEW
// =============================================================================
function OverviewTab({ gci, history, profiles, selectedProfile, onProfileChange }: {
gci: GCIResult
history: GCIHistoryResponse | null
profiles: WeightProfile[]
selectedProfile: string
onProfileChange: (p: string) => void
}) {
return (
{/* Profile Selector */}
{profiles.length > 0 && (
)}
{/* Main Score */}
Gesamt-Compliance-Index
Berechnet: {new Date(gci.calculated_at).toLocaleString('de-DE')}
{MATURITY_INFO[gci.maturity_level]?.description || ''}
{/* Area Scores */}
Regulierungsbereiche
{gci.area_scores.map(area => (
))}
{/* History Chart (simplified) */}
{history && history.snapshots.length > 0 && (
Verlauf
{history.snapshots.map((snap, i) => (
{snap.score.toFixed(0)}
{new Date(snap.calculated_at).toLocaleDateString('de-DE', { month: 'short' })}
))}
)}
{/* Adjustments */}
Kritikalitaets-Multiplikator
{gci.criticality_multiplier.toFixed(2)}x
Incident-Korrektur
{gci.incident_adjustment > 0 ? '+' : ''}{gci.incident_adjustment.toFixed(1)}
)
}
// =============================================================================
// TAB: BREAKDOWN
// =============================================================================
function BreakdownTab({ breakdown }: { breakdown: GCIBreakdown | null; loading: boolean }) {
if (!breakdown) return
return (
{/* Level 1: Modules */}
Level 1: Modul-Scores
| Modul |
Kategorie |
Zugewiesen |
Abgeschlossen |
Raw Score |
Validitaet |
Final |
{breakdown.level1_modules.map(m => (
| {m.module_name} |
{m.category}
|
{m.assigned} |
{m.completed} |
{(m.raw_score * 100).toFixed(1)}% |
{(m.validity_factor * 100).toFixed(0)}% |
{(m.final_score * 100).toFixed(1)}%
|
))}
{/* Level 2: Areas */}
Level 2: Regulierungsbereiche (risikogewichtet)
{breakdown.level2_areas.map(area => (
{area.area_name}
{area.area_score.toFixed(1)}%
{area.modules.map(m => (
{m.module_name}
{(m.final_score * 100).toFixed(0)}% (w:{m.risk_weight.toFixed(1)})
))}
))}
)
}
// =============================================================================
// TAB: NIS2
// =============================================================================
function NIS2Tab({ nis2 }: { nis2: NIS2Score | null }) {
if (!nis2) return
return (
{/* NIS2 Overall */}
NIS2 Compliance Score
Network and Information Security Directive 2 (EU 2022/2555)
{/* NIS2 Areas */}
NIS2 Bereiche
{nis2.areas.map(area => (
))}
{/* NIS2 Roles */}
{nis2.role_scores && nis2.role_scores.length > 0 && (
Rollen-Compliance
{nis2.role_scores.map(role => (
{role.role_name}
{(role.completion_rate * 100).toFixed(0)}%
{role.modules_completed}/{role.modules_required} Module
))}
)}
)
}
// =============================================================================
// TAB: ISO 27001
// =============================================================================
function ISOTab({ iso }: { iso: ISOGapAnalysis | null }) {
if (!iso) return
return (
{/* Coverage Overview */}
ISO 27001:2022 Gap-Analyse
{iso.covered_full}
Voll abgedeckt
{iso.covered_partial}
Teilweise
{iso.not_covered}
Nicht abgedeckt
{/* Category Summaries */}
Kategorien
{iso.category_summaries.map(cat => {
const coveragePercent = cat.total_controls > 0
? ((cat.covered_full + cat.covered_partial * 0.5) / cat.total_controls) * 100
: 0
return (
{cat.category_id}: {cat.category_name}
{cat.covered_full}/{cat.total_controls} Controls
)
})}
{/* Gaps */}
{iso.gaps && iso.gaps.length > 0 && (
Offene Gaps ({iso.gaps.length})
{iso.gaps.map(gap => (
{gap.priority}
{gap.control_id}: {gap.control_name}
{gap.recommendation}
))}
)}
)
}
// =============================================================================
// TAB: MATRIX
// =============================================================================
function MatrixTab({ matrix }: { matrix: GCIMatrixResponse | null }) {
if (!matrix || !matrix.matrix) return
const regulations = matrix.matrix.length > 0 ? Object.keys(matrix.matrix[0].regulations) : []
return (
Compliance-Matrix (Rollen x Regulierungen)
| Rolle |
{regulations.map(r => (
{r} |
))}
Gesamt |
Module |
{matrix.matrix.map(entry => (
| {entry.role_name} |
{regulations.map(r => (
{entry.regulations[r].toFixed(0)}%
|
))}
{entry.overall_score.toFixed(0)}%
|
{entry.completed_modules}/{entry.required_modules}
|
))}
)
}
// =============================================================================
// TAB: AUDIT TRAIL
// =============================================================================
function AuditTab({ gci }: { gci: GCIResult }) {
return (
Audit Trail - Berechnung GCI {gci.gci_score.toFixed(1)}
Jeder Schritt der GCI-Berechnung ist nachvollziehbar und prueffaehig dokumentiert.
{gci.audit_trail.map((entry, i) => (
{entry.factor}
{entry.value > 0 ? '+' : ''}{entry.value.toFixed(2)}
{entry.description}
))}
)
}
// =============================================================================
// MAIN PAGE
// =============================================================================
export default function GCIPage() {
const [activeTab, setActiveTab] = useState('overview')
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [gci, setGCI] = useState(null)
const [breakdown, setBreakdown] = useState(null)
const [history, setHistory] = useState(null)
const [matrix, setMatrix] = useState(null)
const [nis2, setNIS2] = useState(null)
const [iso, setISO] = useState(null)
const [profiles, setProfiles] = useState([])
const [selectedProfile, setSelectedProfile] = useState('default')
const loadData = useCallback(async (profile?: string) => {
setLoading(true)
setError(null)
try {
const [gciRes, historyRes, profilesRes] = await Promise.all([
getGCIScore(profile),
getGCIHistory(),
getWeightProfiles(),
])
setGCI(gciRes)
setHistory(historyRes)
setProfiles(profilesRes.profiles || [])
} catch (err: any) {
setError(err.message || 'Fehler beim Laden der GCI-Daten')
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
loadData(selectedProfile)
}, [selectedProfile, loadData])
// Lazy-load tab data
useEffect(() => {
if (activeTab === 'breakdown' && !breakdown && gci) {
getGCIBreakdown(selectedProfile).then(setBreakdown).catch(() => {})
}
if (activeTab === 'nis2' && !nis2) {
getNIS2Score().then(setNIS2).catch(() => {})
}
if (activeTab === 'iso' && !iso) {
getISOGapAnalysis().then(setISO).catch(() => {})
}
if (activeTab === 'matrix' && !matrix) {
getGCIMatrix().then(setMatrix).catch(() => {})
}
}, [activeTab, breakdown, nis2, iso, matrix, gci, selectedProfile])
const handleProfileChange = (profile: string) => {
setSelectedProfile(profile)
setBreakdown(null) // reset breakdown to reload
}
return (
{/* Header */}
Gesamt-Compliance-Index (GCI)
4-stufiges, mathematisch fundiertes Compliance-Scoring
{/* Tabs */}
{/* Content */}
{error &&
loadData(selectedProfile)} />}
{loading && !gci ? (
) : gci ? (
{activeTab === 'overview' && (
)}
{activeTab === 'breakdown' && (
)}
{activeTab === 'nis2' &&
}
{activeTab === 'iso' &&
}
{activeTab === 'matrix' &&
}
{activeTab === 'audit' &&
}
) : null}
)
}