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