1. BetrVG Obligations (JSON V2): 12 Pflichten basierend auf §87, §90, §94, §95, §99, §111 - BAG-Rechtsprechung referenziert (M365, SAP, Standardsoftware) - Applicability: DE + >=5 Mitarbeiter 2. Betriebsrats-Konflikt-Score (0-100): Gewichtete Formel aus 8 Faktoren - Ueberwachungseignung, HR-Bezug, Individualisierbarkeit, Automation - Escalation-Trigger: Score>=50 ohne BR → E2, Score>=75 → E3 3. Frontend: 3 neue Intake-Felder (Monitoring, HR, BR-Konsultation) - BR-Konflikt-Badge in Use-Case-Liste + Detail-Seite - Farbcodierung: gruen/gelb/orange/rot Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
172 lines
6.8 KiB
TypeScript
172 lines
6.8 KiB
TypeScript
'use client'
|
|
|
|
import { RiskScoreGauge } from './RiskScoreGauge'
|
|
|
|
interface AssessmentResult {
|
|
feasibility: string
|
|
risk_level: string
|
|
risk_score: number
|
|
complexity: string
|
|
dsfa_recommended: boolean
|
|
art22_risk: boolean
|
|
training_allowed: string
|
|
betrvg_conflict_score?: number
|
|
betrvg_consultation_required?: boolean
|
|
summary: string
|
|
recommendation: string
|
|
alternative_approach?: string
|
|
triggered_rules?: Array<{
|
|
rule_code: string
|
|
title: string
|
|
severity: string
|
|
gdpr_ref: string
|
|
}>
|
|
required_controls?: Array<{
|
|
id: string
|
|
title: string
|
|
description: string
|
|
effort: string
|
|
}>
|
|
recommended_architecture?: Array<{
|
|
id: string
|
|
title: string
|
|
description: string
|
|
benefit: string
|
|
}>
|
|
}
|
|
|
|
interface AssessmentResultCardProps {
|
|
result: AssessmentResult
|
|
}
|
|
|
|
const FEASIBILITY_STYLES: Record<string, { bg: string; text: string; label: string }> = {
|
|
YES: { bg: 'bg-green-100', text: 'text-green-700', label: 'Machbar' },
|
|
CONDITIONAL: { bg: 'bg-yellow-100', text: 'text-yellow-700', label: 'Bedingt machbar' },
|
|
NO: { bg: 'bg-red-100', text: 'text-red-700', label: 'Nicht empfohlen' },
|
|
}
|
|
|
|
const SEVERITY_STYLES: Record<string, string> = {
|
|
INFO: 'bg-blue-100 text-blue-700',
|
|
WARN: 'bg-yellow-100 text-yellow-700',
|
|
BLOCK: 'bg-red-100 text-red-700',
|
|
}
|
|
|
|
export function AssessmentResultCard({ result }: AssessmentResultCardProps) {
|
|
const feasibility = FEASIBILITY_STYLES[result.feasibility] || FEASIBILITY_STYLES.YES
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header with Score and Feasibility */}
|
|
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
|
<div className="flex items-start gap-6">
|
|
<RiskScoreGauge score={result.risk_score} riskLevel={result.risk_level} size="lg" />
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<span className={`px-3 py-1 rounded-full text-sm font-medium ${feasibility.bg} ${feasibility.text}`}>
|
|
{feasibility.label}
|
|
</span>
|
|
<span className="px-3 py-1 rounded-full text-sm bg-gray-100 text-gray-700">
|
|
Komplexitaet: {result.complexity}
|
|
</span>
|
|
{result.dsfa_recommended && (
|
|
<span className="px-3 py-1 rounded-full text-sm bg-orange-100 text-orange-700">
|
|
DSFA empfohlen
|
|
</span>
|
|
)}
|
|
{result.art22_risk && (
|
|
<span className="px-3 py-1 rounded-full text-sm bg-red-100 text-red-700">
|
|
Art. 22 Risiko
|
|
</span>
|
|
)}
|
|
{result.betrvg_conflict_score != null && result.betrvg_conflict_score > 0 && (
|
|
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
|
|
result.betrvg_conflict_score >= 75 ? 'bg-red-100 text-red-700' :
|
|
result.betrvg_conflict_score >= 50 ? 'bg-orange-100 text-orange-700' :
|
|
result.betrvg_conflict_score >= 25 ? 'bg-yellow-100 text-yellow-700' :
|
|
'bg-green-100 text-green-700'
|
|
}`}>
|
|
BR-Konflikt: {result.betrvg_conflict_score}/100
|
|
</span>
|
|
)}
|
|
{result.betrvg_consultation_required && (
|
|
<span className="px-3 py-1 rounded-full text-sm bg-purple-100 text-purple-700">
|
|
BR-Konsultation erforderlich
|
|
</span>
|
|
)}
|
|
</div>
|
|
<p className="text-gray-700">{result.summary}</p>
|
|
<p className="text-sm text-gray-500 mt-2">{result.recommendation}</p>
|
|
{result.alternative_approach && (
|
|
<div className="mt-3 p-3 bg-blue-50 rounded-lg text-sm text-blue-700">
|
|
<span className="font-medium">Alternative: </span>
|
|
{result.alternative_approach}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Triggered Rules */}
|
|
{result.triggered_rules && result.triggered_rules.length > 0 && (
|
|
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
|
Ausgeloeste Regeln ({result.triggered_rules.length})
|
|
</h3>
|
|
<div className="space-y-2">
|
|
{result.triggered_rules.map((rule) => (
|
|
<div key={rule.rule_code} className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
|
<span className={`px-2 py-1 text-xs rounded-full ${SEVERITY_STYLES[rule.severity] || 'bg-gray-100 text-gray-700'}`}>
|
|
{rule.severity}
|
|
</span>
|
|
<span className="text-xs text-gray-400 font-mono">{rule.rule_code}</span>
|
|
<span className="text-sm font-medium text-gray-800 flex-1">{rule.title}</span>
|
|
<span className="text-xs text-purple-600">{rule.gdpr_ref}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Required Controls */}
|
|
{result.required_controls && result.required_controls.length > 0 && (
|
|
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
|
Erforderliche Kontrollen ({result.required_controls.length})
|
|
</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
{result.required_controls.map((control) => (
|
|
<div key={control.id} className="p-4 border border-gray-200 rounded-lg">
|
|
<div className="flex items-center justify-between mb-1">
|
|
<span className="font-medium text-gray-900 text-sm">{control.title}</span>
|
|
<span className="px-2 py-0.5 text-xs bg-gray-100 text-gray-600 rounded">
|
|
{control.effort}
|
|
</span>
|
|
</div>
|
|
<p className="text-xs text-gray-500">{control.description}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Recommended Architecture Patterns */}
|
|
{result.recommended_architecture && result.recommended_architecture.length > 0 && (
|
|
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
|
Empfohlene Architektur-Patterns
|
|
</h3>
|
|
<div className="space-y-3">
|
|
{result.recommended_architecture.map((pattern) => (
|
|
<div key={pattern.id} className="p-4 bg-purple-50 border border-purple-200 rounded-lg">
|
|
<h4 className="font-medium text-purple-900">{pattern.title}</h4>
|
|
<p className="text-sm text-purple-700 mt-1">{pattern.description}</p>
|
|
<p className="text-xs text-purple-600 mt-2">Vorteil: {pattern.benefit}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|