Each page.tsx was >1000 LOC; extract components to _components/ and hooks to _hooks/ so page files stay under 500 LOC (164 / 255 / 243 respectively). Zero behavior changes — logic relocated verbatim. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
165 lines
6.2 KiB
TypeScript
165 lines
6.2 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Compliance Hub Page (SDK Version - Zusatzmodul)
|
|
*
|
|
* Central compliance management dashboard with tabs:
|
|
* - Uebersicht: Score, Stats, Quick Access, Findings
|
|
* - Roadmap: 4-column Kanban (Quick Wins / Must Have / Should Have / Nice to Have)
|
|
* - Module: Grid with module cards + progress bars
|
|
* - Trend: Score history chart
|
|
* - Traceability: Evidence traceability matrix
|
|
*/
|
|
|
|
import { useComplianceHub } from './_hooks/useComplianceHub'
|
|
import { OverviewTab } from './_components/OverviewTab'
|
|
import { RoadmapTab } from './_components/RoadmapTab'
|
|
import { ModulesTab } from './_components/ModulesTab'
|
|
import { TrendTab } from './_components/TrendTab'
|
|
import { TraceabilityTab } from './_components/TraceabilityTab'
|
|
import type { TabKey } from './_components/types'
|
|
|
|
const tabs: { key: TabKey; label: string }[] = [
|
|
{ key: 'overview', label: 'Uebersicht' },
|
|
{ key: 'roadmap', label: 'Roadmap' },
|
|
{ key: 'modules', label: 'Module' },
|
|
{ key: 'trend', label: 'Trend' },
|
|
{ key: 'traceability', label: 'Traceability' },
|
|
]
|
|
|
|
export default function ComplianceHubPage() {
|
|
const hub = useComplianceHub()
|
|
|
|
const score = hub.dashboard?.compliance_score || 0
|
|
const scoreColor = score >= 80 ? 'text-green-600' : score >= 60 ? 'text-yellow-600' : 'text-red-600'
|
|
const scoreBgColor = score >= 80 ? 'bg-green-500' : score >= 60 ? 'bg-yellow-500' : 'bg-red-500'
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Title Card */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-slate-900">Compliance Hub</h1>
|
|
<p className="text-slate-500 mt-1">
|
|
Zentrale Verwaltung aller Compliance-Anforderungen nach DSGVO, AI Act, BSI TR-03161 und weiteren Regulierungen.
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={hub.saveSnapshot}
|
|
disabled={hub.savingSnapshot}
|
|
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 text-sm"
|
|
>
|
|
{hub.savingSnapshot ? 'Speichere...' : 'Score-Snapshot speichern'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="bg-white rounded-xl shadow-sm border">
|
|
<div className="flex border-b">
|
|
{tabs.map(tab => (
|
|
<button
|
|
key={tab.key}
|
|
onClick={() => hub.setActiveTab(tab.key)}
|
|
className={`px-6 py-3 text-sm font-medium transition-colors ${
|
|
hub.activeTab === tab.key
|
|
? 'text-purple-600 border-b-2 border-purple-600'
|
|
: 'text-slate-500 hover:text-slate-700'
|
|
}`}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Error Banner */}
|
|
{hub.error && (
|
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4 flex items-center gap-3">
|
|
<svg className="w-5 h-5 text-red-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<span className="text-red-700">{hub.error}</span>
|
|
<button onClick={hub.loadData} className="ml-auto text-red-600 hover:text-red-800 text-sm font-medium">
|
|
Erneut versuchen
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Seed Button if no data */}
|
|
{!hub.loading && (hub.dashboard?.total_controls || 0) === 0 && (
|
|
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium text-yellow-800">Keine Compliance-Daten vorhanden</p>
|
|
<p className="text-sm text-yellow-700">Initialisieren Sie die Datenbank mit den Seed-Daten.</p>
|
|
</div>
|
|
<button
|
|
onClick={hub.seedDatabase}
|
|
disabled={hub.seeding}
|
|
className="px-4 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 disabled:opacity-50"
|
|
>
|
|
{hub.seeding ? 'Initialisiere...' : 'Datenbank initialisieren'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{hub.loading ? (
|
|
<div className="flex items-center justify-center h-64">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-purple-600" />
|
|
</div>
|
|
) : (
|
|
<>
|
|
{hub.activeTab === 'overview' && (
|
|
<OverviewTab
|
|
dashboard={hub.dashboard}
|
|
mappings={hub.mappings}
|
|
findings={hub.findings}
|
|
nextActions={hub.nextActions}
|
|
evidenceDistribution={hub.evidenceDistribution}
|
|
score={score}
|
|
scoreColor={scoreColor}
|
|
scoreBgColor={scoreBgColor}
|
|
loadData={hub.loadData}
|
|
regulations={hub.regulations}
|
|
/>
|
|
)}
|
|
|
|
{hub.activeTab === 'roadmap' && (
|
|
<RoadmapTab roadmap={hub.roadmap} />
|
|
)}
|
|
|
|
{hub.activeTab === 'modules' && (
|
|
<ModulesTab moduleStatus={hub.moduleStatus} />
|
|
)}
|
|
|
|
{hub.activeTab === 'trend' && (
|
|
<TrendTab
|
|
scoreHistory={hub.scoreHistory}
|
|
savingSnapshot={hub.savingSnapshot}
|
|
saveSnapshot={hub.saveSnapshot}
|
|
/>
|
|
)}
|
|
|
|
{hub.activeTab === 'traceability' && (
|
|
<TraceabilityTab
|
|
traceabilityMatrix={hub.traceabilityMatrix}
|
|
traceabilityLoading={hub.traceabilityLoading}
|
|
traceabilityFilter={hub.traceabilityFilter}
|
|
setTraceabilityFilter={hub.setTraceabilityFilter}
|
|
traceabilityDomainFilter={hub.traceabilityDomainFilter}
|
|
setTraceabilityDomainFilter={hub.setTraceabilityDomainFilter}
|
|
expandedControls={hub.expandedControls}
|
|
expandedEvidence={hub.expandedEvidence}
|
|
toggleControlExpanded={hub.toggleControlExpanded}
|
|
toggleEvidenceExpanded={hub.toggleEvidenceExpanded}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|