Files
breakpilot-lehrer/admin-lehrer/components/ocr-kombi/StepAnsicht.tsx
Benjamin Admin e131aa719e
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
SpreadsheetView: formatting improvements for Excel-like display
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>
2026-04-15 09:29:50 +02:00

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>
)
}