refactor(admin): split evidence, process-tasks, iace/hazards pages

Extract components and hooks into _components/ and _hooks/ subdirectories
to reduce each page.tsx to under 500 LOC (was 1545/1383/1316).

Final line counts: evidence=213, process-tasks=304, hazards=157.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-16 17:12:15 +02:00
parent e0c1d21879
commit 1fcd8244b1
27 changed files with 2621 additions and 4083 deletions

View File

@@ -4,13 +4,28 @@ import { useState, useEffect, useRef } from 'react'
import { useSDK, Evidence as SDKEvidence, EvidenceType } from '@/lib/sdk'
import {
DisplayEvidence,
EvidenceCheck,
CheckResult,
EvidenceMapping,
CoverageReport,
EvidenceTabKey,
mapEvidenceTypeToDisplay,
getEvidenceStatus,
evidenceTemplates,
CHECK_API,
} from '../_components/EvidenceTypes'
type AntiFakeMeta = Record<string, {
confidenceLevel: string | null
truthStatus: string | null
generationMode: string | null
approvalStatus: string | null
requiresFourEyes: boolean
}>
export function useEvidence() {
const { state, dispatch } = useSDK()
const [activeTab, setActiveTab] = useState<EvidenceTabKey>('evidence')
const [filter, setFilter] = useState<string>('all')
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
@@ -19,6 +34,25 @@ export function useEvidence() {
const [page, setPage] = useState(1)
const [pageSize] = useState(20)
const [total, setTotal] = useState(0)
const [antiFakeMeta, setAntiFakeMeta] = useState<AntiFakeMeta>({})
// Evidence Checks state
const [checks, setChecks] = useState<EvidenceCheck[]>([])
const [checksLoading, setChecksLoading] = useState(false)
const [runningCheckId, setRunningCheckId] = useState<string | null>(null)
const [checkResults, setCheckResults] = useState<Record<string, CheckResult[]>>({})
// Mappings state
const [mappings, setMappings] = useState<EvidenceMapping[]>([])
const [coverageReport, setCoverageReport] = useState<CoverageReport | null>(null)
const [seedingChecks, setSeedingChecks] = useState(false)
// Phase 3: Review/Reject/AuditTrail state
const [reviewEvidence, setReviewEvidence] = useState<DisplayEvidence | null>(null)
const [rejectEvidence, setRejectEvidence] = useState<DisplayEvidence | null>(null)
const [auditTrailId, setAuditTrailId] = useState<string | null>(null)
const [confidenceFilter, setConfidenceFilter] = useState<string | null>(null)
const [refreshKey, setRefreshKey] = useState(0)
useEffect(() => {
const fetchEvidence = async () => {
@@ -30,18 +64,30 @@ export function useEvidence() {
if (data.total !== undefined) setTotal(data.total)
const backendEvidence = data.evidence || data
if (Array.isArray(backendEvidence) && backendEvidence.length > 0) {
const mapped: SDKEvidence[] = backendEvidence.map((e: Record<string, unknown>) => ({
id: (e.id || '') as string,
controlId: (e.control_id || '') as string,
type: ((e.evidence_type || 'DOCUMENT') as string).toUpperCase() as EvidenceType,
name: (e.title || e.name || '') as string,
description: (e.description || '') as string,
fileUrl: (e.artifact_url || null) as string | null,
validFrom: e.valid_from ? new Date(e.valid_from as string) : new Date(),
validUntil: e.valid_until ? new Date(e.valid_until as string) : null,
uploadedBy: (e.uploaded_by || 'System') as string,
uploadedAt: e.created_at ? new Date(e.created_at as string) : new Date(),
}))
const metaMap: AntiFakeMeta = {}
const mapped: SDKEvidence[] = backendEvidence.map((e: Record<string, unknown>) => {
const id = (e.id || '') as string
metaMap[id] = {
confidenceLevel: (e.confidence_level || null) as string | null,
truthStatus: (e.truth_status || null) as string | null,
generationMode: (e.generation_mode || null) as string | null,
approvalStatus: (e.approval_status || null) as string | null,
requiresFourEyes: !!e.requires_four_eyes,
}
return {
id,
controlId: (e.control_id || '') as string,
type: ((e.evidence_type || 'DOCUMENT') as string).toUpperCase() as EvidenceType,
name: (e.title || e.name || '') as string,
description: (e.description || '') as string,
fileUrl: (e.artifact_url || null) as string | null,
validFrom: e.valid_from ? new Date(e.valid_from as string) : new Date(),
validUntil: e.valid_until ? new Date(e.valid_until as string) : null,
uploadedBy: (e.uploaded_by || 'System') as string,
uploadedAt: e.created_at ? new Date(e.created_at as string) : new Date(),
}
})
setAntiFakeMeta(metaMap)
dispatch({ type: 'SET_STATE', payload: { evidence: mapped } })
setError(null)
return
@@ -58,20 +104,16 @@ export function useEvidence() {
const loadFromTemplates = () => {
if (state.evidence.length > 0) return
if (state.controls.length === 0) return
const relevantEvidence = evidenceTemplates.filter(e =>
state.controls.some(c => c.id === e.controlId || e.linkedControls.includes(c.id))
)
const now = new Date()
relevantEvidence.forEach(template => {
const validFrom = new Date(now)
validFrom.setMonth(validFrom.getMonth() - 1)
const validUntil = template.validityDays > 0
? new Date(validFrom.getTime() + template.validityDays * 24 * 60 * 60 * 1000)
: null
const sdkEvidence: SDKEvidence = {
id: template.id,
controlId: template.controlId,
@@ -89,10 +131,11 @@ export function useEvidence() {
}
fetchEvidence()
}, [page, pageSize]) // eslint-disable-line react-hooks/exhaustive-deps
}, [page, pageSize, refreshKey]) // eslint-disable-line react-hooks/exhaustive-deps
const displayEvidence: DisplayEvidence[] = state.evidence.map(ev => {
const template = evidenceTemplates.find(t => t.id === ev.id)
const meta = antiFakeMeta[ev.id]
return {
id: ev.id,
name: ev.name,
@@ -109,12 +152,18 @@ export function useEvidence() {
status: getEvidenceStatus(ev.validUntil),
fileSize: template?.fileSize || 'Unbekannt',
fileUrl: ev.fileUrl,
confidenceLevel: meta?.confidenceLevel || null,
truthStatus: meta?.truthStatus || null,
generationMode: meta?.generationMode || null,
approvalStatus: meta?.approvalStatus || null,
requiresFourEyes: meta?.requiresFourEyes || false,
}
})
const filteredEvidence = filter === 'all'
const filteredEvidence = (filter === 'all'
? displayEvidence
: displayEvidence.filter(e => e.status === filter || e.displayType === filter)
).filter(e => !confidenceFilter || e.confidenceLevel === confidenceFilter)
const validCount = displayEvidence.filter(e => e.status === 'valid').length
const expiredCount = displayEvidence.filter(e => e.status === 'expired').length
@@ -125,9 +174,7 @@ export function useEvidence() {
dispatch({ type: 'DELETE_EVIDENCE', payload: evidenceId })
try {
await fetch(`/api/sdk/v1/compliance/evidence/${evidenceId}`, { method: 'DELETE' })
} catch {
// Silently fail — SDK state is already updated
}
} catch { /* Silently fail */ }
}
const handleUpload = async (file: File) => {
@@ -135,17 +182,10 @@ export function useEvidence() {
setError(null)
try {
const controlId = state.controls.length > 0 ? state.controls[0].id : 'GENERIC'
const params = new URLSearchParams({
control_id: controlId,
evidence_type: 'document',
title: file.name,
})
const params = new URLSearchParams({ control_id: controlId, evidence_type: 'document', title: file.name })
const formData = new FormData()
formData.append('file', file)
const res = await fetch(`/api/sdk/v1/compliance/evidence/upload?${params}`, {
method: 'POST',
body: formData,
})
const res = await fetch(`/api/sdk/v1/compliance/evidence/upload?${params}`, { method: 'POST', body: formData })
if (!res.ok) {
const errData = await res.json().catch(() => ({ error: 'Upload fehlgeschlagen' }))
throw new Error(errData.error || errData.detail || 'Upload fehlgeschlagen')
@@ -153,7 +193,7 @@ export function useEvidence() {
const data = await res.json()
const newEvidence: SDKEvidence = {
id: data.id || `ev-${Date.now()}`,
controlId: controlId,
controlId,
type: 'DOCUMENT',
name: file.name,
description: `Hochgeladen am ${new Date().toLocaleDateString('de-DE')}`,
@@ -172,11 +212,8 @@ export function useEvidence() {
}
const handleView = (ev: DisplayEvidence) => {
if (ev.fileUrl) {
window.open(ev.fileUrl, '_blank')
} else {
alert('Keine Datei vorhanden')
}
if (ev.fileUrl) window.open(ev.fileUrl, '_blank')
else alert('Keine Datei vorhanden')
}
const handleDownload = (ev: DisplayEvidence) => {
@@ -189,20 +226,73 @@ export function useEvidence() {
document.body.removeChild(a)
}
const handleUploadClick = () => {
fileInputRef.current?.click()
}
const handleUploadClick = () => fileInputRef.current?.click()
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file) {
handleUpload(file)
e.target.value = ''
}
if (file) { handleUpload(file); e.target.value = '' }
}
const loadChecks = async () => {
setChecksLoading(true)
try {
const res = await fetch(`${CHECK_API}?limit=50`)
if (res.ok) { const data = await res.json(); setChecks(data.checks || []) }
} catch { /* silent */ }
finally { setChecksLoading(false) }
}
const runCheck = async (checkId: string) => {
setRunningCheckId(checkId)
try {
const res = await fetch(`${CHECK_API}/${checkId}/run`, { method: 'POST' })
if (res.ok) {
const result = await res.json()
setCheckResults(prev => ({ ...prev, [checkId]: [result, ...(prev[checkId] || [])].slice(0, 5) }))
loadChecks()
}
} catch { /* silent */ }
finally { setRunningCheckId(null) }
}
const loadCheckResults = async (checkId: string) => {
try {
const res = await fetch(`${CHECK_API}/${checkId}/results?limit=5`)
if (res.ok) { const data = await res.json(); setCheckResults(prev => ({ ...prev, [checkId]: data.results || [] })) }
} catch { /* silent */ }
}
const seedChecks = async () => {
setSeedingChecks(true)
try { await fetch(`${CHECK_API}/seed`, { method: 'POST' }); loadChecks() }
catch { /* silent */ }
finally { setSeedingChecks(false) }
}
const loadMappings = async () => {
try {
const res = await fetch(`${CHECK_API}/mappings`)
if (res.ok) { const data = await res.json(); setMappings(data.mappings || []) }
} catch { /* silent */ }
}
const loadCoverageReport = async () => {
try {
const res = await fetch(`${CHECK_API}/mappings/report`)
if (res.ok) setCoverageReport(await res.json())
} catch { /* silent */ }
}
useEffect(() => {
if (activeTab === 'checks' && checks.length === 0) loadChecks()
if (activeTab === 'mapping') { loadMappings(); loadCoverageReport() }
if (activeTab === 'report') loadCoverageReport()
}, [activeTab]) // eslint-disable-line react-hooks/exhaustive-deps
return {
state,
activeTab,
setActiveTab,
filter,
setFilter,
loading,
@@ -219,10 +309,29 @@ export function useEvidence() {
validCount,
expiredCount,
pendingCount,
confidenceFilter,
setConfidenceFilter,
reviewEvidence,
setReviewEvidence,
rejectEvidence,
setRejectEvidence,
auditTrailId,
setAuditTrailId,
setRefreshKey,
checks,
checksLoading,
checkResults,
runningCheckId,
seedingChecks,
mappings,
coverageReport,
handleDelete,
handleView,
handleDownload,
handleUploadClick,
handleFileChange,
runCheck,
loadCheckResults,
seedChecks,
}
}