Compare commits

..

No commits in common. "18ec053e445fe445e50ad6658ecf40d8e7bce793" and "e6133b82fe5a6a918ed8c37d2dd2ecc10ac3050a" have entirely different histories.

3 changed files with 56 additions and 26 deletions

View File

@ -23,7 +23,7 @@ const FORMULA_INDICATOR_STYLES = {
const TEXTAREA_STYLES = { const TEXTAREA_STYLES = {
width: '100%', width: '100%',
height: '3rem', height: 'auto',
fieldSizing: 'content', fieldSizing: 'content',
fontSize: 'inherit', fontSize: 'inherit',
}; };
@ -32,6 +32,7 @@ const EditCell = memo(({ refCell, onChange, disabled, value, tableId, cellId, ta
const { endEditing: contextEndEditing } = useRealtime(); const { endEditing: contextEndEditing } = useRealtime();
const storageKey = `formula_${tableId}_${cellId}`; const storageKey = `formula_${tableId}_${cellId}`;
// Объединенное состояние
const [state, setState] = useState(() => ({ const [state, setState] = useState(() => ({
currentValue: value, currentValue: value,
displayValue: value, displayValue: value,
@ -39,7 +40,7 @@ const EditCell = memo(({ refCell, onChange, disabled, value, tableId, cellId, ta
})); }));
// Позиция ячейки // Позиция ячейки
const [position, setPosition] = useState(); const [position, setPosition] = useState({ left: 0, width: 0, translateY: '' });
const textareaRef = useRef(null); const textareaRef = useRef(null);
const refFinished = useRef(false); const refFinished = useRef(false);
@ -88,8 +89,8 @@ const EditCell = memo(({ refCell, onChange, disabled, value, tableId, cellId, ta
if (!el) return; if (!el) return;
const resize = () => { const resize = () => {
el.style.height = 'auto';
el.style.minHeight = '3rem'; el.style.minHeight = '3rem';
el.style.height = '3rem';
el.style.height = el.scrollHeight + 'px'; el.style.height = el.scrollHeight + 'px';
el.style.maxHeight = '180px'; el.style.maxHeight = '180px';
}; };
@ -101,6 +102,16 @@ const EditCell = memo(({ refCell, onChange, disabled, value, tableId, cellId, ta
return () => observer.disconnect(); return () => observer.disconnect();
}, [state.currentValue]); }, [state.currentValue]);
// Фокус при монтировании
useEffect(() => {
const el = textareaRef.current;
if (el && !disabled) {
el.focus();
el.setSelectionRange(el.value.length, el.value.length);
}
}, [disabled]);
// Сохранение
const handleSave = useCallback(() => { const handleSave = useCallback(() => {
const { currentValue } = state; const { currentValue } = state;
@ -135,8 +146,9 @@ const EditCell = memo(({ refCell, onChange, disabled, value, tableId, cellId, ta
} }
}, [state, storageKey, onChange]); }, [state, storageKey, onChange]);
// Завершение редактирования
const finishEditing = useCallback(() => { const finishEditing = useCallback(() => {
if (refFinished.current) return; if(refFinished.current) return;
refFinished.current = true; refFinished.current = true;
if (!disabled) { if (!disabled) {
handleSave(); handleSave();
@ -158,6 +170,7 @@ const EditCell = memo(({ refCell, onChange, disabled, value, tableId, cellId, ta
return () => document.removeEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside);
}, [finishEditing]); }, [finishEditing]);
// Обработчики событий
const handleChange = useCallback((e) => { const handleChange = useCallback((e) => {
const value = e.target.value; const value = e.target.value;
setState(prev => ({ setState(prev => ({
@ -174,11 +187,12 @@ const EditCell = memo(({ refCell, onChange, disabled, value, tableId, cellId, ta
} }
}, [finishEditing]); }, [finishEditing]);
// Мемоизация стилей
const editorStyles = useMemo(() => ({ const editorStyles = useMemo(() => ({
...EDITOR_STYLES, ...EDITOR_STYLES,
left: position?.left || 0, left: position.left,
width: position?.width || 0, width: position.width,
transform: position?.translateY || 0, transform: position.translateY,
}), [position]); }), [position]);
const textareaStyles = useMemo(() => ({ const textareaStyles = useMemo(() => ({
@ -190,23 +204,20 @@ const EditCell = memo(({ refCell, onChange, disabled, value, tableId, cellId, ta
return ( return (
<tr> <tr>
<td> <td>
{position && <div style={editorStyles}>
<div style={editorStyles}> {state.isFormulaMode && (
{state.isFormulaMode && ( <div style={FORMULA_INDICATOR_STYLES}>fx</div>
<div style={FORMULA_INDICATOR_STYLES}>fx</div> )}
)} <textarea
<textarea ref={textareaRef}
ref={textareaRef} disabled={disabled}
disabled={disabled} style={textareaStyles}
style={textareaStyles} onChange={handleChange}
onChange={handleChange} value={state.currentValue}
value={state.currentValue} onKeyDown={handleKeyDown}
onKeyDown={handleKeyDown} placeholder={state.isFormulaMode ? 'Введите формулу (начинается с =)' : ''}
placeholder={state.isFormulaMode ? 'Введите формулу (начинается с =)' : ''} />
/> </div>
</div>
}
</td> </td>
</tr> </tr>
); );

View File

@ -1,3 +1,4 @@
// components/RealtimeTable/index.js
import React, { useState, useMemo, useRef, useEffect, useCallback, useTransition } from 'react'; import React, { useState, useMemo, useRef, useEffect, useCallback, useTransition } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { import {

View File

@ -1,8 +1,10 @@
// getTableColumns.js
import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react'; import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { useRealtime } from './contexts/RealtimeContext'; import { useRealtime } from './contexts/RealtimeContext';
// Мемоизированный компонент EditCellPortal
const EditCellPortal = React.memo(({ const EditCellPortal = React.memo(({
cell, cell,
table, table,
@ -20,6 +22,7 @@ const EditCellPortal = React.memo(({
const { endEditing: contextEndEditing } = useRealtime(); const { endEditing: contextEndEditing } = useRealtime();
// Оптимизация: вычисляем позицию только при монтировании и изменении cell
useEffect(() => { useEffect(() => {
if (ref.current) { if (ref.current) {
const tbodyRef = ref.current.offsetParent?.offsetParent?.offsetParent; const tbodyRef = ref.current.offsetParent?.offsetParent?.offsetParent;
@ -27,8 +30,9 @@ const EditCellPortal = React.memo(({
setRefTbody(tbodyRef); setRefTbody(tbodyRef);
} }
} }
}, []); }, [refTbody]); // Добавляем refTbody в зависимости
// Мемоизация обработчика изменения
const handleChange = useCallback( (val) => { const handleChange = useCallback( (val) => {
table.setEditingCell(null); table.setEditingCell(null);
try { try {
@ -49,6 +53,7 @@ const EditCellPortal = React.memo(({
} }
}, [table, cell, onError]); }, [table, cell, onError]);
// Мемоизация пропсов для EditCell
const editCellProps = useMemo(() => ({ const editCellProps = useMemo(() => ({
refCell: ref, refCell: ref,
cell, cell,
@ -60,6 +65,7 @@ const EditCellPortal = React.memo(({
cellId: `${cell.row.id}_${cell.column.id}`, cellId: `${cell.row.id}_${cell.column.id}`,
}), [cell, isEditable, handleChange, tableKey, table]); }), [cell, isEditable, handleChange, tableKey, table]);
// Мемоизация портала
const portalContent = useMemo(() => { const portalContent = useMemo(() => {
if (!refTbody || isSaving) return null; if (!refTbody || isSaving) return null;
return createPortal( return createPortal(
@ -71,12 +77,14 @@ const EditCellPortal = React.memo(({
return ( return (
<> <>
<div ref={ref} style={{ width: '100%', height: '100%' }} /> <div ref={ref} style={{ width: '100%', height: '100%' }} />
{portalContent} {portalContent}
</> </>
); );
}); });
EditCellPortal.displayName = 'EditCellPortal';
// Основная функция getTableColumns
export const getTableColumns = ({ export const getTableColumns = ({
Cell, Cell,
EditCell, EditCell,
@ -87,9 +95,11 @@ export const getTableColumns = ({
const columnColors = { ...columnsConfig.colors }; const columnColors = { ...columnsConfig.colors };
const columns = structuredClone(columnsConfig.columns); const columns = structuredClone(columnsConfig.columns);
// Кэши для вычисленных значений
const cellPropsCache = new WeakMap(); const cellPropsCache = new WeakMap();
const editPropsCache = new WeakMap(); const editPropsCache = new WeakMap();
// Функция для получения кешированных пропсов Cell
const getCachedCellProps = (row, column, table) => { const getCachedCellProps = (row, column, table) => {
const key = `${row.id}_${column.id}`; const key = `${row.id}_${column.id}`;
@ -99,6 +109,7 @@ export const getTableColumns = ({
const rowCache = cellPropsCache.get(row); const rowCache = cellPropsCache.get(row);
if (rowCache[key]) { if (rowCache[key]) {
// Проверяем, не изменились ли фильтры
const cached = rowCache[key]; const cached = rowCache[key];
const currentGlobalFilter = table.getState().globalFilter; const currentGlobalFilter = table.getState().globalFilter;
const currentColumnFilter = table.getState().columnFilters?.find(f => f.id === column.id)?.value; const currentColumnFilter = table.getState().columnFilters?.find(f => f.id === column.id)?.value;
@ -125,6 +136,7 @@ export const getTableColumns = ({
return props; return props;
}; };
// Функция для получения кешированных пропсов Edit
const getCachedEditProps = (row, column, table) => { const getCachedEditProps = (row, column, table) => {
const key = `${row.id}_${column.id}`; const key = `${row.id}_${column.id}`;
@ -148,9 +160,11 @@ export const getTableColumns = ({
// Оптимизированная функция processColumns // Оптимизированная функция processColumns
const processColumns = (columns) => { const processColumns = (columns) => {
columns.forEach((col) => { columns.forEach((col) => {
// Оптимизированный Cell
col.Cell = ({ cell, table, column, row }) => { col.Cell = ({ cell, table, column, row }) => {
const props = getCachedCellProps(row, column, table); const props = getCachedCellProps(row, column, table);
// Используем useMemo для мемоизации пропсов
const cellProps = useMemo(() => ({ const cellProps = useMemo(() => ({
row, row,
column, column,
@ -161,9 +175,11 @@ export const getTableColumns = ({
return <Cell {...cellProps} />; return <Cell {...cellProps} />;
}; };
// Оптимизированный Edit
col.Edit = ({ cell, table, row, column }) => { col.Edit = ({ cell, table, row, column }) => {
const props = getCachedEditProps(row, column, table); const props = getCachedEditProps(row, column, table);
// Мемоизируем компонент EditCellPortal
const editComponent = useMemo(() => ( const editComponent = useMemo(() => (
<EditCellPortal <EditCellPortal
cell={cell} cell={cell}
@ -177,6 +193,7 @@ export const getTableColumns = ({
return editComponent; return editComponent;
}; };
// Рекурсивно обрабатываем вложенные колонки
if (col.columns?.length) { if (col.columns?.length) {
processColumns(col.columns); processColumns(col.columns);
} }
@ -185,6 +202,7 @@ export const getTableColumns = ({
processColumns(columns); processColumns(columns);
// Оптимизированная колонка с номерами строк
const rowNumber = { const rowNumber = {
accessorKey: 'sort_order', accessorKey: 'sort_order',
header: '#', header: '#',