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]; };