refactor(admin): split AIUseCaseModuleEditor, DataPointCatalog, ProjectSelector components
AIUseCaseModuleEditor (698 LOC) → thin orchestrator (187) + constants (29) + barrel tabs (4) + tabs implementation split into SystemData (261), PurposeAct (149), RisksReview (219). DataPointCatalog (658 LOC) → main (291) + helpers (190) + CategoryGroup (124) + Row (108). ProjectSelector (656 LOC) → main (211) + CreateProjectDialog (169) + ProjectActionDialog (140) + ProjectCard (128). All files now under 300 LOC soft target and 500 LOC hard cap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
149
admin-compliance/components/sdk/dsfa/AIUseCaseTabsPurposeAct.tsx
Normal file
149
admin-compliance/components/sdk/dsfa/AIUseCaseTabsPurposeAct.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
AIUseCaseModule,
|
||||
AIActRiskClass,
|
||||
AI_ACT_RISK_CLASSES,
|
||||
} from '@/lib/sdk/dsfa/ai-use-case-types'
|
||||
import { Art22AssessmentPanel } from './Art22AssessmentPanel'
|
||||
import { AIRiskCriteriaChecklist } from './AIRiskCriteriaChecklist'
|
||||
import { LEGAL_BASES } from './AIUseCaseEditorConstants'
|
||||
|
||||
type UpdateFn = (updates: Partial<AIUseCaseModule>) => void
|
||||
|
||||
// =============================================================================
|
||||
// TAB 3: Zweck & Art. 22
|
||||
// =============================================================================
|
||||
|
||||
interface Tab3PurposeProps {
|
||||
module: AIUseCaseModule
|
||||
update: UpdateFn
|
||||
art22Required: boolean
|
||||
}
|
||||
|
||||
export function Tab3Purpose({ module, update, art22Required }: Tab3PurposeProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Verarbeitungszweck *</label>
|
||||
<textarea
|
||||
value={module.processing_purpose}
|
||||
onChange={e => update({ processing_purpose: e.target.value })}
|
||||
rows={3}
|
||||
placeholder="Welchem Zweck dient dieses KI-System? Was soll erreicht werden?"
|
||||
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Rechtsgrundlage *</label>
|
||||
<select
|
||||
value={module.legal_basis}
|
||||
onChange={e => update({ legal_basis: e.target.value })}
|
||||
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
>
|
||||
<option value="">Bitte wählen...</option>
|
||||
{LEGAL_BASES.map(lb => (
|
||||
<option key={lb} value={lb}>{lb}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{module.legal_basis && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Details zur Rechtsgrundlage</label>
|
||||
<textarea
|
||||
value={module.legal_basis_details || ''}
|
||||
onChange={e => update({ legal_basis_details: e.target.value })}
|
||||
rows={2}
|
||||
placeholder="Ergänzende Erläuterung zur Rechtsgrundlage..."
|
||||
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="border-t border-gray-200 pt-4">
|
||||
<h4 className="text-sm font-semibold text-gray-900 mb-3">
|
||||
Art. 22 DSGVO – Automatisierte Einzelentscheidung
|
||||
{art22Required && (
|
||||
<span className="ml-2 text-xs px-1.5 py-0.5 bg-red-100 text-red-700 rounded">
|
||||
Wahrscheinlich anwendbar
|
||||
</span>
|
||||
)}
|
||||
</h4>
|
||||
<Art22AssessmentPanel
|
||||
assessment={module.art22_assessment}
|
||||
onChange={a22 => update({ art22_assessment: a22 })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TAB 4: KI-Kriterien & AI Act
|
||||
// =============================================================================
|
||||
|
||||
interface Tab4AIActProps {
|
||||
module: AIUseCaseModule
|
||||
update: UpdateFn
|
||||
}
|
||||
|
||||
export function Tab4AIAct({ module, update }: Tab4AIActProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-gray-900 mb-3">WP248-Risikokriterien</h4>
|
||||
<AIRiskCriteriaChecklist
|
||||
criteria={module.risk_criteria}
|
||||
onChange={criteria => update({ risk_criteria: criteria })}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-t border-gray-200 pt-4">
|
||||
<h4 className="text-sm font-semibold text-gray-900 mb-3">EU AI Act – Risikoklasse</h4>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{(Object.entries(AI_ACT_RISK_CLASSES) as [AIActRiskClass, typeof AI_ACT_RISK_CLASSES[AIActRiskClass]][]).map(([cls, info]) => (
|
||||
<button
|
||||
key={cls}
|
||||
onClick={() => update({ ai_act_risk_class: cls })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
module.ai_act_risk_class === cls
|
||||
? cls === 'unacceptable' ? 'border-red-500 bg-red-50'
|
||||
: cls === 'high_risk' ? 'border-orange-500 bg-orange-50'
|
||||
: cls === 'limited' ? 'border-yellow-500 bg-yellow-50'
|
||||
: 'border-green-500 bg-green-50'
|
||||
: 'border-gray-200 bg-white hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="font-medium text-sm text-gray-900">{info.labelDE}</div>
|
||||
<p className="text-xs text-gray-500 mt-0.5 line-clamp-2">{info.description}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{module.ai_act_risk_class && (
|
||||
<div className="mt-3">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Begründung der Klassifizierung</label>
|
||||
<textarea
|
||||
value={module.ai_act_justification || ''}
|
||||
onChange={e => update({ ai_act_justification: e.target.value })}
|
||||
rows={2}
|
||||
placeholder="Warum wurde diese Risikoklasse gewählt?"
|
||||
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{module.ai_act_risk_class && AI_ACT_RISK_CLASSES[module.ai_act_risk_class].requirements.length > 0 && (
|
||||
<div className="mt-3 p-3 bg-gray-50 rounded-lg">
|
||||
<div className="text-xs font-medium text-gray-700 mb-2">Anforderungen dieser Klasse:</div>
|
||||
<ul className="space-y-1">
|
||||
{AI_ACT_RISK_CLASSES[module.ai_act_risk_class].requirements.map((req, i) => (
|
||||
<li key={i} className="text-xs text-gray-600 flex items-start gap-1.5">
|
||||
<span className="text-purple-500 flex-shrink-0">•</span>
|
||||
{req}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user