Phase 1 — Python (klausur-service): 5 monoliths → 36 files - dsfa_corpus_ingestion.py (1,828 LOC → 5 files) - cv_ocr_engines.py (2,102 LOC → 7 files) - cv_layout.py (3,653 LOC → 10 files) - vocab_worksheet_api.py (2,783 LOC → 8 files) - grid_build_core.py (1,958 LOC → 6 files) Phase 2 — Go (edu-search-service, school-service): 8 monoliths → 19 files - staff_crawler.go (1,402 → 4), policy/store.go (1,168 → 3) - policy_handlers.go (700 → 2), repository.go (684 → 2) - search.go (592 → 2), ai_extraction_handlers.go (554 → 2) - seed_data.go (591 → 2), grade_service.go (646 → 2) Phase 3 — TypeScript (admin-lehrer): 45 monoliths → 220+ files - sdk/types.ts (2,108 → 16 domain files) - ai/rag/page.tsx (2,686 → 14 files) - 22 page.tsx files split into _components/ + _hooks/ - 11 component files split into sub-components - 10 SDK data catalogs added to loc-exceptions - Deleted dead backup index_original.ts (4,899 LOC) All original public APIs preserved via re-export facades. Zero new errors: Python imports verified, Go builds clean, TypeScript tsc --noEmit shows only pre-existing errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
'use client'
|
||
|
||
import type { LlmChange, RowStatus } from './llm-review-types'
|
||
|
||
/** Cell content with inline diff for corrections */
|
||
export function CellContent({ text, field, rowChanges }: {
|
||
text: string
|
||
field: string
|
||
rowChanges?: LlmChange[]
|
||
}) {
|
||
const change = rowChanges?.find(c => c.field === field)
|
||
|
||
if (!text && !change) {
|
||
return <span className="text-gray-300 dark:text-gray-600">—</span>
|
||
}
|
||
|
||
if (change) {
|
||
return (
|
||
<span>
|
||
<span className="line-through text-red-400 dark:text-red-500 text-xs mr-1">{change.old}</span>
|
||
<span className="text-green-600 dark:text-green-400 font-medium text-xs">{change.new}</span>
|
||
</span>
|
||
)
|
||
}
|
||
|
||
return <span className="text-gray-700 dark:text-gray-300 text-xs">{text}</span>
|
||
}
|
||
|
||
/** Status icon for each row */
|
||
export function StatusIcon({ status }: { status: RowStatus }) {
|
||
switch (status) {
|
||
case 'pending':
|
||
return <span className="text-gray-300 dark:text-gray-600 text-xs">—</span>
|
||
case 'active':
|
||
return (
|
||
<span className="inline-block w-3 h-3 rounded-full bg-yellow-400 animate-pulse" title="Wird geprueft" />
|
||
)
|
||
case 'reviewed':
|
||
return (
|
||
<svg className="w-4 h-4 text-green-500 inline-block" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||
</svg>
|
||
)
|
||
case 'corrected':
|
||
return (
|
||
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-teal-100 dark:bg-teal-900/30 text-teal-700 dark:text-teal-400">
|
||
korr.
|
||
</span>
|
||
)
|
||
case 'skipped':
|
||
return (
|
||
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400">
|
||
skip
|
||
</span>
|
||
)
|
||
}
|
||
}
|
||
|
||
/** Loading spinner screen */
|
||
export function LoadingScreen() {
|
||
return (
|
||
<div className="flex items-center gap-3 justify-center py-12">
|
||
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-teal-500" />
|
||
<span className="text-gray-500">Session-Daten werden geladen...</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/** Error screen with retry */
|
||
export function ErrorScreen({ error, onRetry, onSkip }: {
|
||
error: string
|
||
onRetry: () => void
|
||
onSkip: () => void
|
||
}) {
|
||
return (
|
||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||
<div className="text-5xl mb-4">⚠️</div>
|
||
<h3 className="text-lg font-medium text-red-600 dark:text-red-400 mb-2">Fehler bei OCR-Zeichenkorrektur</h3>
|
||
<p className="text-sm text-gray-500 dark:text-gray-400 max-w-lg mb-4">{error}</p>
|
||
<div className="flex gap-3">
|
||
<button onClick={onRetry}
|
||
className="px-5 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors text-sm">
|
||
Erneut versuchen
|
||
</button>
|
||
<button onClick={onSkip}
|
||
className="px-5 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors text-sm">
|
||
Ueberspringen →
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/** Applied screen shown after corrections are applied */
|
||
export function AppliedScreen({ acceptedCount, totalChanges, onNext }: {
|
||
acceptedCount: number
|
||
totalChanges: number
|
||
onNext: () => void
|
||
}) {
|
||
return (
|
||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||
<div className="text-5xl mb-4">✅</div>
|
||
<h3 className="text-lg font-medium text-gray-700 dark:text-gray-300 mb-2">Korrekturen uebernommen</h3>
|
||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||
{acceptedCount} von {totalChanges} Korrekturen wurden angewendet.
|
||
</p>
|
||
<button onClick={onNext}
|
||
className="px-6 py-2.5 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors font-medium">
|
||
Weiter →
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/** No session placeholder */
|
||
export function NoSessionScreen() {
|
||
return <div className="text-center py-12 text-gray-400">Bitte zuerst eine Session auswaehlen.</div>
|
||
}
|