A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
204 lines
7.0 KiB
TypeScript
204 lines
7.0 KiB
TypeScript
'use client'
|
|
|
|
import { useMemo } from 'react'
|
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from '@/components/ui/collapsible'
|
|
import {
|
|
AlertCircle,
|
|
AlertTriangle,
|
|
Info,
|
|
ChevronDown,
|
|
Lightbulb,
|
|
Plus,
|
|
} from 'lucide-react'
|
|
import { DataPoint } from '@/lib/sdk/einwilligungen/types'
|
|
import {
|
|
validateDocument,
|
|
ValidationWarning,
|
|
} from '@/lib/sdk/document-generator/datapoint-helpers'
|
|
|
|
interface DocumentValidationProps {
|
|
dataPoints: DataPoint[]
|
|
documentContent: string
|
|
language?: 'de' | 'en'
|
|
onInsertPlaceholder?: (placeholder: string) => void
|
|
}
|
|
|
|
/**
|
|
* Icon für den Warnungstyp
|
|
*/
|
|
function getWarningIcon(type: ValidationWarning['type']) {
|
|
switch (type) {
|
|
case 'error':
|
|
return AlertCircle
|
|
case 'warning':
|
|
return AlertTriangle
|
|
case 'info':
|
|
default:
|
|
return Info
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Alert-Variante für den Warnungstyp
|
|
*/
|
|
function getAlertVariant(type: ValidationWarning['type']): 'default' | 'destructive' {
|
|
return type === 'error' ? 'destructive' : 'default'
|
|
}
|
|
|
|
/**
|
|
* Placeholder-Vorschlag aus der Warnung extrahieren
|
|
*/
|
|
function extractPlaceholderSuggestion(warning: ValidationWarning): string | null {
|
|
const match = warning.suggestion.match(/\[([A-Z_]+)\]/)
|
|
return match ? match[0] : null
|
|
}
|
|
|
|
/**
|
|
* DocumentValidation Komponente
|
|
*
|
|
* Zeigt Validierungswarnungen basierend auf ausgewählten Datenpunkten und
|
|
* dem generierten Dokumentinhalt.
|
|
*/
|
|
export function DocumentValidation({
|
|
dataPoints,
|
|
documentContent,
|
|
language = 'de',
|
|
onInsertPlaceholder,
|
|
}: DocumentValidationProps) {
|
|
// Führe Validierung durch
|
|
const warnings = useMemo(() => {
|
|
if (dataPoints.length === 0 || !documentContent) {
|
|
return []
|
|
}
|
|
return validateDocument(dataPoints, documentContent, language)
|
|
}, [dataPoints, documentContent, language])
|
|
|
|
// Gruppiere nach Typ
|
|
const errorCount = warnings.filter(w => w.type === 'error').length
|
|
const warningCount = warnings.filter(w => w.type === 'warning').length
|
|
const infoCount = warnings.filter(w => w.type === 'info').length
|
|
|
|
if (warnings.length === 0) {
|
|
// Keine Warnungen - zeige Erfolgsmeldung wenn Datenpunkte vorhanden
|
|
if (dataPoints.length > 0 && documentContent.length > 100) {
|
|
return (
|
|
<Alert className="bg-green-50 border-green-200 dark:bg-green-950 dark:border-green-900">
|
|
<Info className="h-4 w-4 text-green-600 dark:text-green-400" />
|
|
<AlertTitle className="text-green-800 dark:text-green-200">
|
|
{language === 'de' ? 'Dokument valide' : 'Document valid'}
|
|
</AlertTitle>
|
|
<AlertDescription className="text-green-700 dark:text-green-300">
|
|
{language === 'de'
|
|
? 'Alle notwendigen Abschnitte für die ausgewählten Datenpunkte sind vorhanden.'
|
|
: 'All necessary sections for the selected data points are present.'}
|
|
</AlertDescription>
|
|
</Alert>
|
|
)
|
|
}
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
{/* Zusammenfassung */}
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<span className="font-medium">
|
|
{language === 'de' ? 'Validierung:' : 'Validation:'}
|
|
</span>
|
|
{errorCount > 0 && (
|
|
<Badge variant="destructive">
|
|
{errorCount} {language === 'de' ? 'Fehler' : 'Error'}
|
|
{errorCount > 1 && (language === 'de' ? '' : 's')}
|
|
</Badge>
|
|
)}
|
|
{warningCount > 0 && (
|
|
<Badge variant="secondary" className="bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200">
|
|
{warningCount} {language === 'de' ? 'Warnung' : 'Warning'}
|
|
{warningCount > 1 && (language === 'de' ? 'en' : 's')}
|
|
</Badge>
|
|
)}
|
|
{infoCount > 0 && (
|
|
<Badge variant="outline">
|
|
{infoCount} {language === 'de' ? 'Hinweis' : 'Info'}
|
|
{infoCount > 1 && (language === 'de' ? 'e' : 's')}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
|
|
{/* Warnungen */}
|
|
{warnings.map((warning, index) => {
|
|
const Icon = getWarningIcon(warning.type)
|
|
const placeholder = extractPlaceholderSuggestion(warning)
|
|
|
|
return (
|
|
<Alert key={`${warning.code}-${index}`} variant={getAlertVariant(warning.type)}>
|
|
<Icon className="h-4 w-4" />
|
|
<AlertTitle className="flex items-center justify-between">
|
|
<span>{warning.message}</span>
|
|
</AlertTitle>
|
|
<AlertDescription className="mt-2 space-y-2">
|
|
{/* Vorschlag */}
|
|
<div className="flex items-start gap-2">
|
|
<Lightbulb className="h-4 w-4 mt-0.5 flex-shrink-0" />
|
|
<span className="text-sm">{warning.suggestion}</span>
|
|
</div>
|
|
|
|
{/* Quick-Fix Button */}
|
|
{placeholder && onInsertPlaceholder && (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="mt-2"
|
|
onClick={() => onInsertPlaceholder(placeholder)}
|
|
>
|
|
<Plus className="h-3 w-3 mr-1" />
|
|
{language === 'de' ? 'Platzhalter einfügen' : 'Insert placeholder'}
|
|
<code className="ml-1 text-xs bg-muted px-1 rounded">
|
|
{placeholder}
|
|
</code>
|
|
</Button>
|
|
)}
|
|
|
|
{/* Betroffene Datenpunkte */}
|
|
{warning.affectedDataPoints && warning.affectedDataPoints.length > 0 && (
|
|
<Collapsible>
|
|
<CollapsibleTrigger className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground">
|
|
<ChevronDown className="h-3 w-3" />
|
|
{warning.affectedDataPoints.length}{' '}
|
|
{language === 'de' ? 'betroffene Datenpunkte' : 'affected data points'}
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent className="mt-2">
|
|
<ul className="text-xs space-y-0.5 pl-4">
|
|
{warning.affectedDataPoints.slice(0, 5).map(dp => (
|
|
<li key={dp.id} className="list-disc">
|
|
{language === 'de' ? dp.name.de : dp.name.en}
|
|
</li>
|
|
))}
|
|
{warning.affectedDataPoints.length > 5 && (
|
|
<li className="list-none text-muted-foreground">
|
|
... {language === 'de' ? 'und' : 'and'}{' '}
|
|
{warning.affectedDataPoints.length - 5}{' '}
|
|
{language === 'de' ? 'weitere' : 'more'}
|
|
</li>
|
|
)}
|
|
</ul>
|
|
</CollapsibleContent>
|
|
</Collapsible>
|
|
)}
|
|
</AlertDescription>
|
|
</Alert>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default DocumentValidation
|