Files
breakpilot-compliance/admin-compliance/components/sdk/dsfa/index.ts
Benjamin Admin 308d559c85
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 38s
CI / test-python-backend-compliance (push) Successful in 33s
CI / test-python-document-crawler (push) Successful in 24s
CI / test-python-dsms-gateway (push) Successful in 19s
feat: DSFA Section 8 KI-Anwendungsfälle + Bundesland RAG-Ingest
- Migration 028: ai_use_case_modules JSONB + section_8_complete auf compliance_dsfas
- Neues ai-use-case-types.ts: AIUseCaseModule Interface, 8 Typen, Art22Assessment,
  AI Act Risikoklassen, WP248-Kriterien, Privacy by Design, createEmptyModule() Helper
- types.ts: Section 8 in DSFA_SECTIONS, ai_use_case_modules im DSFA Interface,
  section_8_complete in DSFASectionProgress
- api.ts: addAIUseCaseModule, updateAIUseCaseModule, removeAIUseCaseModule
- 5 neue UI-Komponenten: AIUseCaseTypeSelector, Art22AssessmentPanel,
  AIRiskCriteriaChecklist, AIUseCaseModuleEditor (7 Tabs), AIUseCaseSection
- DSFASidebar: Section 8 Eintrag + calculateSectionProgress case 8
- ReviewScheduleSection: ai_use_case_module Trigger-Typ ergänzt
- page.tsx: Section 8 Rendering + Weiter-Button auf activeSection < 8 + KI-Module Counter
- scripts/ingest-dsfa-bundesland.sh: WP248 + alle 17 Behörden → bp_dsfa_corpus
- Docs: dsfa.md Section 8 + RAG-Corpus, Developer Portal DSFA mit AI-Modul-Code-Beispielen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 09:20:27 +01:00

228 lines
9.4 KiB
TypeScript

