diff --git a/studio-v2/app/vocab-worksheet/page.tsx b/studio-v2/app/vocab-worksheet/page.tsx index 58579fd..85e4565 100644 --- a/studio-v2/app/vocab-worksheet/page.tsx +++ b/studio-v2/app/vocab-worksheet/page.tsx @@ -33,6 +33,13 @@ interface VocabularyEntry { word_type?: string source_page?: number selected?: boolean + extras?: Record +} + +// Dynamic column definition (per source page) +interface ExtraColumn { + key: string + label: string } interface Session { @@ -132,6 +139,9 @@ export default function VocabWorksheetPage() { const [isLoadingThumbnails, setIsLoadingThumbnails] = useState(false) const [excludedPages, setExcludedPages] = useState([]) + // Dynamic extra columns per source page (key: page number, value: extra columns) + const [pageExtraColumns, setPageExtraColumns] = useState>({}) + // Upload state const [uploadedImage, setUploadedImage] = useState(null) const [isExtracting, setIsExtracting] = useState(false) @@ -559,10 +569,63 @@ export default function VocabWorksheetPage() { } // Update vocabulary entry - const updateVocabularyEntry = (id: string, field: keyof VocabularyEntry, value: string) => { - setVocabulary(prev => prev.map(v => - v.id === id ? { ...v, [field]: value } : v - )) + const updateVocabularyEntry = (id: string, field: string, value: string) => { + setVocabulary(prev => prev.map(v => { + if (v.id !== id) return v + // Check if it's a base field or an extra column + if (field === 'english' || field === 'german' || field === 'example_sentence' || field === 'word_type') { + return { ...v, [field]: value } + } + // Extra column + return { ...v, extras: { ...(v.extras || {}), [field]: value } } + })) + } + + // Add a custom column for a specific source page (0 = all pages) + const addExtraColumn = (sourcePage: number) => { + const label = prompt('Spaltenname:') + if (!label || !label.trim()) return + const key = `extra_${Date.now()}` + setPageExtraColumns(prev => ({ + ...prev, + [sourcePage]: [...(prev[sourcePage] || []), { key, label: label.trim() }], + })) + } + + // Remove a custom column + const removeExtraColumn = (sourcePage: number, key: string) => { + setPageExtraColumns(prev => ({ + ...prev, + [sourcePage]: (prev[sourcePage] || []).filter(c => c.key !== key), + })) + // Clean up extras from entries + setVocabulary(prev => prev.map(v => { + if (!v.extras || !(key in v.extras)) return v + const { [key]: _, ...rest } = v.extras + return { ...v, extras: rest } + })) + } + + // Get extra columns for a given source page (page-specific + global) + const getExtraColumnsForPage = (sourcePage: number): ExtraColumn[] => { + const global = pageExtraColumns[0] || [] + const pageSpecific = pageExtraColumns[sourcePage] || [] + return [...global, ...pageSpecific] + } + + // Get ALL extra columns across all pages (for unified table header) + const getAllExtraColumns = (): ExtraColumn[] => { + const seen = new Set() + const result: ExtraColumn[] = [] + for (const cols of Object.values(pageExtraColumns)) { + for (const col of cols) { + if (!seen.has(col.key)) { + seen.add(col.key) + result.push(col) + } + } + } + return result } // Delete vocabulary entry @@ -1417,8 +1480,13 @@ export default function VocabWorksheetPage() { {/* Vocabulary Tab */} {session && activeTab === 'vocabulary' && ( + {(() => { + const extras = getAllExtraColumns() + const baseCols = 3 + extras.length // english, german, example + extras + const gridCols = `14px 32px 36px repeat(${baseCols}, 1fr) 32px` + return (
- {/* Left: Original pages (scrollable, 1/3 width) */} + {/* Left: Original pages — full quality */}

Original ({(() => { const pp = selectedPages.length > 0 ? selectedPages : [...new Set(vocabulary.map(v => (v.source_page || 1) - 1))]; return pp.length; })()} Seiten) @@ -1429,22 +1497,19 @@ export default function VocabWorksheetPage() { ? selectedPages : [...new Set(vocabulary.map(v => (v.source_page || 1) - 1))].sort((a, b) => a - b) - // Use blob URLs if available, otherwise fall back to direct API URLs const apiBase = getApiBase() - const thumbsToShow = processedPageIndices + const pagesToShow = processedPageIndices .filter(idx => idx >= 0) .map(idx => ({ idx, - src: (idx < pagesThumbnails.length && pagesThumbnails[idx]) - ? pagesThumbnails[idx] - : session ? `${apiBase}/api/v1/vocab/sessions/${session.id}/pdf-thumbnail/${idx}?hires=true` : null, + src: session ? `${apiBase}/api/v1/vocab/sessions/${session.id}/pdf-page-image/${idx}` : null, })) .filter(t => t.src !== null) as { idx: number; src: string }[] - if (thumbsToShow.length > 0) { - return thumbsToShow.map(({ idx, src }) => ( + if (pagesToShow.length > 0) { + return pagesToShow.map(({ idx, src }) => (
-
+
S. {idx + 1}
{`Seite @@ -1535,29 +1600,61 @@ export default function VocabWorksheetPage() { ) : (
{/* Fixed Header */} -
+
+
{/* insert-triangle spacer */}
0 && vocabulary.every(v => v.selected)} onChange={toggleAllSelection} className="w-4 h-4 rounded border-gray-300 text-purple-600 focus:ring-purple-500 cursor-pointer" - title="Alle auswählen" + title="Alle auswaehlen" />
S.
Englisch
Deutsch
Beispiel
-
+ {extras.map(col => ( +
+ {col.label} + +
+ ))} +
+ +
{/* Scrollable Content */} -
+
{vocabulary.map((entry, index) => ( {/* Vocabulary row */} -
+
+ {/* Insert triangle */} +
updateVocabularyEntry(entry.id, 'english', e.target.value)} - className={`px-2 py-1 rounded-lg border text-sm ${glassInput} focus:outline-none focus:ring-1 focus:ring-purple-500`} + className={`px-2 py-1 rounded-lg border text-sm min-w-0 ${glassInput} focus:outline-none focus:ring-1 focus:ring-purple-500`} /> updateVocabularyEntry(entry.id, 'german', e.target.value)} - className={`px-2 py-1 rounded-lg border text-sm ${glassInput} focus:outline-none focus:ring-1 focus:ring-purple-500`} + className={`px-2 py-1 rounded-lg border text-sm min-w-0 ${glassInput} focus:outline-none focus:ring-1 focus:ring-purple-500`} /> updateVocabularyEntry(entry.id, 'example_sentence', e.target.value)} placeholder="Beispiel" - className={`px-2 py-1 rounded-lg border text-sm ${glassInput} focus:outline-none focus:ring-1 focus:ring-purple-500`} + className={`px-2 py-1 rounded-lg border text-sm min-w-0 ${glassInput} focus:outline-none focus:ring-1 focus:ring-purple-500`} /> + {extras.map(col => ( + updateVocabularyEntry(entry.id, col.key, e.target.value)} + placeholder={col.label} + className={`px-2 py-1 rounded-lg border text-sm min-w-0 ${glassInput} focus:outline-none focus:ring-1 focus:ring-purple-500`} + /> + ))}
))} + {/* Final insert triangle after last row */} +
+ +
{/* Footer */} @@ -1626,6 +1743,8 @@ export default function VocabWorksheetPage() { )}
+ ) + })()} )} {/* Worksheet Tab */}