import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react'; import { createPortal } from 'react-dom'; import { useParams } from 'react-router'; import { useVspOptions } from './contexts/RealtimeContext'; import { getDenominationDivisor } from '../common/useDenomination'; import { ROLES_NAME_ID } from '../../constants/constants'; import { blueColumn } from './constants/columnColors'; import { sectionCodeColor } from './constants/columnConfig'; import VspDropdownEditCell from './Cell/EditCell/VspDropdownEditCell'; const EditCellPortal = React.memo(({ cell, table, EditCell, onError, isEditable }) => { const { formId, formType, sheetName, direction } = useParams(); const tableKey = `${formId}_${formType}_${sheetName}_${direction}`; const ref = useRef(null); const [refTbody, setRefTbody] = useState(null); 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 editType = cell.column.columnDef?.editType; const portalContent = useMemo(() => { if (!refTbody) return null; if (editType === 'vsp_dropdown') { return createPortal(, refTbody); } return createPortal(, refTbody); }, [refTbody, editCellProps, EditCell, editType]); return ( <>
{portalContent} ); }); function getColorCell(colors, column, row) { const rowType = row?.original?.row_type || row?.row_type; const sectionCodeValue = row?.original?.data?.section_code ?? row?.original?.data?.header?.section_code; if (colors[column.id]?.color_type?.[rowType]) { return colors[column.id]?.color_type?.[rowType]; } if (sectionCodeValue !== null && sectionCodeValue !== undefined && sectionCodeValue !== '') { const sectionCode = String(sectionCodeValue)[0]; return (sectionCodeColor[sectionCode] || blueColumn)[rowType]; } return 'white'; } const UPPERCASE_MONTH_PATTERN = /ЯНВАРЬ|ФЕВРАЛЬ|МАРТ|АПРЕЛЬ|МАЙ|ИЮНЬ|ИЮЛЬ|АВГУСТ|СЕНТЯБРЬ|ОКТЯБРЬ|НОЯБРЬ|ДЕКАБРЬ/g; const normalizeMonthHeader = (header) => { if (typeof header !== 'string') return header; return header.replace(UPPERCASE_MONTH_PATTERN, (month, offset) => { const normalizedMonth = month.toLowerCase(); if (offset !== 0) return normalizedMonth; return normalizedMonth[0].toUpperCase() + normalizedMonth.slice(1); }); }; const CellWithVspOptions = React.memo(({ CellComponent, cellProps }) => { const vspOptions = useVspOptions(); return ; }); export const getTableColumns = ({ Cell, EditCell, columnsConfig, columnsCurStage, userRoleId, onCellUpdateError, onCellNumberClick, isCellInvalid, denomination = 'rub', }) => { const displayDivisor = getDenominationDivisor(denomination); const columnColors = { ...columnsConfig.colors }; const columns = structuredClone(columnsConfig.columns); const cellPropsCache = new WeakMap(); const editPropsCache = new WeakMap(); const isEditable = (row, column) => { if (row.original?.row_type !== 'INPUT') return false; if (userRoleId === ROLES_NAME_ID.admin) return true; const keyCol = column.id; if (columnsCurStage.includes(keyCol)) return true; return false; }; 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; const isEditableCell = isEditable(row, column); const backgroundColor = getColorCell(columnColors, column, row); 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.displayDivisor === displayDivisor && cached.isInvalid === isInvalid && cached.backgroundColor === backgroundColor ) { return cached; } } const globalFilter = table.getState().globalFilter; const columnFilter = table.getState().columnFilters?.find((f) => f.id === column.id)?.value; const props = { globalFilter, columnFilter, isEditable: isEditableCell, backgroundColor, isInvalid, value, displayDivisor, _hash: `${globalFilter}_${columnFilter}_${key}_${value}_${isInvalid}_${backgroundColor}_${displayDivisor}`, }; rowCache[key] = props; return props; }; const getCachedEditProps = (row, column) => { 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: isEditable(row, column), }; rowCache[key] = props; return props; }; // Оптимизированная функция processColumns const processColumns = (columns) => { for (const col of columns) { col.header = normalizeMonthHeader(col.header); const usesVspOptions = col.editType === 'vsp_dropdown'; col.Cell = ({ cell, table, column, row }) => { const props = getCachedCellProps(row, column, table); const cellProps = useMemo( () => ({ row, column, cell, ...props, }), [row, column, cell, props], ); return usesVspOptions ? : ; }; col.Edit = ({ cell, table, row, column }) => { const props = getCachedEditProps(row, column); const editComponent = useMemo( () => , [cell, table, EditCell, onCellUpdateError, props.isEditable], ); return editComponent; }; if (col.editType === 'vsp_dropdown') { col.enableEditing = (row) => row?.original?.row_type === 'INPUT'; } 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((event) => { event.stopPropagation(); onCellNumberClick(row.id); }, [row.id, onCellNumberClick]); return ; }, }; return [rowNumber, ...columns]; };