import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { useParams } from 'react-router';
import { useRealtime } from './contexts/RealtimeContext';
const EditCellPortal = React.memo(({
cell,
table,
EditCell,
onSaveStart,
onSaveEnd,
onError,
isEditable
}) => {
const { formId, formType, sheetName, direction } = useParams();
const tableKey = `${formId}_${formType}_${sheetName}_${direction}`;
const ref = useRef(null);
const [refTbody, setRefTbody] = useState(null);
const [isSaving, setIsSaving] = useState(false);
const { endEditing: contextEndEditing } = useRealtime();
useEffect(() => {
if (ref.current) {
const tbodyRef = ref.current.offsetParent?.offsetParent?.offsetParent;
if (tbodyRef && tbodyRef !== refTbody) {
setRefTbody(tbodyRef);
}
}
}, []);
const handleChange = useCallback( (val) => {
table.setEditingCell(null);
try {
if (table.options.meta?.updateCell) {
const success = table.options.meta.updateCell(
cell.row,
cell.column,
val
);
if (!success) {
console.error('Failed to update cell via WebSocket');
onError?.('Не удалось сохранить изменение');
}
}
} catch (error) {
console.error('Error updating cell:', error);
onError?.(error.message);
}
}, [table, cell, onError]);
const editCellProps = useMemo(() => ({
refCell: ref,
cell,
value: cell.getValue(),
disabled: !isEditable,
onChange: handleChange,
tableId: tableKey,
table,
cellId: `${cell.row.id}_${cell.column.id}`,
}), [cell, isEditable, handleChange, tableKey, table]);
const portalContent = useMemo(() => {
if (!refTbody || isSaving) return null;
return createPortal(
,
refTbody
);
}, [refTbody, isSaving, editCellProps, EditCell]);
return (
<>
{portalContent}
>
);
});
export const getTableColumns = ({
Cell,
EditCell,
columnsConfig,
onCellUpdateError,
onCellNumberClick,
isCellInvalid,
}) => {
const columnColors = { ...columnsConfig.colors };
const columns = structuredClone(columnsConfig.columns);
const cellPropsCache = new WeakMap();
const editPropsCache = new WeakMap();
const getCachedCellProps = (row, column, table) => {
const key = `${row.id}_${column.id}`;
const value = row.getValue(column.id);
const isInvalid = isCellInvalid?.(column.id, value, row.original) || false;
if (!cellPropsCache.has(row)) {
cellPropsCache.set(row, {});
}
const rowCache = cellPropsCache.get(row);
if (rowCache[key]) {
const cached = rowCache[key];
const currentGlobalFilter = table.getState().globalFilter;
const currentColumnFilter = table.getState().columnFilters?.find(f => f.id === column.id)?.value;
if (cached.globalFilter === currentGlobalFilter &&
cached.columnFilter === currentColumnFilter &&
cached.value === value &&
cached.isInvalid === isInvalid) {
return cached;
}
}
const globalFilter = table.getState().globalFilter;
const columnFilter = table.getState().columnFilters?.find(f => f.id === column.id)?.value;
const props = {
globalFilter,
columnFilter,
isEditable: row.original?.row_type === 'INPUT' || false,
backgroundColor: columnColors[column.id]?.color_type?.[row.original?.row_type || row.row_type],
isUpdating: table.options.meta?.updatingCells?.[`${row.id}_${column.id}`],
isInvalid,
value,
_hash: `${globalFilter}_${columnFilter}_${row.id}_${column.id}_${value}_${isInvalid}`,
};
rowCache[key] = props;
return props;
};
const getCachedEditProps = (row, column, table) => {
const key = `${row.id}_${column.id}`;
if (!editPropsCache.has(row)) {
editPropsCache.set(row, {});
}
const rowCache = editPropsCache.get(row);
if (rowCache[key]) {
return rowCache[key];
}
const props = {
isEditable: row.original?.row_type === 'INPUT' || false,
};
rowCache[key] = props;
return props;
};
// Оптимизированная функция processColumns
const processColumns = (columns) => {
columns.forEach((col) => {
col.Cell = ({ cell, table, column, row }) => {
const props = getCachedCellProps(row, column, table);
const cellProps = useMemo(() => ({
row,
column,
cell,
...props,
}), [row.id, column.id, cell.getValue(), props._hash]);
return | ;
};
col.Edit = ({ cell, table, row, column }) => {
const props = getCachedEditProps(row, column, table);
const editComponent = useMemo(() => (
), [cell, table, EditCell, onCellUpdateError, props.isEditable]);
return editComponent;
};
if (col.columns?.length) {
processColumns(col.columns);
}
});
};
processColumns(columns);
const rowNumber = {
accessorKey: 'sort_order',
header: '#',
size: 50,
enableEditing: false,
Cell: ({ cell, row }) => {
const handleClick = useCallback(() => {
onCellNumberClick(row.id);
}, [row.id, onCellNumberClick]);
return ;
},
};
return [rowNumber, ...columns];
};