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>
156 lines
8.7 KiB
TypeScript
156 lines
8.7 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import type { VocabWorksheetHook } from '../types'
|
|
import { worksheetFormats, worksheetTypes } from '../constants'
|
|
|
|
export function WorksheetTab({ h }: { h: VocabWorksheetHook }) {
|
|
const { isDark, glassCard, glassInput } = h
|
|
|
|
return (
|
|
<div className={`${glassCard} rounded-2xl p-6`}>
|
|
{/* Step 1: Format Selection */}
|
|
<div className="mb-8">
|
|
<h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
1. Vorlage waehlen
|
|
</h2>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{worksheetFormats.map((format) => (
|
|
<button
|
|
key={format.id}
|
|
onClick={() => h.setSelectedFormat(format.id)}
|
|
className={`p-5 rounded-xl border text-left transition-all ${
|
|
h.selectedFormat === format.id
|
|
? (isDark ? 'border-purple-400/50 bg-purple-500/20 ring-2 ring-purple-500/50' : 'border-purple-500 bg-purple-50 ring-2 ring-purple-500/30')
|
|
: (isDark ? 'border-white/20 hover:border-white/40' : 'border-slate-200 hover:border-slate-300')
|
|
}`}
|
|
>
|
|
<div className="flex items-start gap-3">
|
|
<div className={`w-10 h-10 rounded-lg flex items-center justify-center shrink-0 ${
|
|
h.selectedFormat === format.id
|
|
? (isDark ? 'bg-purple-500/30' : 'bg-purple-200')
|
|
: (isDark ? 'bg-white/10' : 'bg-slate-100')
|
|
}`}>
|
|
{format.id === 'standard' ? (
|
|
<svg className={`w-5 h-5 ${h.selectedFormat === format.id ? 'text-purple-400' : (isDark ? 'text-white/60' : 'text-slate-500')}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
) : (
|
|
<svg className={`w-5 h-5 ${h.selectedFormat === format.id ? 'text-purple-400' : (isDark ? 'text-white/60' : 'text-slate-500')}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
|
|
</svg>
|
|
)}
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center justify-between">
|
|
<span className={`font-medium ${isDark ? 'text-white' : 'text-slate-900'}`}>{format.label}</span>
|
|
{h.selectedFormat === format.id && (
|
|
<svg className="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
)}
|
|
</div>
|
|
<p className={`text-sm mt-1 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>{format.description}</p>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Step 2: Configuration */}
|
|
<div className="mb-6">
|
|
<h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
2. Arbeitsblatt konfigurieren
|
|
</h2>
|
|
|
|
{/* Title */}
|
|
<div className="mb-6">
|
|
<label className={`block text-sm font-medium mb-2 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>Titel</label>
|
|
<input
|
|
type="text"
|
|
value={h.worksheetTitle}
|
|
onChange={(e) => h.setWorksheetTitle(e.target.value)}
|
|
placeholder="z.B. Vokabeln Unit 3"
|
|
className={`w-full px-4 py-3 rounded-xl border ${glassInput} focus:outline-none focus:ring-2 focus:ring-purple-500`}
|
|
/>
|
|
</div>
|
|
|
|
{/* Standard format options */}
|
|
{h.selectedFormat === 'standard' && (
|
|
<>
|
|
<div className="mb-6">
|
|
<label className={`block text-sm font-medium mb-3 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>Arbeitsblatt-Typen</label>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
{worksheetTypes.map((type) => (
|
|
<button
|
|
key={type.id}
|
|
onClick={() => h.toggleWorksheetType(type.id)}
|
|
className={`p-4 rounded-xl border text-left transition-all ${
|
|
h.selectedTypes.includes(type.id)
|
|
? (isDark ? 'border-purple-400/50 bg-purple-500/20' : 'border-purple-500 bg-purple-50')
|
|
: (isDark ? 'border-white/20 hover:border-white/40' : 'border-slate-200 hover:border-slate-300')
|
|
}`}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<span className={`font-medium ${isDark ? 'text-white' : 'text-slate-900'}`}>{type.label}</span>
|
|
{h.selectedTypes.includes(type.id) && <svg className="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /></svg>}
|
|
</div>
|
|
<p className={`text-sm mt-1 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>{type.description}</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-6 mb-6">
|
|
<div>
|
|
<label className={`block text-sm font-medium mb-2 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>Zeilenhoehe</label>
|
|
<select value={h.lineHeight} onChange={(e) => h.setLineHeight(e.target.value)} className={`w-full px-4 py-3 rounded-xl border ${glassInput} focus:outline-none focus:ring-2 focus:ring-purple-500`}>
|
|
<option value="normal">Normal</option>
|
|
<option value="large">Gross</option>
|
|
<option value="extra-large">Extra gross</option>
|
|
</select>
|
|
</div>
|
|
<div className="flex items-center">
|
|
<label className={`flex items-center gap-3 cursor-pointer ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
<input type="checkbox" checked={h.includeSolutions} onChange={(e) => h.setIncludeSolutions(e.target.checked)} className="w-5 h-5 rounded border-2 border-purple-500 text-purple-500 focus:ring-purple-500" />
|
|
<span>Loesungsblatt erstellen</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* NRU format options */}
|
|
{h.selectedFormat === 'nru' && (
|
|
<div className="space-y-4">
|
|
<div className={`p-4 rounded-xl ${isDark ? 'bg-indigo-500/20 border border-indigo-500/30' : 'bg-indigo-50 border border-indigo-200'}`}>
|
|
<h4 className={`font-medium mb-2 ${isDark ? 'text-indigo-200' : 'text-indigo-700'}`}>NRU-Format Uebersicht:</h4>
|
|
<ul className={`text-sm space-y-1 ${isDark ? 'text-indigo-200/80' : 'text-indigo-600'}`}>
|
|
<li>• <strong>Vokabeln:</strong> 3-Spalten-Tabelle (Englisch | Deutsch leer | Korrektur leer)</li>
|
|
<li>• <strong>Lernsaetze:</strong> Deutscher Satz + 2 leere Zeilen fuer englische Uebersetzung</li>
|
|
<li>• Pro gescannter Seite werden 2 Arbeitsblatt-Seiten erzeugt</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div className="flex items-center">
|
|
<label className={`flex items-center gap-3 cursor-pointer ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
<input type="checkbox" checked={h.includeSolutions} onChange={(e) => h.setIncludeSolutions(e.target.checked)} className="w-5 h-5 rounded border-2 border-purple-500 text-purple-500 focus:ring-purple-500" />
|
|
<span>Loesungsblatt erstellen (mit deutschen Uebersetzungen)</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<button
|
|
onClick={h.generateWorksheet}
|
|
disabled={(h.selectedFormat === 'standard' && h.selectedTypes.length === 0) || h.isGenerating}
|
|
className="w-full py-4 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-xl font-semibold disabled:opacity-50 hover:shadow-xl hover:shadow-purple-500/30 transition-all"
|
|
>
|
|
{h.isGenerating ? 'Generiere PDF...' : `${h.selectedFormat === 'nru' ? 'NRU-Arbeitsblatt' : 'Arbeitsblatt'} generieren`}
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|