'use client'
import React from 'react'
// =============================================================================
// RE-EXPORTS FROM SEPARATE FILES
// =============================================================================
export { ThresholdAnalysisSection } from './ThresholdAnalysisSection'
export { DSFASidebar } from './DSFASidebar'
export { StakeholderConsultationSection } from './StakeholderConsultationSection'
export { Art36Warning } from './Art36Warning'
export { ReviewScheduleSection } from './ReviewScheduleSection'
export { SourceAttribution, InlineSourceRef, AttributionFooter } from './SourceAttribution'
export { AIUseCaseSection } from './AIUseCaseSection'
export { AIUseCaseModuleEditor } from './AIUseCaseModuleEditor'
export { AIUseCaseTypeSelector } from './AIUseCaseTypeSelector'
export { Art22AssessmentPanel } from './Art22AssessmentPanel'
export { AIRiskCriteriaChecklist } from './AIRiskCriteriaChecklist'
// =============================================================================
// DSFA Card Component
// =============================================================================
interface DSFACardProps {
dsfa: {
id: string
title: string
status: string
risk_level?: string
created_at?: string
updated_at?: string
processing_description?: string
}
onDelete?: (id: string) => void
onExport?: (id: string) => void
}
export function DSFACard({ dsfa, onDelete, onExport }: DSFACardProps) {
const statusColors: Record<string, string> = {
draft: 'bg-slate-100 text-slate-700',
in_progress: 'bg-blue-100 text-blue-700',
review: 'bg-amber-100 text-amber-700',
approved: 'bg-green-100 text-green-700',
rejected: 'bg-red-100 text-red-700',
}
return React.createElement('div', {
className: 'bg-white rounded-xl border border-slate-200 p-5 hover:shadow-md transition-shadow'
},
React.createElement('div', { className: 'flex items-start justify-between mb-3' },
React.createElement('h3', { className: 'font-semibold text-slate-900 text-lg' }, dsfa.title),
React.createElement('span', {
className: `px-2.5 py-1 rounded-full text-xs font-medium ${statusColors[dsfa.status] || statusColors.draft}`
}, dsfa.status)
),
dsfa.processing_description && React.createElement('p', {
className: 'text-sm text-slate-500 mb-4 line-clamp-2'
}, dsfa.processing_description),
React.createElement('div', { className: 'flex items-center gap-2' },
React.createElement('a', {
href: `/sdk/dsfa/${dsfa.id}`,
className: 'px-3 py-1.5 bg-primary-600 text-white rounded-lg text-sm hover:bg-primary-700 transition-colors'
}, 'Bearbeiten'),
onExport && React.createElement('button', {
onClick: () => onExport(dsfa.id),
className: 'px-3 py-1.5 bg-slate-100 text-slate-700 rounded-lg text-sm hover:bg-slate-200 transition-colors'
}, 'Export'),
onDelete && React.createElement('button', {
onClick: () => onDelete(dsfa.id),
className: 'px-3 py-1.5 text-red-600 rounded-lg text-sm hover:bg-red-50 transition-colors'
}, 'Loeschen')
)
)
}
// =============================================================================
// Risk Matrix Component
// =============================================================================
// DSFARisk type matching lib/sdk/dsfa/types.ts
interface DSFARiskInput {
id: string
category?: string
description: string
likelihood: 'low' | 'medium' | 'high'
impact: 'low' | 'medium' | 'high'
risk_level?: string
affected_data?: string[]
}
interface RiskMatrixProps {
risks: DSFARiskInput[]
onRiskSelect?: (risk: DSFARiskInput) => void
onRiskClick?: (riskId: string) => void
onAddRisk?: (likelihood: 'low' | 'medium' | 'high', impact: 'low' | 'medium' | 'high') => void
selectedRiskId?: string
readOnly?: boolean
}
export function RiskMatrix({ risks, onRiskSelect, onRiskClick, onAddRisk, selectedRiskId, readOnly }: RiskMatrixProps) {
const likelihoodLevels: Array<'low' | 'medium' | 'high'> = ['high', 'medium', 'low']
const impactLevels: Array<'low' | 'medium' | 'high'> = ['low', 'medium', 'high']
const levelLabels = { low: 'Niedrig', medium: 'Mittel', high: 'Hoch' }
const cellColors: Record<string, string> = {
low: 'bg-green-100 hover:bg-green-200',
medium: 'bg-yellow-100 hover:bg-yellow-200',
high: 'bg-orange-100 hover:bg-orange-200',
very_high: 'bg-red-100 hover:bg-red-200',
}
const getRiskColor = (likelihood: 'low' | 'medium' | 'high', impact: 'low' | 'medium' | 'high') => {
const matrix: Record<string, Record<string, string>> = {
low: { low: 'low', medium: 'low', high: 'medium' },
medium: { low: 'low', medium: 'medium', high: 'high' },
high: { low: 'medium', medium: 'high', high: 'very_high' },
}
return cellColors[matrix[likelihood]?.[impact] || 'medium']
}
const handleCellClick = (likelihood: 'low' | 'medium' | 'high', impact: 'low' | 'medium' | 'high') => {
const cellRisks = risks.filter(r => r.likelihood === likelihood && r.impact === impact)
if (cellRisks.length > 0 && onRiskSelect) {
onRiskSelect(cellRisks[0])
} else if (cellRisks.length > 0 && onRiskClick) {
onRiskClick(cellRisks[0].id)
} else if (!readOnly && onAddRisk) {
onAddRisk(likelihood, impact)
}
}
return React.createElement('div', { className: 'bg-white rounded-xl border border-slate-200 p-5' },
React.createElement('h3', { className: 'font-semibold text-slate-900 mb-4' }, 'Risikomatrix'),
React.createElement('div', { className: 'text-xs text-slate-500 mb-2' }, 'Eintrittswahrscheinlichkeit ↑ | Schwere →'),
React.createElement('div', { className: 'grid grid-cols-4 gap-1' },
// Header row
React.createElement('div'),
...impactLevels.map(i => React.createElement('div', {
key: `h-${i}`,
className: 'text-center text-xs text-slate-500 py-1'
}, levelLabels[i])),
// Grid rows
...likelihoodLevels.map(likelihood =>
[
React.createElement('div', {
key: `l-${likelihood}`,
className: 'text-right text-xs text-slate-500 pr-2 flex items-center justify-end'
}, levelLabels[likelihood]),
...impactLevels.map(impact => {
const cellRisks = risks.filter(r => r.likelihood === likelihood && r.impact === impact)
const isSelected = cellRisks.some(r => r.id === selectedRiskId)
return React.createElement('div', {
key: `${likelihood}-${impact}`,
className: `aspect-square rounded ${getRiskColor(likelihood, impact)} flex items-center justify-center text-xs font-medium cursor-pointer ${isSelected ? 'ring-2 ring-purple-500' : ''}`,
onClick: () => handleCellClick(likelihood, impact)
}, cellRisks.length > 0 ? String(cellRisks.length) : (readOnly ? '' : '+'))
})
]
).flat()
)
)
}
// =============================================================================
// Approval Panel Component
// =============================================================================
interface ApprovalPanelProps {
dsfa: {
id: string
status: string
approved_by?: string
approved_at?: string
rejection_reason?: string
}
onApprove?: () => void
onReject?: (reason: string) => void
}
export function ApprovalPanel({ dsfa, onApprove, onReject }: ApprovalPanelProps) {
const [rejectionReason, setRejectionReason] = React.useState('')
const [showRejectForm, setShowRejectForm] = React.useState(false)
if (dsfa.status === 'approved') {
return React.createElement('div', {
className: 'bg-green-50 border border-green-200 rounded-xl p-5'
},
React.createElement('div', { className: 'flex items-center gap-2 mb-2' },
React.createElement('span', { className: 'text-green-600 text-lg' }, '\u2713'),
React.createElement('h3', { className: 'font-semibold text-green-800' }, 'DSFA genehmigt')
),
dsfa.approved_by && React.createElement('p', { className: 'text-sm text-green-700' },
`Genehmigt von: ${dsfa.approved_by}`
)
)
}
return React.createElement('div', {
className: 'bg-white rounded-xl border border-slate-200 p-5'
},
React.createElement('h3', { className: 'font-semibold text-slate-900 mb-4' }, 'Freigabe'),
React.createElement('div', { className: 'flex gap-3' },
onApprove && React.createElement('button', {
onClick: onApprove,
className: 'px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 transition-colors'
}, 'Genehmigen'),
onReject && React.createElement('button', {
onClick: () => setShowRejectForm(true),
className: 'px-4 py-2 bg-red-600 text-white rounded-lg text-sm hover:bg-red-700 transition-colors'
}, 'Ablehnen')
),
showRejectForm && React.createElement('div', { className: 'mt-4' },
React.createElement('textarea', {
value: rejectionReason,
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => setRejectionReason(e.target.value),
placeholder: 'Ablehnungsgrund...',
className: 'w-full p-3 border border-slate-200 rounded-lg text-sm resize-none',
rows: 3
}),
React.createElement('button', {
onClick: () => { onReject?.(rejectionReason); setShowRejectForm(false) },
className: 'mt-2 px-4 py-2 bg-red-600 text-white rounded-lg text-sm hover:bg-red-700'
}, 'Ablehnung senden')
)
)
}