This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/admin-v2/components/sdk/ValidationReport.tsx
BreakPilot Dev 206183670d feat(sdk): Add Drafting Engine with 4-mode agent system (Explain/Ask/Draft/Validate)
Extends the Compliance Advisor from a Q&A chatbot into a full drafting engine
that can generate, validate, and refine compliance documents within Scope Engine
constraints. Includes intent classifier, state projector, constraint enforcer,
SOUL templates, Go backend endpoints, and React UI components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 12:37:18 +01:00

221 lines
8.9 KiB
TypeScript

'use client'
/**
* ValidationReport - Strukturierte Anzeige von Validierungsergebnissen
*
* Errors (Scope-Violations) in Rot
* Warnings (Inkonsistenzen) in Amber
* Suggestions in Blau
*/
import { DOCUMENT_TYPE_LABELS } from '@/lib/sdk/compliance-scope-types'
import type { ValidationResult, ValidationFinding } from '@/lib/sdk/drafting-engine/types'
interface ValidationReportProps {
result: ValidationResult
onClose: () => void
/** Compact mode for inline display in widget */
compact?: boolean
}
const SEVERITY_CONFIG = {
error: {
bg: 'bg-red-50',
border: 'border-red-200',
text: 'text-red-700',
icon: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z',
label: 'Fehler',
dotColor: 'bg-red-500',
},
warning: {
bg: 'bg-amber-50',
border: 'border-amber-200',
text: 'text-amber-700',
icon: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z',
label: 'Warnungen',
dotColor: 'bg-amber-500',
},
suggestion: {
bg: 'bg-blue-50',
border: 'border-blue-200',
text: 'text-blue-700',
icon: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
label: 'Vorschlaege',
dotColor: 'bg-blue-500',
},
}
function FindingCard({ finding, compact }: { finding: ValidationFinding; compact?: boolean }) {
const config = SEVERITY_CONFIG[finding.severity]
const docLabel = DOCUMENT_TYPE_LABELS[finding.documentType]?.split(' (')[0] || finding.documentType
if (compact) {
return (
<div className={`flex items-start gap-2 px-2.5 py-1.5 ${config.bg} rounded-md border ${config.border}`}>
<span className={`w-1.5 h-1.5 rounded-full mt-1.5 shrink-0 ${config.dotColor}`} />
<div className="min-w-0">
<p className={`text-xs font-medium ${config.text}`}>{finding.title}</p>
<p className="text-xs text-gray-500 truncate">{finding.description}</p>
</div>
</div>
)
}
return (
<div className={`${config.bg} rounded-lg border ${config.border} p-3`}>
<div className="flex items-start gap-2">
<svg className={`w-4 h-4 mt-0.5 shrink-0 ${config.text}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={config.icon} />
</svg>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className={`text-sm font-medium ${config.text}`}>{finding.title}</h4>
<span className="text-xs text-gray-400 bg-gray-100 px-1.5 py-0.5 rounded">{docLabel}</span>
</div>
<p className="text-xs text-gray-600 mt-1">{finding.description}</p>
{finding.crossReferenceType && (
<p className="text-xs text-gray-500 mt-1">
Cross-Referenz: {DOCUMENT_TYPE_LABELS[finding.crossReferenceType]?.split(' (')[0] || finding.crossReferenceType}
</p>
)}
{finding.legalReference && (
<p className="text-xs text-gray-500 mt-1 font-mono">{finding.legalReference}</p>
)}
{finding.suggestion && (
<div className="mt-2 flex items-start gap-1.5 px-2.5 py-1.5 bg-white/60 rounded border border-gray-100">
<svg className="w-3.5 h-3.5 mt-0.5 text-gray-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
</svg>
<p className="text-xs text-gray-600">{finding.suggestion}</p>
</div>
)}
</div>
</div>
</div>
)
}
export function ValidationReport({ result, onClose, compact }: ValidationReportProps) {
const totalFindings = result.errors.length + result.warnings.length + result.suggestions.length
if (compact) {
return (
<div className="rounded-lg border border-gray-200 bg-white overflow-hidden">
<div className="flex items-center justify-between px-3 py-2 bg-gray-50 border-b border-gray-100">
<div className="flex items-center gap-2">
<span className={`w-2 h-2 rounded-full ${result.passed ? 'bg-green-500' : 'bg-red-500'}`} />
<span className="text-xs font-medium text-gray-700">
{result.passed ? 'Validierung bestanden' : 'Validierung fehlgeschlagen'}
</span>
<span className="text-xs text-gray-400">
({totalFindings} {totalFindings === 1 ? 'Fund' : 'Funde'})
</span>
</div>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div className="p-2 space-y-1.5 max-h-36 overflow-y-auto">
{result.errors.map((f) => <FindingCard key={f.id} finding={f} compact />)}
{result.warnings.map((f) => <FindingCard key={f.id} finding={f} compact />)}
{result.suggestions.map((f) => <FindingCard key={f.id} finding={f} compact />)}
</div>
</div>
)
}
return (
<div className="space-y-4">
{/* Summary Header */}
<div className={`rounded-lg border p-4 ${result.passed ? 'bg-green-50 border-green-200' : 'bg-red-50 border-red-200'}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${result.passed ? 'bg-green-100' : 'bg-red-100'}`}>
<svg className={`w-5 h-5 ${result.passed ? 'text-green-600' : 'text-red-600'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={result.passed ? 'M5 13l4 4L19 7' : 'M6 18L18 6M6 6l12 12'} />
</svg>
</div>
<div>
<h3 className={`text-sm font-semibold ${result.passed ? 'text-green-800' : 'text-red-800'}`}>
{result.passed ? 'Validierung bestanden' : 'Validierung fehlgeschlagen'}
</h3>
<p className="text-xs text-gray-500">
Level {result.scopeLevel} | {new Date(result.timestamp).toLocaleString('de-DE')}
</p>
</div>
</div>
{/* Stats */}
<div className="flex items-center gap-3">
{result.errors.length > 0 && (
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-red-500" />
<span className="text-xs font-medium text-red-700">{result.errors.length}</span>
</div>
)}
{result.warnings.length > 0 && (
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-amber-500" />
<span className="text-xs font-medium text-amber-700">{result.warnings.length}</span>
</div>
)}
{result.suggestions.length > 0 && (
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-blue-500" />
<span className="text-xs font-medium text-blue-700">{result.suggestions.length}</span>
</div>
)}
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 ml-2">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
{/* Errors */}
{result.errors.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-red-700 uppercase tracking-wide mb-2">
Fehler ({result.errors.length})
</h4>
<div className="space-y-2">
{result.errors.map((f) => <FindingCard key={f.id} finding={f} />)}
</div>
</div>
)}
{/* Warnings */}
{result.warnings.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-amber-700 uppercase tracking-wide mb-2">
Warnungen ({result.warnings.length})
</h4>
<div className="space-y-2">
{result.warnings.map((f) => <FindingCard key={f.id} finding={f} />)}
</div>
</div>
)}
{/* Suggestions */}
{result.suggestions.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-blue-700 uppercase tracking-wide mb-2">
Vorschlaege ({result.suggestions.length})
</h4>
<div className="space-y-2">
{result.suggestions.map((f) => <FindingCard key={f.id} finding={f} />)}
</div>
</div>
)}
</div>
)
}