diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx index c40ca7e..c3bdbac 100644 --- a/web/src/components/RealtimeTable/RealtimeTable.jsx +++ b/web/src/components/RealtimeTable/RealtimeTable.jsx @@ -134,6 +134,11 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { return () => document.removeEventListener('mousedown', handleDocumentClick); }, [selectedColumnId]); + const handleCellUpdateError = useCallback((rowId, columnId, error) => { + console.error(`Error updating cell ${rowId}_${columnId}:`, error); + toast.error('Ошибка обновления ячейки'); + }, []); + const columns = useMemo(() => { if (!columnsConfig || !columnsConfig.columns) return []; try { @@ -143,128 +148,160 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { columnsConfig, onCellUpdate: handleUpdateCell, onCellNumberClick: handleClickRowCell, - onCellUpdateError: (rowId, columnId, error) => { - console.error(`Error updating cell ${rowId}_${columnId}:`, error); - toast.error('Ошибка обновления ячейки') - } + onCellUpdateError: handleCellUpdateError, }); } catch (error) { console.error('Error creating columns:', error); return []; } - }, [columnsConfig, handleUpdateCell, handleClickRowCell]); + }, [columnsConfig, handleUpdateCell, handleClickRowCell, handleCellUpdateError]); + + const selectedColumnIdRef = useRef(selectedColumnId); + + useEffect(() => { + selectedColumnIdRef.current = selectedColumnId; + }, [selectedColumnId]); const tableMeta = useMemo(() => ({ updateCell: handleUpdateCell, - selectedColumnId, - }), [handleUpdateCell, selectedColumnId]); + getSelectedColumnId: () => selectedColumnIdRef.current, + }), [handleUpdateCell]); - // Конфигурация таблицы - const tableConfig = useMemo( - () => ({ - ...BASE_TABLE_CONFIG, - columns, - data: data, - enableRowVirtualization: true, - enableColumnVirtualization: true, - rowVirtualizerInstanceRef: rowVirtualizerRef, - rowVirtualizerOptions: { - overscan: 30, - scrollPaddingStart: 0, - scrollPaddingEnd: 0, - estimateSize: () => TABLE_ROW_HEIGHT, - measureElement: (el) => el?.offsetHeight || TABLE_ROW_HEIGHT, - }, - onEditingCellChange: (cell) => { - if (cell) { - if (editingCell) return; - contextStartEditing?.(cell.row, cell.column); + const ROW_VIRTUALIZER_OPTIONS = { + overscan: 30, + scrollPaddingStart: 0, + scrollPaddingEnd: 0, + estimateSize: () => TABLE_ROW_HEIGHT, + measureElement: (el) => el?.offsetHeight || TABLE_ROW_HEIGHT, + }; + + const createTableConfig = ({ + columns, + data, + columnSizing, + columnPinning, + columnVisibility, + globalFilter, + showColumnFilters, + rowSelection, + containerRef, + tableMeta, + setColumnSizing, + setColumnPinning, + editingCell, + contextStartEditing, + }) => ({ + ...BASE_TABLE_CONFIG, + columns, + data, + enableRowVirtualization: true, + enableColumnVirtualization: true, + rowVirtualizerInstanceRef: rowVirtualizerRef, + rowVirtualizerOptions: ROW_VIRTUALIZER_OPTIONS, + onEditingCellChange: (cell) => { + if (cell && !editingCell) { + contextStartEditing?.(cell.row, cell.column); + } + }, + columnVirtualizerOptions: ({ table }) => ({ + overscan: 10, + measureElement: (el) => { + if (!el) return 150; + const index = Number(el?.getAttribute?.('data-index')); + const isPinned = Boolean(el?.getAttribute?.('data-pinned')); + if (isPinned) { + const colId = table.getState().columnPinning.left[index]; + const column = table.getColumn(colId); + return column?.getSize() ?? 150; } - }, - columnVirtualizerOptions: ({ table }) => ({ - overscan: 10, - measureElement: (el) => { - if (!el) return 150; - const index = Number(el?.getAttribute?.('data-index')); - const isPinned = Boolean(el?.getAttribute?.('data-pinned')); - if (isPinned) { - const colId = table.getState().columnPinning.left[index]; - const column = table.getColumn(colId); - return column?.getSize() ?? 150; - } - const allCols = [...table.getLeftVisibleLeafColumns(), ...table.getCenterVisibleLeafColumns()] - return allCols[index]?.getSize() ?? 150; - - }, - }), - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onColumnSizingChange: setColumnSizing, - onColumnPinningChange: setColumnPinning, - onGlobalFilterChange: handleGlobalFilterChange, - state: { - columnSizing, - columnPinning, - columnVisibility, - globalFilter, - showColumnFilters, - rowSelection, - editingCell, - }, - initialState: { - expanded: true, - }, - meta: tableMeta, - muiTableHeadCellProps: { - sx: { boxSizing: 'border-box' }, - }, - muiTableBodyCellProps: getTableBodyCellProps, - muiTableHeadProps: { - sx: { - display: 'table-header-group', - height: '1px', - minHeight: '1px', - maxHeight: '1px', - visibility: 'hidden', - '& svg': { - height: '1px', - minHeight: '1px', - maxHeight: '1px', - visibility: 'hidden', - } - }, - }, - muiTablePaperProps: getTablePaperStyles(), - muiTableContainerProps: { - ref: containerRef, - sx: { - position: 'relative', - contain: 'layout', - minHeight: '100%', - '& .MuiTable-root': { position: 'relative' }, - }, - }, - muiTableHeadCellFilterTextFieldProps: { - placeholder: 'Поиск...', - size: 'small', + const allCols = [...table.getLeftVisibleLeafColumns(), ...table.getCenterVisibleLeafColumns()] + return allCols[index]?.getSize() ?? 150; }, }), - [ - columns, - data, - setColumnSizing, - setColumnPinning, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onColumnSizingChange: setColumnSizing, + onColumnPinningChange: setColumnPinning, + onGlobalFilterChange: handleGlobalFilterChange, + state: { columnSizing, columnPinning, columnVisibility, globalFilter, showColumnFilters, - containerRef, - tableMeta, rowSelection, - editingCell - ], - ); + editingCell, + }, + initialState: { + expanded: true, + }, + meta: tableMeta, + muiTableHeadCellProps: { + sx: { boxSizing: 'border-box' }, + }, + muiTableBodyCellProps: getTableBodyCellProps, + muiTableHeadProps: { + sx: { + display: 'table-header-group', + height: '1px', + minHeight: '1px', + maxHeight: '1px', + visibility: 'hidden', + '& svg': { + height: '1px', + minHeight: '1px', + maxHeight: '1px', + visibility: 'hidden', + } + }, + }, + muiTablePaperProps: getTablePaperStyles(), + muiTableContainerProps: { + ref: containerRef, + sx: { + position: 'relative', + contain: 'layout', + minHeight: '100%', + '& .MuiTable-root': { position: 'relative' }, + }, + }, + muiTableHeadCellFilterTextFieldProps: { + placeholder: 'Поиск...', + size: 'small', + }, + }); + + // Конфигурация таблицы + const tableConfig = useMemo(() => createTableConfig({ + columns, + data, + columnSizing, + columnPinning, + columnVisibility, + globalFilter, + showColumnFilters, + rowSelection, + containerRef, + tableMeta, + setColumnSizing, + setColumnPinning, + editingCell, + contextStartEditing, + }), [ + columns, + data, + columnSizing, + columnPinning, + columnVisibility, + globalFilter, + showColumnFilters, + rowSelection, + containerRef, + tableMeta, + setColumnSizing, + setColumnPinning, + editingCell, + ]); const table = useMaterialReactTable(tableConfig); @@ -342,6 +379,17 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { setRowSelection({}); }, [rowSelection, table]) + useEffect(() => { + return () => { + setData([]); + setRowSelection({}); + setEditingCell(null); + if (rowVirtualizerRef.current) { + rowVirtualizerRef.current = null; + } + }; + }, []); + return ( <>