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) Failing after 19s
CI / test-go-edu-search (push) Failing after 23s
CI / test-python-klausur (push) Failing after 10s
CI / test-python-agent-core (push) Failing after 9s
CI / test-nodejs-website (push) Failing after 26s
grid_editor_api.py: 2411 → 474 lines - Extracted _build_grid_core() (1892 lines) into grid_build_core.py - API file now only contains endpoints (build, save, get, gutter, box, unified) StepAnsicht.tsx: 212 → 112 lines - Removed useGridEditor imports (not needed for read-only spreadsheet) - Removed unified grid fetch/build (not used with multi-sheet approach) - Removed Spreadsheet/Grid toggle (only spreadsheet mode now) - Simple: fetch grid-editor data → pass to SpreadsheetView Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
111 lines
3.7 KiB
TypeScript
111 lines
3.7 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* StepAnsicht — Excel-like Spreadsheet View.
|
|
*
|
|
* Left: Original scan with OCR word overlay
|
|
* Right: Fortune Sheet spreadsheet with multi-sheet tabs per zone
|
|
*/
|
|
|
|
import { useEffect, useRef, useState } from 'react'
|
|
import dynamic from 'next/dynamic'
|
|
|
|
const SpreadsheetView = dynamic(
|
|
() => import('./SpreadsheetView').then((m) => m.SpreadsheetView),
|
|
{ ssr: false, loading: () => <div className="py-8 text-center text-sm text-gray-400">Spreadsheet wird geladen...</div> },
|
|
)
|
|
|
|
const KLAUSUR_API = '/klausur-api'
|
|
|
|
interface StepAnsichtProps {
|
|
sessionId: string | null
|
|
onNext: () => void
|
|
}
|
|
|
|
export function StepAnsicht({ sessionId, onNext }: StepAnsichtProps) {
|
|
const [gridData, setGridData] = useState<any>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const leftRef = useRef<HTMLDivElement>(null)
|
|
const [leftHeight, setLeftHeight] = useState(600)
|
|
|
|
// Load grid data on mount
|
|
useEffect(() => {
|
|
if (!sessionId) return
|
|
;(async () => {
|
|
try {
|
|
const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/grid-editor`)
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
setGridData(await res.json())
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : 'Fehler beim Laden')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
})()
|
|
}, [sessionId])
|
|
|
|
// Track left panel height
|
|
useEffect(() => {
|
|
if (!leftRef.current) return
|
|
const ro = new ResizeObserver(([e]) => setLeftHeight(e.contentRect.height))
|
|
ro.observe(leftRef.current)
|
|
return () => ro.disconnect()
|
|
}, [])
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center py-16">
|
|
<div className="w-8 h-8 border-4 border-teal-500 border-t-transparent rounded-full animate-spin" />
|
|
<span className="ml-3 text-gray-500">Lade Spreadsheet...</span>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (error || !gridData) {
|
|
return (
|
|
<div className="p-8 text-center">
|
|
<p className="text-red-500 mb-4">{error || 'Keine Grid-Daten.'}</p>
|
|
<button onClick={onNext} className="px-5 py-2 bg-teal-600 text-white rounded-lg">Weiter →</button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Ansicht — Spreadsheet</h3>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
Jede Zone als eigenes Sheet-Tab. Spaltenbreiten pro Sheet optimiert.
|
|
</p>
|
|
</div>
|
|
<button onClick={onNext} className="px-5 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 text-sm font-medium">
|
|
Weiter →
|
|
</button>
|
|
</div>
|
|
|
|
{/* Split view */}
|
|
<div className="flex gap-2">
|
|
{/* LEFT: Original + OCR overlay */}
|
|
<div ref={leftRef} className="w-1/3 border border-gray-300 dark:border-gray-600 rounded-lg overflow-hidden bg-white dark:bg-gray-900 flex-shrink-0">
|
|
<div className="px-2 py-1 bg-black/60 text-white text-[10px] font-medium">Original + OCR</div>
|
|
{sessionId && (
|
|
<img
|
|
src={`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/words-overlay`}
|
|
alt="Original + OCR"
|
|
className="w-full h-auto"
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* RIGHT: Fortune Sheet */}
|
|
<div className="flex-1 border border-gray-300 dark:border-gray-600 rounded-lg overflow-hidden bg-white dark:bg-gray-900">
|
|
<SpreadsheetView gridData={gridData} height={Math.max(650, leftHeight - 10)} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|