Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 45s
CI / test-go-edu-search (push) Successful in 43s
CI / test-python-klausur (push) Failing after 2m51s
CI / test-python-agent-core (push) Successful in 36s
CI / test-nodejs-website (push) Successful in 37s
SmartSpellChecker (klausur-service): - Language-aware OCR post-correction without LLMs - Dual-dictionary heuristic for EN/DE language detection - Context-based a/I disambiguation via bigram lookup - Multi-digit substitution (sch00l→school) - Cross-language guard (don't false-correct DE words in EN column) - Umlaut correction (Schuler→Schüler, uber→über) - Integrated into spell_review_entries_sync() pipeline - 31 tests, 9ms/100 corrections Vocab-worksheet refactoring (studio-v2): - Split 2337-line page.tsx into 14 files - Custom hook useVocabWorksheet.ts (all state + logic) - 9 components in components/ directory - types.ts, constants.ts for shared definitions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
109 lines
5.7 KiB
TypeScript
109 lines
5.7 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import type { VocabWorksheetHook } from '../types'
|
|
|
|
export function PageSelection({ h }: { h: VocabWorksheetHook }) {
|
|
const { isDark, glassCard } = h
|
|
|
|
return (
|
|
<div className={`${glassCard} rounded-2xl p-6`}>
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className={`text-lg font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
PDF-Seiten auswaehlen ({h.selectedPages.length} von {h.pdfPageCount - h.excludedPages.length} ausgewaehlt)
|
|
</h2>
|
|
<div className="flex gap-2">
|
|
{h.excludedPages.length > 0 && (
|
|
<button onClick={h.restoreExcludedPages} className={`px-3 py-1 rounded-lg text-sm ${isDark ? 'bg-orange-500/20 text-orange-300 hover:bg-orange-500/30' : 'bg-orange-100 text-orange-700 hover:bg-orange-200'}`}>
|
|
{h.excludedPages.length} ausgeblendet - wiederherstellen
|
|
</button>
|
|
)}
|
|
<button onClick={h.selectAllPages} className={`px-3 py-1 rounded-lg text-sm transition-colors ${isDark ? 'bg-white/10 hover:bg-white/20 text-white' : 'bg-slate-100 hover:bg-slate-200 text-slate-900'}`}>
|
|
Alle
|
|
</button>
|
|
<button onClick={h.selectNoPages} className={`px-3 py-1 rounded-lg text-sm transition-colors ${isDark ? 'bg-white/10 hover:bg-white/20 text-white' : 'bg-slate-100 hover:bg-slate-200 text-slate-900'}`}>
|
|
Keine
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<p className={`text-sm mb-4 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
|
Klicken Sie auf eine Seite um sie auszuwaehlen. Klicken Sie auf das X um leere Seiten auszublenden.
|
|
</p>
|
|
|
|
{h.isLoadingThumbnails ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<div className="w-8 h-8 border-4 border-purple-500 border-t-transparent rounded-full animate-spin" />
|
|
<span className={`ml-3 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>Lade Seitenvorschau...</span>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4 mb-6">
|
|
{h.pagesThumbnails.map((thumb, idx) => {
|
|
if (h.excludedPages.includes(idx)) return null
|
|
return (
|
|
<div key={idx} className="relative group">
|
|
{/* Exclude/Delete Button */}
|
|
<button
|
|
onClick={(e) => h.excludePage(idx, e)}
|
|
className="absolute top-1 left-1 z-10 p-1 rounded-full opacity-0 group-hover:opacity-100 transition-opacity bg-red-500/80 hover:bg-red-600 text-white"
|
|
title="Seite ausblenden"
|
|
>
|
|
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
|
|
{/* OCR Compare Button */}
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); h.runOcrComparison(idx); }}
|
|
className="absolute top-1 right-1 z-10 p-1 rounded-full opacity-0 group-hover:opacity-100 transition-opacity bg-blue-500/80 hover:bg-blue-600 text-white"
|
|
title="OCR-Methoden vergleichen"
|
|
>
|
|
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => h.togglePageSelection(idx)}
|
|
className={`relative rounded-xl overflow-hidden border-2 transition-all w-full ${
|
|
h.selectedPages.includes(idx)
|
|
? 'border-purple-500 ring-2 ring-purple-500/50'
|
|
: (isDark ? 'border-white/20 hover:border-white/40' : 'border-slate-200 hover:border-slate-300')
|
|
}`}
|
|
>
|
|
<img src={thumb} alt={`Seite ${idx + 1}`} className="w-full h-auto" />
|
|
<div className={`absolute bottom-0 left-0 right-0 py-1 text-center text-xs font-medium ${
|
|
h.selectedPages.includes(idx)
|
|
? 'bg-purple-500 text-white'
|
|
: (isDark ? 'bg-black/60 text-white/80' : 'bg-white/90 text-slate-700')
|
|
}`}>
|
|
Seite {idx + 1}
|
|
</div>
|
|
{h.selectedPages.includes(idx) && (
|
|
<div className="absolute top-2 right-2 w-6 h-6 bg-purple-500 rounded-full flex items-center justify-center">
|
|
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
)}
|
|
</button>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex justify-center">
|
|
<button
|
|
onClick={h.processSelectedPages}
|
|
disabled={h.selectedPages.length === 0 || h.isExtracting}
|
|
className="px-8 py-4 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-2xl font-semibold disabled:opacity-50 hover:shadow-xl hover:shadow-purple-500/30 transition-all transform hover:scale-105"
|
|
>
|
|
{h.isExtracting ? 'Extrahiere Vokabeln...' : `${h.selectedPages.length} Seiten verarbeiten`}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|