import React, { useEffect, useState, useRef, useCallback } from 'react'; import { useRows, useEditCell } from '../api/hooks'; import { useSessionStore } from '../store/session'; import { formatCellValue, typeBadgeClass, typeLabel } from '../lib/format'; interface Props { cellId: string; config: Record; } export function TableCellView({ cellId, config }: Props) { const { columns } = useSessionStore(); const { fetch: fetchRows, loading, error } = useRows(); const { edit: editCell, loading: editing } = useEditCell(); const [rows, setRows] = useState[]>([]); const [colNames, setColNames] = useState([]); const [offset, setOffset] = useState(0); const [total, setTotal] = useState(0); const [editMode, setEditMode] = useState(false); const limit = 50; // Inline editing state const [editingCell, setEditingCell] = useState<{ rowIdx: number; colName: string } | null>(null); const [editValue, setEditValue] = useState(''); const editInputRef = useRef(null); useEffect(() => { loadData(); }, [offset]); useEffect(() => { if (editingCell && editInputRef.current) { editInputRef.current.focus(); editInputRef.current.select(); } }, [editingCell]); const loadData = async () => { const data = await fetchRows(offset, limit); if (data) { setRows(data.rows); setColNames(data.columns); setTotal(data.total); } }; const handleDoubleClick = useCallback((rowIdx: number, colName: string, currentValue: any) => { if (!editMode) return; setEditingCell({ rowIdx, colName }); setEditValue(currentValue == null ? '' : String(currentValue)); }, [editMode]); const handleEditSubmit = useCallback(async () => { if (!editingCell) return; const { rowIdx, colName } = editingCell; try { const result = await editCell(rowIdx, colName, editValue); if (result) { // Refresh data after successful edit await loadData(); } } catch { // Error handled by hook } setEditingCell(null); setEditValue(''); }, [editingCell, editValue, editCell]); const handleEditKeyDown = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); handleEditSubmit(); } else if (e.key === 'Escape') { setEditingCell(null); setEditValue(''); } }, [handleEditSubmit]); const totalPages = Math.ceil(total / limit); const currentPage = Math.floor(offset / limit) + 1; return (
{loading && rows.length === 0 ? (
Loading data...
) : error ? (
{error}
) : ( <> {/* Table toolbar with edit mode toggle */}
{editMode && ( Double-click a cell to edit )}
{colNames.map((name) => { const col = columns.find((c) => c.name === name); return ( ); })} {rows.map((row, i) => { const globalRowIdx = offset + i; return ( {colNames.map((name) => { const isEditing = editingCell?.rowIdx === globalRowIdx && editingCell?.colName === name; const cellValue = row[name]; if (isEditing) { return ( ); } return ( ); })} ); })}
#
{typeLabel(col?.column_type || 'unknown')} {name}
{globalRowIdx + 1} setEditValue(e.target.value)} onBlur={handleEditSubmit} onKeyDown={handleEditKeyDown} disabled={editing} /> handleDoubleClick(globalRowIdx, name, cellValue)} title={editMode ? 'Double-click to edit' : undefined} > {formatCellValue(cellValue)}
{offset + 1}-{Math.min(offset + limit, total)} of {total.toLocaleString()} rows
Page {currentPage} / {totalPages}
)}
); }