'use client' /** * SpreadsheetView — Fortune Sheet integration for unified grid display. * * Converts unified grid data into Fortune Sheet format and renders * a full-featured Excel-like spreadsheet editor. */ import { useMemo } from 'react' import dynamic from 'next/dynamic' // Lazy-load Fortune Sheet (uses canvas, SSR-incompatible) const Workbook = dynamic( () => import('@fortune-sheet/react').then((m) => m.Workbook), { ssr: false, loading: () =>
Spreadsheet wird geladen...
}, ) // Import Fortune Sheet CSS import '@fortune-sheet/react/dist/index.css' import type { GridZone, GridEditorCell } from '@/components/grid-editor/types' interface SpreadsheetViewProps { unifiedGrid: any // unified_grid_result from backend height?: number } /** * Convert unified grid data to Fortune Sheet format. */ function unifiedGridToSheet(grid: any) { const zone: GridZone | undefined = grid?.zones?.[0] if (!zone) return null const numRows = zone.rows.length const numCols = zone.columns.length // Build celldata array const celldata: any[] = [] const merges: Record = {} // Build cell lookup const cellMap = new Map() for (const cell of zone.cells) { cellMap.set(`${cell.row_index}_${cell.col_index}`, cell) } for (const cell of zone.cells) { const r = cell.row_index const c = cell.col_index const text = cell.text ?? '' const isBox = cell.source_zone_type === 'box' const boxHex = cell.box_region?.bg_hex // Cell value const v: any = { v: text, m: text, } // Bold if (cell.is_bold) { v.bl = 1 } // Text color from word_boxes or color_override const color = cell.color_override ?? cell.word_boxes?.find((wb: any) => wb.color_name && wb.color_name !== 'black')?.color if (color) { v.fc = color } // Box background tint if (isBox && boxHex) { v.bg = `${boxHex}15` // very light tint } // Multi-line: enable text wrap if (text.includes('\n')) { v.tb = '2' // text wrap } celldata.push({ r, c, v }) // Colspan → merge const colspan = cell.colspan || 0 if (colspan > 1 || cell.col_type === 'spanning_header') { const cs = colspan || numCols merges[`${r}_${c}`] = { r, c, rs: 1, cs } } } // Column widths from zone columns const columnlen: Record = {} for (const col of zone.columns) { const w = (col.x_max_px ?? 0) - (col.x_min_px ?? 0) columnlen[String(col.index)] = Math.max(60, Math.round(w * 0.4)) // scale down for screen } // Row heights const dominantH = grid.dominant_row_h || 30 const rowlen: Record = {} for (const row of zone.rows) { // Count max lines in this row's cells const rowCells = zone.cells.filter((c: any) => c.row_index === row.index) const maxLines = Math.max(1, ...rowCells.map((c: any) => (c.text ?? '').split('\n').length)) rowlen[String(row.index)] = Math.max(22, Math.round(dominantH * 0.6 * maxLines)) } // Box region borders const borderInfo: any[] = [] // Collect box cells and draw borders around box regions const boxCells = zone.cells.filter((c: any) => c.source_zone_type === 'box' && c.box_region?.border) if (boxCells.length > 0) { const boxHex = boxCells[0].box_region?.bg_hex || '#2563eb' const boxRows = [...new Set(boxCells.map((c: any) => c.row_index))].sort((a: number, b: number) => a - b) const boxCols = [...new Set(boxCells.map((c: any) => c.col_index))].sort((a: number, b: number) => a - b) if (boxRows.length > 0 && boxCols.length > 0) { borderInfo.push({ rangeType: 'range', borderType: 'border-all', color: boxHex, style: 1, range: [{ row: [boxRows[0], boxRows[boxRows.length - 1]], column: [boxCols[0], boxCols[boxCols.length - 1]], }], }) } } return { name: 'Seite', id: 'unified', celldata, row: numRows, column: numCols, config: { merge: Object.keys(merges).length > 0 ? merges : undefined, columnlen, rowlen, borderInfo: borderInfo.length > 0 ? borderInfo : undefined, }, } } export function SpreadsheetView({ unifiedGrid, height = 600 }: SpreadsheetViewProps) { const sheet = useMemo(() => unifiedGridToSheet(unifiedGrid), [unifiedGrid]) if (!sheet) { return
Keine Daten für Spreadsheet.
} return (
) }