Files
breakpilot-compliance/admin-compliance/app/sdk/risks/_components/RiskCard.tsx
Sharang Parnerkar 7907b3f25b 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>
2026-04-16 13:07:04 +02:00

153 lines
5.8 KiB
TypeScript

'use client'
import { useState } from 'react'
import { Risk, RiskStatus } from '@/lib/sdk'
export function RiskCard({
risk,
onEdit,
onDelete,
onStatusChange,
}: {
risk: Risk
onEdit: () => void
onDelete: () => void
onStatusChange: (status: RiskStatus) => void
}) {
const [showMitigations, setShowMitigations] = useState(false)
const severityColors = {
CRITICAL: 'border-red-200 bg-red-50',
HIGH: 'border-orange-200 bg-orange-50',
MEDIUM: 'border-yellow-200 bg-yellow-50',
LOW: 'border-green-200 bg-green-50',
}
return (
<div className={`bg-white rounded-xl border-2 p-6 ${severityColors[risk.severity]}`}>
<div className="flex items-start justify-between">
<div>
<div className="flex items-center gap-2">
<h4 className="font-semibold text-gray-900">{risk.title}</h4>
<span
className={`px-2 py-0.5 text-xs rounded-full ${
risk.severity === 'CRITICAL'
? 'bg-red-100 text-red-700'
: risk.severity === 'HIGH'
? 'bg-orange-100 text-orange-700'
: risk.severity === 'MEDIUM'
? 'bg-yellow-100 text-yellow-700'
: 'bg-green-100 text-green-700'
}`}
>
{risk.severity}
</span>
</div>
<p className="text-sm text-gray-500 mt-1">{risk.description}</p>
</div>
<div className="flex items-center gap-2">
<button
onClick={onEdit}
className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
</button>
<button
onClick={onDelete}
className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
</div>
</div>
<div className="mt-4 grid grid-cols-4 gap-4 text-sm">
<div>
<span className="text-gray-500">Wahrscheinlichkeit:</span>
<span className="ml-2 font-medium">{risk.likelihood}/5</span>
</div>
<div>
<span className="text-gray-500">Auswirkung:</span>
<span className="ml-2 font-medium">{risk.impact}/5</span>
</div>
<div>
<span className="text-gray-500">Inherent:</span>
<span className="ml-2 font-medium">{risk.inherentRiskScore}</span>
</div>
<div>
<span className="text-gray-500">Residual:</span>
<span className={`ml-2 font-medium ${
risk.residualRiskScore < risk.inherentRiskScore ? 'text-green-600' : ''
}`}>
{risk.residualRiskScore}
</span>
{risk.residualRiskScore < risk.inherentRiskScore && (
<span className="ml-1 text-xs text-green-600">
({risk.inherentRiskScore} &rarr; {risk.residualRiskScore})
</span>
)}
</div>
</div>
<div className="mt-4 pt-4 border-t border-gray-200 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">Status:</span>
<select
value={risk.status}
onChange={(e) => onStatusChange(e.target.value as RiskStatus)}
className="px-2 py-1 text-sm border border-gray-300 rounded-lg"
>
<option value="IDENTIFIED">Identifiziert</option>
<option value="ASSESSED">Bewertet</option>
<option value="MITIGATED">Mitigiert</option>
<option value="ACCEPTED">Akzeptiert</option>
<option value="CLOSED">Geschlossen</option>
</select>
</div>
{risk.mitigation.length > 0 && (
<button
onClick={() => setShowMitigations(!showMitigations)}
className="text-sm text-purple-600 hover:text-purple-700"
>
{showMitigations ? 'Mitigationen ausblenden' : `${risk.mitigation.length} Mitigation(en) anzeigen`}
</button>
)}
</div>
{showMitigations && risk.mitigation.length > 0 && (
<div className="mt-3 space-y-2">
{risk.mitigation.map((m, idx) => (
<div key={idx} className="p-3 bg-gray-50 rounded-lg text-sm">
<div className="flex items-center justify-between">
<span className="font-medium text-gray-700">{m.controlId || `Mitigation ${idx + 1}`}</span>
<span className={`px-2 py-0.5 text-xs rounded-full ${
m.status === 'IMPLEMENTED' ? 'bg-green-100 text-green-700' :
m.status === 'IN_PROGRESS' ? 'bg-yellow-100 text-yellow-700' :
'bg-gray-100 text-gray-500'
}`}>
{m.status === 'IMPLEMENTED' ? 'Implementiert' :
m.status === 'IN_PROGRESS' ? 'In Bearbeitung' : m.status || 'Geplant'}
</span>
</div>
{m.description && <p className="text-gray-500 mt-1">{m.description}</p>}
</div>
))}
</div>
)}
</div>
)
}