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 21s
CI / test-go-edu-search (push) Failing after 19s
CI / test-python-klausur (push) Failing after 11s
CI / test-python-agent-core (push) Failing after 10s
CI / test-nodejs-website (push) Failing after 23s
Height: sheet height auto-calculated from row count (26px/row + toolbar), no more cutoff at 21 rows. Row count set to exact (no padding). Box borders: thick colored outside border + thin inner grid lines. Content zone: light gray grid lines on all cells. Headers: bold (bl=1) for is_header rows. Larger font detected via word_box height comparison (>1.3x median → fs=12 + bold). Box cells: light tinted background from box_bg_hex. Header cells in boxes: slightly stronger tint. Multi-line cells: text wrap enabled (tb='2'), \n preserved. Bullet points (•) and indentation preserved in cell text. 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 — height adapts to content */}
|
|
<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(700, leftHeight)} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|