fix: Restore all files lost during destructive rebase

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>
This commit is contained in:
Benjamin Admin
2026-02-09 09:51:32 +01:00
parent f7487ee240
commit bfdaf63ba9
2009 changed files with 749983 additions and 1731 deletions

View File

@@ -1,6 +1,22 @@
'use client'
import { useMemo, useState } from 'react'
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,
@@ -14,6 +30,28 @@ interface DocumentValidationProps {
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
*/
@@ -24,6 +62,9 @@ function extractPlaceholderSuggestion(warning: ValidationWarning): string | null
/**
* DocumentValidation Komponente
*
* Zeigt Validierungswarnungen basierend auf ausgewählten Datenpunkten und
* dem generierten Dokumentinhalt.
*/
export function DocumentValidation({
dataPoints,
@@ -31,8 +72,6 @@ export function DocumentValidation({
language = 'de',
onInsertPlaceholder,
}: DocumentValidationProps) {
const [expandedWarnings, setExpandedWarnings] = useState<string[]>([])
// Führe Validierung durch
const warnings = useMemo(() => {
if (dataPoints.length === 0 || !documentContent) {
@@ -46,33 +85,21 @@ export function DocumentValidation({
const warningCount = warnings.filter(w => w.type === 'warning').length
const infoCount = warnings.filter(w => w.type === 'info').length
const toggleWarning = (code: string) => {
setExpandedWarnings(prev =>
prev.includes(code) ? prev.filter(c => c !== code) : [...prev, code]
)
}
if (warnings.length === 0) {
// Keine Warnungen - zeige Erfolgsmeldung wenn Datenpunkte vorhanden
if (dataPoints.length > 0 && documentContent.length > 100) {
return (
<div className="bg-green-50 border border-green-200 rounded-xl p-4">
<div className="flex items-start gap-3">
<svg className="w-5 h-5 text-green-600 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<h4 className="font-medium text-green-800">
{language === 'de' ? 'Dokument valide' : 'Document valid'}
</h4>
<p className="text-sm text-green-700 mt-1">
{language === 'de'
? 'Alle notwendigen Abschnitte für die ausgewählten Datenpunkte sind vorhanden.'
: 'All necessary sections for the selected data points are present.'}
</p>
</div>
</div>
</div>
<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
@@ -82,126 +109,91 @@ export function DocumentValidation({
<div className="space-y-3">
{/* Zusammenfassung */}
<div className="flex items-center gap-2 text-sm">
<span className="font-medium text-gray-700">
<span className="font-medium">
{language === 'de' ? 'Validierung:' : 'Validation:'}
</span>
{errorCount > 0 && (
<span className="px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-700">
{errorCount} {language === 'de' ? 'Fehler' : 'Error'}{errorCount > 1 && 's'}
</span>
<Badge variant="destructive">
{errorCount} {language === 'de' ? 'Fehler' : 'Error'}
{errorCount > 1 && (language === 'de' ? '' : 's')}
</Badge>
)}
{warningCount > 0 && (
<span className="px-2 py-0.5 text-xs rounded-full bg-yellow-100 text-yellow-700">
{warningCount} {language === 'de' ? 'Warnung' : 'Warning'}{warningCount > 1 && (language === 'de' ? 'en' : 's')}
</span>
<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 && (
<span className="px-2 py-0.5 text-xs rounded-full bg-blue-100 text-blue-700">
{infoCount} {language === 'de' ? 'Hinweis' : 'Info'}{infoCount > 1 && (language === 'de' ? 'e' : 's')}
</span>
<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)
const isExpanded = expandedWarnings.includes(warning.code)
const isError = warning.type === 'error'
return (
<div
key={`${warning.code}-${index}`}
className={`rounded-xl border p-4 ${
isError
? 'bg-red-50 border-red-200'
: 'bg-yellow-50 border-yellow-200'
}`}
>
<div className="flex items-start gap-3">
{/* Icon */}
<svg
className={`w-5 h-5 mt-0.5 ${isError ? 'text-red-600' : 'text-yellow-600'}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{isError ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
)}
</svg>
<div className="flex-1">
{/* Message */}
<p className={`font-medium ${isError ? 'text-red-800' : 'text-yellow-800'}`}>
{warning.message}
</p>
{/* Suggestion */}
<div className="flex items-start gap-2 mt-2">
<svg className="w-4 h-4 mt-0.5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
<span className="text-sm text-gray-600">{warning.suggestion}</span>
</div>
{/* Quick-Fix Button */}
{placeholder && onInsertPlaceholder && (
<button
onClick={() => onInsertPlaceholder(placeholder)}
className="mt-3 inline-flex items-center gap-1.5 px-3 py-1.5 text-sm bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
{language === 'de' ? 'Platzhalter einfügen' : 'Insert placeholder'}
<code className="ml-1 text-xs bg-gray-100 px-1.5 py-0.5 rounded">
{placeholder}
</code>
</button>
)}
{/* Betroffene Datenpunkte */}
{warning.affectedDataPoints && warning.affectedDataPoints.length > 0 && (
<div className="mt-3">
<button
onClick={() => toggleWarning(warning.code)}
className="flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700"
>
<svg
className={`w-3 h-3 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
{warning.affectedDataPoints.length}{' '}
{language === 'de' ? 'betroffene Datenpunkte' : 'affected data points'}
</button>
{isExpanded && (
<ul className="mt-2 text-xs space-y-0.5 pl-4">
{warning.affectedDataPoints.slice(0, 5).map(dp => (
<li key={dp.id} className="list-disc text-gray-600">
{language === 'de' ? dp.name.de : dp.name.en}
</li>
))}
{warning.affectedDataPoints.length > 5 && (
<li className="list-none text-gray-400">
... {language === 'de' ? 'und' : 'and'}{' '}
{warning.affectedDataPoints.length - 5}{' '}
{language === 'de' ? 'weitere' : 'more'}
</li>
)}
</ul>
)}
</div>
)}
<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>
</div>
</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>