Files
breakpilot-compliance/admin-compliance/components/sdk/dsfa/AIUseCaseTabsPurposeAct.tsx
Sharang Parnerkar ada50f0466 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>
2026-04-17 09:16:21 +02:00

150 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
)
}