"use client"; import React, { useEffect, useState, useMemo, useCallback } from "react"; import { DocumentTable, TableColumn, TableRowData } from "@/types/online-table"; import { DEFAULT_TABLE_COLUMNS, DEFAULT_TABLE_ROWS, deleteOnlineTable, getDocumentTable, } from "@/lib/online-table"; import { Loader2, Table, Maximize2, Trash2 } from "lucide-react"; interface CompactTablePreviewProps { tableId: string; onFullScreen: () => void; onDelete?: () => void; } const useTableData = (tableId: string) => { const [table, setTable] = useState(null); const [isLoading, setIsLoading] = useState(true); const [version, setVersion] = useState(0); const refresh = useCallback(() => setVersion((prev) => prev + 1), []); useEffect(() => { let aborted = false; setIsLoading(true); getDocumentTable(tableId) .then((data) => { if (aborted) return; setTable({ ...data, title: data.title || "未命名表格" }); }) .catch((err) => { console.error("Failed to load table:", err); if (aborted) return; setTable(null); }) .finally(() => { if (!aborted) { setIsLoading(false); } }); return () => { aborted = true; }; }, [tableId, version]); return { table, isLoading, refresh }; }; const pickCellDisplayValue = (cell: any) => { if (!cell) return undefined; if (cell.m !== undefined && cell.m !== null) return cell.m; if (cell.v?.m !== undefined && cell.v?.m !== null) return cell.v.m; if (cell.v?.v !== undefined && cell.v?.v !== null) return cell.v.v; if (cell.v !== undefined && cell.v !== null && typeof cell.v !== "object") return cell.v; if (cell.w !== undefined && cell.w !== null) return cell.w; return undefined; }; const deriveRowsFromLuckysheet = (luckysheetData: any, columns: TableColumn[]): TableRowData[] => { const sheets = Array.isArray(luckysheetData) ? luckysheetData : []; const sheet = sheets[0]; if (!sheet) return []; const columnIds = columns.length > 0 ? columns.map((col) => col.id) : Array.from({ length: DEFAULT_TABLE_COLUMNS }, (_, index) => `col${index + 1}`); const rows: TableRowData[] = []; const dataGrid = Array.isArray(sheet.data) ? sheet.data : []; dataGrid.forEach((rowData: any[], rowIndex: number) => { if (!Array.isArray(rowData)) return; const row: TableRowData = {}; let hasValue = false; columnIds.forEach((colId, colIndex) => { const value = pickCellDisplayValue(rowData[colIndex]); if (value !== undefined && value !== null && value !== "") { row[colId] = value; hasValue = true; } }); if (hasValue) rows.push(row); }); if (rows.length === 0 && Array.isArray(sheet.celldata)) { const map = new Map(); sheet.celldata.forEach((cell: { r: number; c: number; v: unknown }) => { const value = pickCellDisplayValue(cell?.v ?? cell); if (value === undefined || value === null || value === "") return; const existing = map.get(cell.r) ?? {}; existing[columnIds[cell.c] ?? `col${cell.c + 1}`] = value; map.set(cell.r, existing); }); Array.from(map.entries()) .sort(([a], [b]) => a - b) .forEach(([, row]) => rows.push(row)); } return rows; }; const renderCellContent = (column: TableColumn, value: any) => { if (value === undefined || value === null || value === "") { return ""; } if (column.type === "select") { const option = column.options?.find((opt) => opt.value === value); if (option) { return ( {value} ); } } return String(value); }; const CompactTablePreview: React.FC = ({ tableId, onFullScreen, onDelete }) => { const { table, isLoading, refresh } = useTableData(tableId); const [visibleRows, setVisibleRows] = useState(5); const [visibleCols, setVisibleCols] = useState(5); const schemaColumns = useMemo( () => Array.isArray(table?.schema?.columns) ? table!.schema.columns : [], [table], ); const rows: TableRowData[] = useMemo(() => { if (!table) return []; if (Array.isArray(table.snapshot?.rows) && table.snapshot.rows.length > 0) { return table.snapshot.rows as TableRowData[]; } if ((table.snapshot as { luckysheet?: unknown })?.luckysheet) { return deriveRowsFromLuckysheet( (table.snapshot as { luckysheet?: unknown }).luckysheet, schemaColumns, ); } return []; }, [schemaColumns, table]); useEffect(() => { const handleSaved = (event: Event) => { const detail = (event as CustomEvent<{ tableId?: string }>).detail; if (detail?.tableId === tableId) { refresh(); } }; const handleDeleted = (event: Event) => { const detail = (event as CustomEvent<{ tableId?: string }>).detail; if (detail?.tableId === tableId) { refresh(); } }; window.addEventListener("online-table-saved", handleSaved as EventListener); window.addEventListener("online-table-deleted", handleDeleted as EventListener); return () => { window.removeEventListener("online-table-saved", handleSaved as EventListener); window.removeEventListener("online-table-deleted", handleDeleted as EventListener); }; }, [refresh, tableId]); const hasData = rows.length > 0 && schemaColumns.length > 0; const totalRowsAvailable = hasData ? rows.length : DEFAULT_TABLE_ROWS; const totalColsAvailable = hasData ? schemaColumns.length : DEFAULT_TABLE_COLUMNS; useEffect(() => { const maxRows = Math.max(5, totalRowsAvailable); const maxCols = Math.max(5, totalColsAvailable); if (visibleRows > maxRows) { setVisibleRows(maxRows); } else if (visibleRows < 5) { setVisibleRows(5); } if (visibleCols > maxCols) { setVisibleCols(maxCols); } else if (visibleCols < 5) { setVisibleCols(5); } }, [totalRowsAvailable, totalColsAvailable, visibleRows, visibleCols]); const columns = useMemo(() => { if (schemaColumns.length > 0) { return schemaColumns.slice(0, visibleCols); } const placeholderCount = Math.max(visibleCols, 5); return Array.from({ length: placeholderCount }, (_, index) => ({ id: `placeholder_col_${index}`, name: "", type: "text" as const, width: 100, })); }, [schemaColumns, visibleCols]); const rowsToDisplay = useMemo(() => { if (rows.length > 0) { return rows.slice(0, visibleRows); } const placeholderCount = Math.max(visibleRows, 5); return Array.from({ length: placeholderCount }, (_, index) => ({ id: `placeholder_row_${index}` })); }, [rows, visibleRows]); const headerCells = useMemo(() => { const cells = [ , ]; columns.forEach((col) => { cells.push( {col.name} , ); }); return cells; }, [columns]); const suppressEditorEvents = useCallback((event: React.MouseEvent) => { event.stopPropagation(); }, []); const handleDeleteTable = useCallback(async () => { const confirmed = window.confirm("删除表格将同步移除 Supabase 记录,确认继续?"); if (!confirmed) return; try { await deleteOnlineTable(tableId); window.dispatchEvent(new CustomEvent("online-table-deleted", { detail: { tableId } })); onDelete?.(); } catch (error) { console.error("删除表格失败", error); window.alert("删除失败,请稍后重试"); } }, [onDelete, tableId]); if (isLoading) { return (
); } if (!table) { return (
表格加载失败或不存在。 ); } return (

{table.title}

只读预览,双击进入全屏编辑
{headerCells} {rowsToDisplay.map((row, index) => { const rowId = (row as { id?: string }).id || index; return ( ))} ); })} {rows.length > rowsToDisplay.length && ( )}
{columns.map((col) => ( {renderCellContent(col, (row as Record)[col.id])}
... 更多 {rows.length - rowsToDisplay.length} 行数据,双击进入全屏查看
); }; export default CompactTablePreview;