Extracted components and constants into _components/ subdirectories to bring all three pages under the 300 LOC soft target (was 651/628/612, now 255/232/278 LOC respectively). Zero behavior changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
117 lines
4.4 KiB
TypeScript
117 lines
4.4 KiB
TypeScript
'use client'
|
|
|
|
import { AISystem } from './types'
|
|
|
|
interface AISystemCardProps {
|
|
system: AISystem
|
|
onAssess: () => void
|
|
onEdit: () => void
|
|
onDelete: () => void
|
|
assessing: boolean
|
|
}
|
|
|
|
const classificationColors: Record<AISystem['classification'], string> = {
|
|
prohibited: 'bg-red-100 text-red-700 border-red-200',
|
|
'high-risk': 'bg-orange-100 text-orange-700 border-orange-200',
|
|
'limited-risk': 'bg-yellow-100 text-yellow-700 border-yellow-200',
|
|
'minimal-risk': 'bg-green-100 text-green-700 border-green-200',
|
|
unclassified: 'bg-gray-100 text-gray-500 border-gray-200',
|
|
}
|
|
|
|
const classificationLabels: Record<AISystem['classification'], string> = {
|
|
prohibited: 'Verboten',
|
|
'high-risk': 'Hochrisiko',
|
|
'limited-risk': 'Begrenztes Risiko',
|
|
'minimal-risk': 'Minimales Risiko',
|
|
unclassified: 'Nicht klassifiziert',
|
|
}
|
|
|
|
const statusColors: Record<AISystem['status'], string> = {
|
|
draft: 'bg-gray-100 text-gray-500',
|
|
classified: 'bg-blue-100 text-blue-700',
|
|
compliant: 'bg-green-100 text-green-700',
|
|
'non-compliant': 'bg-red-100 text-red-700',
|
|
}
|
|
|
|
const statusLabels: Record<AISystem['status'], string> = {
|
|
draft: 'Entwurf',
|
|
classified: 'Klassifiziert',
|
|
compliant: 'Konform',
|
|
'non-compliant': 'Nicht konform',
|
|
}
|
|
|
|
export function AISystemCard({ system, onAssess, onEdit, onDelete, assessing }: AISystemCardProps) {
|
|
return (
|
|
<div className={`bg-white rounded-xl border-2 p-6 ${
|
|
system.classification === 'high-risk' ? 'border-orange-200' :
|
|
system.classification === 'prohibited' ? 'border-red-200' : 'border-gray-200'
|
|
}`}>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<span className={`px-2 py-1 text-xs rounded-full ${classificationColors[system.classification]}`}>
|
|
{classificationLabels[system.classification]}
|
|
</span>
|
|
<span className={`px-2 py-1 text-xs rounded-full ${statusColors[system.status]}`}>
|
|
{statusLabels[system.status]}
|
|
</span>
|
|
</div>
|
|
<h3 className="text-lg font-semibold text-gray-900">{system.name}</h3>
|
|
<p className="text-sm text-gray-500 mt-1">{system.description}</p>
|
|
<div className="mt-2 text-sm text-gray-500">
|
|
<span>Sektor: {system.sector}</span>
|
|
{system.assessmentDate && (
|
|
<span className="ml-4">Klassifiziert: {system.assessmentDate.toLocaleDateString('de-DE')}</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{system.obligations.length > 0 && (
|
|
<div className="mt-4 pt-4 border-t border-gray-100">
|
|
<p className="text-sm font-medium text-gray-700 mb-2">Pflichten nach AI Act:</p>
|
|
<div className="flex flex-wrap gap-2">
|
|
{system.obligations.map(obl => (
|
|
<span key={obl} className="px-2 py-1 text-xs bg-purple-50 text-purple-700 rounded">
|
|
{obl}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{system.assessmentResult && (
|
|
<div className="mt-3 p-3 bg-blue-50 rounded-lg">
|
|
<p className="text-xs font-medium text-blue-700">KI-Risikobewertung abgeschlossen</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-4 flex items-center gap-2">
|
|
<button
|
|
onClick={onAssess}
|
|
disabled={assessing}
|
|
className="flex-1 px-4 py-2 text-sm bg-purple-50 text-purple-700 rounded-lg hover:bg-purple-100 transition-colors disabled:opacity-50"
|
|
>
|
|
{assessing ? (
|
|
<span className="flex items-center justify-center gap-2">
|
|
<svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
</svg>
|
|
Bewertung laeuft...
|
|
</span>
|
|
) : (
|
|
system.classification === 'unclassified' ? 'Klassifizierung starten' : 'Risikobewertung starten'
|
|
)}
|
|
</button>
|
|
<button onClick={onEdit} className="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">
|
|
Bearbeiten
|
|
</button>
|
|
<button onClick={onDelete} className="px-4 py-2 text-sm text-red-600 hover:bg-red-50 rounded-lg transition-colors">
|
|
Loeschen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|