'use client' import { useCallback, useEffect, useState } from 'react' import { useGridEditor } from '@/components/grid-editor/useGridEditor' import type { GridZone } from '@/components/grid-editor/types' import { GridTable } from '@/components/grid-editor/GridTable' const KLAUSUR_API = '/klausur-api' type BoxLayoutType = 'flowing' | 'columnar' | 'bullet_list' | 'header_only' const LAYOUT_LABELS: Record = { flowing: 'Fließtext', columnar: 'Tabelle/Spalten', bullet_list: 'Aufzählung', header_only: 'Überschrift', } interface StepBoxGridReviewProps { sessionId: string | null onNext: () => void } export function StepBoxGridReview({ sessionId, onNext }: StepBoxGridReviewProps) { const { grid, loading, saving, error, dirty, selectedCell, setSelectedCell, loadGrid, saveGrid, updateCellText, undo, redo, canUndo, canRedo, getAdjacentCell, commitUndoPoint, selectedCells, toggleCellSelection, clearCellSelection, toggleSelectedBold, setCellColor, } = useGridEditor(sessionId) const [building, setBuilding] = useState(false) const [buildError, setBuildError] = useState(null) // Load grid on mount useEffect(() => { if (sessionId) loadGrid() }, [sessionId]) // eslint-disable-line react-hooks/exhaustive-deps // Get box zones const boxZones: GridZone[] = (grid?.zones || []).filter( (z: GridZone) => z.zone_type === 'box' ) // Build box grids via backend const buildBoxGrids = useCallback(async (overrides?: Record) => { if (!sessionId) return setBuilding(true) setBuildError(null) try { const res = await fetch( `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/build-box-grids`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ overrides: overrides || {} }), }, ) if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data.detail || `HTTP ${res.status}`) } // Reload grid to see updated box zones await loadGrid() } catch (e) { setBuildError(e instanceof Error ? e.message : String(e)) } finally { setBuilding(false) } }, [sessionId, loadGrid]) // Handle layout type change for a specific box zone const changeLayoutType = useCallback(async (zoneIndex: number, layoutType: string) => { await buildBoxGrids({ [String(zoneIndex)]: layoutType }) }, [buildBoxGrids]) // Auto-build on first load if box zones have no cells useEffect(() => { if (!grid || loading || building) return const needsBuild = boxZones.some(z => !z.cells || z.cells.length === 0) if (needsBuild && boxZones.length > 0) { buildBoxGrids() } }, [grid, loading]) // eslint-disable-line react-hooks/exhaustive-deps if (loading) { return (
Lade Grid...
) } // No boxes detected — skip step if (boxZones.length === 0) { return (
📦

Keine Boxen erkannt

Auf dieser Seite wurden keine eingebetteten Boxen (Grammatik-Tipps, Übungen etc.) erkannt.

) } return (
{/* Header */}

📦 Box-Review ({boxZones.length} {boxZones.length === 1 ? 'Box' : 'Boxen'})

Eingebettete Boxen prüfen und korrigieren. Layout-Typ kann pro Box angepasst werden.

{dirty && ( )}
{/* Errors */} {(error || buildError) && (
{error || buildError}
)} {building && (
Box-Grids werden aufgebaut...
)} {/* Box zones */} {boxZones.map((zone) => (
{/* Box header */}
📦
Box {zone.zone_index + 1} {zone.bbox_px.w}×{zone.bbox_px.h}px {zone.cells?.length ? ` • ${zone.cells.length} Zellen` : ''}
{/* Box content — image + grid side by side */}
{/* Box image crop */} {sessionId && (
{`Box { // Fallback: hide image if endpoint doesn't exist (e.target as HTMLImageElement).style.display = 'none' }} />
)} {/* Box grid table */}
{zone.cells && zone.cells.length > 0 ? ( ) : (

Keine Zellen erkannt.

)}
))}
) }