'use client' import { useCallback, useEffect, useState } from 'react' import { useGridEditor } from './useGridEditor' import type { GridZone } from './types' import { GridToolbar } from './GridToolbar' import { GridTable } from './GridTable' import { GridImageOverlay } from './GridImageOverlay' interface GridEditorProps { sessionId: string | null onNext?: () => void } export function GridEditor({ sessionId, onNext }: GridEditorProps) { const { grid, loading, saving, error, dirty, selectedCell, setSelectedCell, buildGrid, loadGrid, saveGrid, updateCellText, toggleColumnBold, toggleRowHeader, undo, redo, canUndo, canRedo, getAdjacentCell, deleteColumn, addColumn, deleteRow, addRow, ipaMode, setIpaMode, syllableMode, setSyllableMode, } = useGridEditor(sessionId) const [showOverlay, setShowOverlay] = useState(false) // Load grid on mount useEffect(() => { if (sessionId) { loadGrid() } }, [sessionId, loadGrid]) // Keyboard shortcuts useEffect(() => { const handler = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'z' && !e.shiftKey) { e.preventDefault() undo() } else if ((e.metaKey || e.ctrlKey) && e.key === 'z' && e.shiftKey) { e.preventDefault() redo() } else if ((e.metaKey || e.ctrlKey) && e.key === 's') { e.preventDefault() saveGrid() } } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) }, [undo, redo, saveGrid]) const handleNavigate = useCallback( (cellId: string, direction: 'up' | 'down' | 'left' | 'right') => { const target = getAdjacentCell(cellId, direction) if (target) { setSelectedCell(target) // Focus the input setTimeout(() => { const el = document.getElementById(`cell-${target}`) if (el) { el.focus() if (el instanceof HTMLInputElement) el.select() } }, 0) } }, [getAdjacentCell, setSelectedCell], ) if (!sessionId) { return (
Keine Session ausgewaehlt.
) } if (loading) { return (
Grid wird aufgebaut...
) } if (error) { return (

Fehler: {error}

) } if (!grid || !grid.zones.length) { return (

Kein Grid vorhanden.

) } return (
{/* Summary bar */}
{grid.summary.total_zones} Zone(n) {grid.summary.total_columns} Spalten {grid.summary.total_rows} Zeilen {grid.summary.total_cells} Zellen {grid.boxes_detected > 0 && ( {grid.boxes_detected} Box(en) erkannt )} {grid.summary.color_stats && Object.entries(grid.summary.color_stats) .filter(([name]) => name !== 'black') .map(([name, count]) => ( {count} {name} )) } {(grid.summary.recovered_colored ?? 0) > 0 && ( +{grid.summary.recovered_colored} recovered )} {grid.dictionary_detection?.is_dictionary && ( Woerterbuch ({Math.round(grid.dictionary_detection.confidence * 100)}%) )} {grid.page_number?.text && ( S. {grid.page_number.text} )} {grid.duration_seconds.toFixed(1)}s
{/* Toolbar */}
setShowOverlay(!showOverlay)} onIpaModeChange={setIpaMode} onSyllableModeChange={setSyllableMode} />
{/* Image overlay */} {showOverlay && ( )} {/* Zone tables — group vsplit zones side by side */}
{(() => { // Group consecutive zones with same vsplit_group const groups: GridZone[][] = [] for (const zone of grid.zones) { const prev = groups[groups.length - 1] if ( prev && zone.vsplit_group != null && prev[0].vsplit_group === zone.vsplit_group ) { prev.push(zone) } else { groups.push([zone]) } } return groups.map((group) => group.length === 1 ? (
) : (
{group.map((zone) => (
))}
), ) })()}
{/* Tip */}
Tab: naechste Zelle Enter: Zeile runter Spalte fett: Klick auf Spaltenkopf Header: Klick auf Zeilennummer Ctrl+Z/Y: Undo/Redo Ctrl+S: Speichern
{/* Next step button */} {onNext && (
)}
) }