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>
88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import { Risk } from '@/lib/sdk'
|
|
|
|
export function RiskMatrix({ risks, onCellClick }: { risks: Risk[]; onCellClick: (l: number, i: number) => void }) {
|
|
const matrix: Record<string, Risk[]> = {}
|
|
|
|
risks.forEach(risk => {
|
|
const key = `${risk.likelihood}-${risk.impact}`
|
|
if (!matrix[key]) matrix[key] = []
|
|
matrix[key].push(risk)
|
|
})
|
|
|
|
const getCellColor = (likelihood: number, impact: number): string => {
|
|
const score = likelihood * impact
|
|
if (score >= 20) return 'bg-red-500'
|
|
if (score >= 15) return 'bg-red-400'
|
|
if (score >= 12) return 'bg-orange-400'
|
|
if (score >= 8) return 'bg-yellow-400'
|
|
if (score >= 4) return 'bg-yellow-300'
|
|
return 'bg-green-400'
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">5x5 Risikomatrix</h3>
|
|
<div className="flex">
|
|
<div className="flex flex-col justify-center pr-2">
|
|
<div className="transform -rotate-90 whitespace-nowrap text-sm text-gray-500 font-medium">
|
|
Wahrscheinlichkeit
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1">
|
|
<div className="grid grid-cols-5 gap-1">
|
|
{[5, 4, 3, 2, 1].map(likelihood => (
|
|
<React.Fragment key={likelihood}>
|
|
{[1, 2, 3, 4, 5].map(impact => {
|
|
const key = `${likelihood}-${impact}`
|
|
const cellRisks = matrix[key] || []
|
|
return (
|
|
<button
|
|
key={key}
|
|
onClick={() => onCellClick(likelihood, impact)}
|
|
className={`aspect-square rounded-lg ${getCellColor(
|
|
likelihood,
|
|
impact
|
|
)} hover:opacity-80 transition-opacity relative`}
|
|
>
|
|
{cellRisks.length > 0 && (
|
|
<span className="absolute inset-0 flex items-center justify-center text-white font-bold text-lg">
|
|
{cellRisks.length}
|
|
</span>
|
|
)}
|
|
</button>
|
|
)
|
|
})}
|
|
</React.Fragment>
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-2 text-center text-sm text-gray-500 font-medium">Auswirkung</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6 flex items-center justify-center gap-4 text-sm">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 rounded bg-green-400" />
|
|
<span>Niedrig</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 rounded bg-yellow-400" />
|
|
<span>Mittel</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 rounded bg-orange-400" />
|
|
<span>Hoch</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 rounded bg-red-500" />
|
|
<span>Kritisch</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|