refactor(admin): split compliance-hub, obligations, document-generator pages
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>
This commit is contained in:
@@ -1,84 +1,78 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export interface DashboardData {
|
||||
compliance_score: number
|
||||
total_regulations: number
|
||||
total_requirements: number
|
||||
total_controls: number
|
||||
controls_by_status: Record<string, number>
|
||||
controls_by_domain: Record<string, Record<string, number>>
|
||||
total_evidence: number
|
||||
evidence_by_status: Record<string, number>
|
||||
total_risks: number
|
||||
risks_by_level: Record<string, number>
|
||||
}
|
||||
|
||||
export interface Regulation {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
full_name: string
|
||||
regulation_type: string
|
||||
effective_date: string | null
|
||||
description: string
|
||||
requirement_count: number
|
||||
}
|
||||
|
||||
export interface MappingsData {
|
||||
total: number
|
||||
by_regulation: Record<string, number>
|
||||
}
|
||||
|
||||
export interface FindingsData {
|
||||
major_count: number
|
||||
minor_count: number
|
||||
ofi_count: number
|
||||
total: number
|
||||
open_majors: number
|
||||
open_minors: number
|
||||
}
|
||||
import type {
|
||||
DashboardData, Regulation, MappingsData, FindingsData,
|
||||
RoadmapData, ModuleStatusData, NextAction, ScoreSnapshot,
|
||||
TraceabilityMatrixData, TabKey,
|
||||
} from '../_components/types'
|
||||
|
||||
export function useComplianceHub() {
|
||||
const [activeTab, setActiveTab] = useState<TabKey>('overview')
|
||||
const [dashboard, setDashboard] = useState<DashboardData | null>(null)
|
||||
const [regulations, setRegulations] = useState<Regulation[]>([])
|
||||
const [mappings, setMappings] = useState<MappingsData | null>(null)
|
||||
const [findings, setFindings] = useState<FindingsData | null>(null)
|
||||
const [roadmap, setRoadmap] = useState<RoadmapData | null>(null)
|
||||
const [moduleStatus, setModuleStatus] = useState<ModuleStatusData | null>(null)
|
||||
const [nextActions, setNextActions] = useState<NextAction[]>([])
|
||||
const [scoreHistory, setScoreHistory] = useState<ScoreSnapshot[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [seeding, setSeeding] = useState(false)
|
||||
const [savingSnapshot, setSavingSnapshot] = useState(false)
|
||||
const [evidenceDistribution, setEvidenceDistribution] = useState<{
|
||||
by_confidence: Record<string, number>
|
||||
four_eyes_pending: number
|
||||
total: number
|
||||
} | null>(null)
|
||||
const [traceabilityMatrix, setTraceabilityMatrix] = useState<TraceabilityMatrixData | null>(null)
|
||||
const [traceabilityLoading, setTraceabilityLoading] = useState(false)
|
||||
const [traceabilityFilter, setTraceabilityFilter] = useState<'all' | 'covered' | 'uncovered' | 'fully_verified'>('all')
|
||||
const [traceabilityDomainFilter, setTraceabilityDomainFilter] = useState<string>('all')
|
||||
const [expandedControls, setExpandedControls] = useState<Set<string>>(new Set())
|
||||
const [expandedEvidence, setExpandedEvidence] = useState<Set<string>>(new Set())
|
||||
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'roadmap' && !roadmap) loadRoadmap()
|
||||
if (activeTab === 'modules' && !moduleStatus) loadModuleStatus()
|
||||
if (activeTab === 'trend' && scoreHistory.length === 0) loadScoreHistory()
|
||||
if (activeTab === 'traceability' && !traceabilityMatrix) loadTraceabilityMatrix()
|
||||
}, [activeTab]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const loadData = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const [dashboardRes, regulationsRes, mappingsRes, findingsRes] = await Promise.all([
|
||||
const [dashboardRes, regulationsRes, mappingsRes, findingsRes, actionsRes] = await Promise.all([
|
||||
fetch('/api/sdk/v1/compliance/dashboard'),
|
||||
fetch('/api/sdk/v1/compliance/regulations'),
|
||||
fetch('/api/sdk/v1/compliance/mappings'),
|
||||
fetch('/api/sdk/v1/isms/findings?status=open'),
|
||||
fetch('/api/sdk/v1/compliance/dashboard/next-actions?limit=5'),
|
||||
])
|
||||
|
||||
if (dashboardRes.ok) {
|
||||
setDashboard(await dashboardRes.json())
|
||||
}
|
||||
if (dashboardRes.ok) setDashboard(await dashboardRes.json())
|
||||
if (regulationsRes.ok) {
|
||||
const data = await regulationsRes.json()
|
||||
setRegulations(data.regulations || [])
|
||||
}
|
||||
if (mappingsRes.ok) {
|
||||
const data = await mappingsRes.json()
|
||||
setMappings(data)
|
||||
}
|
||||
if (findingsRes.ok) {
|
||||
const data = await findingsRes.json()
|
||||
setFindings(data)
|
||||
if (mappingsRes.ok) setMappings(await mappingsRes.json())
|
||||
if (findingsRes.ok) setFindings(await findingsRes.json())
|
||||
if (actionsRes.ok) {
|
||||
const data = await actionsRes.json()
|
||||
setNextActions(data.actions || [])
|
||||
}
|
||||
|
||||
// Evidence distribution (Anti-Fake-Evidence Phase 3)
|
||||
try {
|
||||
const evidenceDistRes = await fetch('/api/sdk/v1/compliance/dashboard/evidence-distribution')
|
||||
if (evidenceDistRes.ok) setEvidenceDistribution(await evidenceDistRes.json())
|
||||
} catch { /* silent */ }
|
||||
} catch (err) {
|
||||
console.error('Failed to load compliance data:', err)
|
||||
setError('Verbindung zum Backend fehlgeschlagen')
|
||||
@@ -87,6 +81,66 @@ export function useComplianceHub() {
|
||||
}
|
||||
}
|
||||
|
||||
const loadRoadmap = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/sdk/v1/compliance/dashboard/roadmap')
|
||||
if (res.ok) setRoadmap(await res.json())
|
||||
} catch { /* silent */ }
|
||||
}
|
||||
|
||||
const loadModuleStatus = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/sdk/v1/compliance/dashboard/module-status')
|
||||
if (res.ok) setModuleStatus(await res.json())
|
||||
} catch { /* silent */ }
|
||||
}
|
||||
|
||||
const loadScoreHistory = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/sdk/v1/compliance/dashboard/score-history?months=12')
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setScoreHistory(data.snapshots || [])
|
||||
}
|
||||
} catch { /* silent */ }
|
||||
}
|
||||
|
||||
const loadTraceabilityMatrix = async () => {
|
||||
setTraceabilityLoading(true)
|
||||
try {
|
||||
const res = await fetch('/api/sdk/v1/compliance/dashboard/traceability-matrix')
|
||||
if (res.ok) setTraceabilityMatrix(await res.json())
|
||||
} catch { /* silent */ }
|
||||
finally { setTraceabilityLoading(false) }
|
||||
}
|
||||
|
||||
const toggleControlExpanded = (id: string) => {
|
||||
setExpandedControls(prev => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(id)) next.delete(id); else next.add(id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const toggleEvidenceExpanded = (id: string) => {
|
||||
setExpandedEvidence(prev => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(id)) next.delete(id); else next.add(id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const saveSnapshot = async () => {
|
||||
setSavingSnapshot(true)
|
||||
try {
|
||||
const res = await fetch('/api/sdk/v1/compliance/dashboard/snapshot', { method: 'POST' })
|
||||
if (res.ok) {
|
||||
loadScoreHistory()
|
||||
}
|
||||
} catch { /* silent */ }
|
||||
finally { setSavingSnapshot(false) }
|
||||
}
|
||||
|
||||
const seedDatabase = async () => {
|
||||
setSeeding(true)
|
||||
try {
|
||||
@@ -101,8 +155,8 @@ export function useComplianceHub() {
|
||||
alert(`Datenbank erfolgreich initialisiert!\n\nRegulations: ${result.counts?.regulations || 0}\nControls: ${result.counts?.controls || 0}\nRequirements: ${result.counts?.requirements || 0}`)
|
||||
loadData()
|
||||
} else {
|
||||
const errorText = await res.text()
|
||||
alert(`Fehler beim Seeding: ${errorText}`)
|
||||
const error = await res.text()
|
||||
alert(`Fehler beim Seeding: ${error}`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Seeding failed:', err)
|
||||
@@ -112,5 +166,16 @@ export function useComplianceHub() {
|
||||
}
|
||||
}
|
||||
|
||||
return { dashboard, regulations, mappings, findings, loading, error, seeding, loadData, seedDatabase }
|
||||
return {
|
||||
activeTab, setActiveTab,
|
||||
dashboard, regulations, mappings, findings,
|
||||
roadmap, moduleStatus, nextActions, scoreHistory,
|
||||
loading, error, seeding, savingSnapshot,
|
||||
evidenceDistribution, traceabilityMatrix, traceabilityLoading,
|
||||
traceabilityFilter, setTraceabilityFilter,
|
||||
traceabilityDomainFilter, setTraceabilityDomainFilter,
|
||||
expandedControls, expandedEvidence,
|
||||
loadData, saveSnapshot, seedDatabase,
|
||||
toggleControlExpanded, toggleEvidenceExpanded,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user