refactor(admin): split evidence, import, portfolio pages

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>
This commit is contained in:
Sharang Parnerkar
2026-04-16 13:07:04 +02:00
parent 9096aad693
commit 7907b3f25b
42 changed files with 3568 additions and 3591 deletions

View File

@@ -0,0 +1,184 @@
'use client'
import { useState, useEffect } from 'react'
import { useSDK, Risk, RiskLikelihood, RiskImpact, RiskSeverity, RiskStatus, RiskMitigation, calculateRiskScore, getRiskSeverityFromScore } from '@/lib/sdk'
import { RiskFormData } from '../_components/RiskForm'
export function useRisks() {
const { state, dispatch, addRisk } = useSDK()
const [showForm, setShowForm] = useState(false)
const [editingRisk, setEditingRisk] = useState<Risk | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [matrixFilter, setMatrixFilter] = useState<{ likelihood: number; impact: number } | null>(null)
useEffect(() => {
const fetchRisks = async () => {
try {
setLoading(true)
const res = await fetch('/api/sdk/v1/compliance/risks')
if (res.ok) {
const data = await res.json()
const backendRisks = data.risks || data
if (Array.isArray(backendRisks) && backendRisks.length > 0) {
const mapped: Risk[] = backendRisks.map((r: Record<string, unknown>) => ({
id: (r.risk_id || r.id || '') as string,
title: (r.title || '') as string,
description: (r.description || '') as string,
category: (r.category || 'technical') as string,
likelihood: (r.likelihood || 3) as RiskLikelihood,
impact: (r.impact || 3) as RiskImpact,
severity: ((r.inherent_risk || r.severity || 'MEDIUM') as string).toUpperCase() as RiskSeverity,
inherentRiskScore: (r.likelihood as number || 3) * (r.impact as number || 3),
residualRiskScore: (r.residual_likelihood as number || r.likelihood as number || 3) * (r.residual_impact as number || r.impact as number || 3),
status: (r.status || 'IDENTIFIED') as RiskStatus,
mitigation: (Array.isArray(r.mitigating_controls) ? (r.mitigating_controls as RiskMitigation[]) : []) as RiskMitigation[],
owner: (r.owner || null) as string | null,
relatedControls: [] as string[],
relatedRequirements: [] as string[],
}))
dispatch({ type: 'SET_STATE', payload: { risks: mapped } })
setError(null)
}
}
} catch {
// Backend unavailable — use SDK state as-is
} finally {
setLoading(false)
}
}
fetchRisks()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
const handleSubmit = async (data: RiskFormData) => {
const score = calculateRiskScore(data.likelihood, data.impact)
const severity = getRiskSeverityFromScore(score)
if (editingRisk) {
dispatch({
type: 'UPDATE_RISK',
payload: {
id: editingRisk.id,
data: { ...data, severity, inherentRiskScore: score, residualRiskScore: score },
},
})
try {
await fetch(`/api/sdk/v1/compliance/risks/${editingRisk.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: data.title,
description: data.description,
category: data.category,
likelihood: data.likelihood,
impact: data.impact,
}),
})
} catch {
// Silently fail
}
} else {
const riskId = `risk-${Date.now()}`
const newRisk: Risk = {
id: riskId,
...data,
severity,
inherentRiskScore: score,
residualRiskScore: score,
status: 'IDENTIFIED',
mitigation: [],
owner: null,
relatedControls: [],
relatedRequirements: [],
}
addRisk(newRisk)
try {
await fetch('/api/sdk/v1/compliance/risks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
risk_id: riskId,
title: data.title,
description: data.description,
category: data.category,
likelihood: data.likelihood,
impact: data.impact,
}),
})
} catch {
// Silently fail
}
}
setShowForm(false)
setEditingRisk(null)
}
const handleDelete = async (id: string) => {
if (!confirm('Moechten Sie dieses Risiko wirklich loeschen?')) return
dispatch({ type: 'DELETE_RISK', payload: id })
try {
await fetch(`/api/sdk/v1/compliance/risks/${id}`, { method: 'DELETE' })
} catch {
// Silently fail
}
}
const handleStatusChange = async (riskId: string, status: RiskStatus) => {
dispatch({ type: 'UPDATE_RISK', payload: { id: riskId, data: { status } } })
try {
await fetch(`/api/sdk/v1/compliance/risks/${riskId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status }),
})
} catch {
// Silently fail
}
}
const handleEdit = (risk: Risk) => {
setEditingRisk(risk)
setShowForm(true)
}
const handleMatrixCellClick = (l: number, i: number) => {
if (matrixFilter && matrixFilter.likelihood === l && matrixFilter.impact === i) {
setMatrixFilter(null)
} else {
setMatrixFilter({ likelihood: l, impact: i })
}
}
const stats = {
totalRisks: state.risks.length,
criticalRisks: state.risks.filter(r => r.severity === 'CRITICAL').length,
highRisks: state.risks.filter(r => r.severity === 'HIGH').length,
mitigatedRisks: state.risks.filter(r => r.mitigation.length > 0).length,
}
const filteredRisks = state.risks
.filter(risk => !matrixFilter || (risk.likelihood === matrixFilter.likelihood && risk.impact === matrixFilter.impact))
.sort((a, b) => b.inherentRiskScore - a.inherentRiskScore)
return {
state,
showForm,
setShowForm,
editingRisk,
setEditingRisk,
loading,
error,
setError,
matrixFilter,
setMatrixFilter,
handleSubmit,
handleDelete,
handleStatusChange,
handleEdit,
handleMatrixCellClick,
stats,
filteredRisks,
}
}