Files
breakpilot-compliance/admin-compliance/components/sdk/dsfa/AIUseCaseTabsSystemData.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

262 lines
11 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,
AI_USE_CASE_TYPES,
} from '@/lib/sdk/dsfa/ai-use-case-types'
type UpdateFn = (updates: Partial<AIUseCaseModule>) => void
type AddToListFn = (field: keyof AIUseCaseModule, value: string, setter: (v: string) => void) => void
type RemoveFromListFn = (field: keyof AIUseCaseModule, idx: number) => void
// =============================================================================
// TAB 1: System
// =============================================================================
interface Tab1SystemProps {
module: AIUseCaseModule
update: UpdateFn
typeInfo: typeof AI_USE_CASE_TYPES[keyof typeof AI_USE_CASE_TYPES]
}
export function Tab1System({ module, update, typeInfo }: Tab1SystemProps) {
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Name des KI-Anwendungsfalls *</label>
<input
type="text"
value={module.name}
onChange={e => update({ name: e.target.value })}
placeholder={`z.B. ${typeInfo.label} für Kundenservice`}
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Systembeschreibung *</label>
<textarea
value={module.model_description}
onChange={e => update({ model_description: e.target.value })}
rows={4}
placeholder="Beschreiben Sie das KI-System: Funktionsweise, Input/Output, eingesetzte Algorithmen..."
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="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Modell-Typ</label>
<input
type="text"
value={module.model_type || ''}
onChange={e => update({ model_type: e.target.value })}
placeholder="z.B. Random Forest, GPT-4, CNN"
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Anbieter / Provider</label>
<input
type="text"
value={module.provider || ''}
onChange={e => update({ provider: e.target.value })}
placeholder="z.B. Anthropic, OpenAI, intern"
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Datenfluss-Beschreibung</label>
<textarea
value={module.data_flow_description || ''}
onChange={e => update({ data_flow_description: e.target.value })}
rows={3}
placeholder="Wie fließen Daten in das KI-System ein und aus? Gibt es Drittland-Transfers?"
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="flex items-center gap-3">
<input
type="checkbox"
id="third_country"
checked={module.third_country_transfer}
onChange={e => update({ third_country_transfer: e.target.checked })}
className="h-4 w-4 rounded border-gray-300 text-purple-600"
/>
<label htmlFor="third_country" className="text-sm text-gray-700">
Drittland-Transfer (außerhalb EU/EWR)
</label>
{module.third_country_transfer && (
<input
type="text"
value={module.provider_country || ''}
onChange={e => update({ provider_country: e.target.value })}
placeholder="Land (z.B. USA)"
className="ml-2 px-2 py-1 text-sm border border-orange-300 rounded focus:ring-2 focus:ring-orange-500"
/>
)}
</div>
</div>
)
}
// =============================================================================
// TAB 2: Daten & Betroffene
// =============================================================================
interface Tab2DataProps {
module: AIUseCaseModule
update: UpdateFn
newCategory: string
setNewCategory: (v: string) => void
newOutputCategory: string
setNewOutputCategory: (v: string) => void
newSubject: string
setNewSubject: (v: string) => void
addToList: AddToListFn
removeFromList: RemoveFromListFn
}
export function Tab2Data({
module, update,
newCategory, setNewCategory,
newOutputCategory, setNewOutputCategory,
newSubject, setNewSubject,
addToList, removeFromList,
}: Tab2DataProps) {
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Input-Datenkategorien *</label>
<div className="flex gap-2 mb-2">
<input
type="text"
value={newCategory}
onChange={e => setNewCategory(e.target.value)}
onKeyDown={e => e.key === 'Enter' && addToList('input_data_categories', newCategory, setNewCategory)}
placeholder="Datenkategorie hinzufügen..."
className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
/>
<button
onClick={() => addToList('input_data_categories', newCategory, setNewCategory)}
className="px-3 py-2 bg-purple-600 text-white rounded-lg text-sm hover:bg-purple-700"
>
+
</button>
</div>
<div className="flex flex-wrap gap-2">
{(module.input_data_categories || []).map((cat, i) => (
<span key={i} className="flex items-center gap-1 px-2 py-1 bg-purple-100 text-purple-700 rounded text-xs">
{cat}
<button onClick={() => removeFromList('input_data_categories', i)} className="hover:text-purple-900">×</button>
</span>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Output-Datenkategorien</label>
<div className="flex gap-2 mb-2">
<input
type="text"
value={newOutputCategory}
onChange={e => setNewOutputCategory(e.target.value)}
onKeyDown={e => e.key === 'Enter' && addToList('output_data_categories', newOutputCategory, setNewOutputCategory)}
placeholder="Output-Kategorie (z.B. Bewertung, Empfehlung)..."
className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
/>
<button
onClick={() => addToList('output_data_categories', newOutputCategory, setNewOutputCategory)}
className="px-3 py-2 bg-purple-600 text-white rounded-lg text-sm hover:bg-purple-700"
>
+
</button>
</div>
<div className="flex flex-wrap gap-2">
{(module.output_data_categories || []).map((cat, i) => (
<span key={i} className="flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">
{cat}
<button onClick={() => removeFromList('output_data_categories', i)} className="hover:text-blue-900">×</button>
</span>
))}
</div>
</div>
<div className="flex items-start gap-3 p-3 rounded-lg border border-gray-200">
<input
type="checkbox"
id="special_cats"
checked={module.involves_special_categories}
onChange={e => update({ involves_special_categories: e.target.checked })}
className="mt-1 h-4 w-4 rounded border-gray-300 text-purple-600"
/>
<label htmlFor="special_cats" className="flex-1">
<div className="text-sm font-medium text-gray-900">Besondere Kategorien (Art. 9 DSGVO)</div>
<p className="text-xs text-gray-500">Gesundheit, Biometrie, Religion, politische Meinung etc.</p>
{module.involves_special_categories && (
<textarea
value={module.special_categories_justification || ''}
onChange={e => update({ special_categories_justification: e.target.value })}
rows={2}
placeholder="Begründung nach Art. 9 Abs. 2 DSGVO..."
className="mt-2 w-full px-3 py-2 text-xs border border-orange-300 rounded focus:ring-2 focus:ring-orange-400 resize-none"
/>
)}
</label>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Betroffenengruppen *</label>
<div className="flex gap-2 mb-2">
<input
type="text"
value={newSubject}
onChange={e => setNewSubject(e.target.value)}
onKeyDown={e => e.key === 'Enter' && addToList('data_subjects', newSubject, setNewSubject)}
placeholder="z.B. Kunden, Mitarbeiter, Nutzer..."
className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
/>
<button
onClick={() => addToList('data_subjects', newSubject, setNewSubject)}
className="px-3 py-2 bg-purple-600 text-white rounded-lg text-sm hover:bg-purple-700"
>
+
</button>
</div>
<div className="flex flex-wrap gap-2">
{(module.data_subjects || []).map((s, i) => (
<span key={i} className="flex items-center gap-1 px-2 py-1 bg-green-100 text-green-700 rounded text-xs">
{s}
<button onClick={() => removeFromList('data_subjects', i)} className="hover:text-green-900">×</button>
</span>
))}
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Geschätztes Volumen</label>
<input
type="text"
value={module.estimated_volume || ''}
onChange={e => update({ estimated_volume: e.target.value })}
placeholder="z.B. >10.000 Personen/Monat"
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Aufbewahrungsdauer (Monate)</label>
<input
type="number"
value={module.data_retention_months || ''}
onChange={e => update({ data_retention_months: parseInt(e.target.value) || undefined })}
min={1}
placeholder="z.B. 24"
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
/>
</div>
</div>
</div>
)
}