import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { useParams } from 'react-router';
import { useRealtime } from './contexts/RealtimeContext';
import { sectionCodeColor } from './constants/columnConfig';
import { blueColumn, greenColumn } from './constants/columnColors';
import { useAuth } from '../../app/context/AuthProvider';
import { ROLES_NAME_ID } from '../../constants/constants';
import VspDropdownEditCell from './Cell/EditCell/VspDropdownEditCell';
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 editType = cell.column.columnDef?.editType;
const portalContent = useMemo(() => {
if (!refTbody || isSaving) return null;
if (editType === 'vsp_dropdown') {
return createPortal(
,
refTbody
);
}
return createPortal(
,
refTbody
);
}, [refTbody, isSaving, editCellProps, EditCell, editType]);
return (
<>
{portalContent}
>
);
});
function getColorCell(colors, column, row) {
if (row.original.data.section_code) {
const section_code = row.original.data.section_code[0];
return (sectionCodeColor[section_code] || blueColumn)[row.original?.row_type || row.row_type];
}
return colors[column.id]?.color_type?.[row.original?.row_type || row.row_type];
}
export const getTableColumns = ({
Cell,
EditCell,
columnsConfig,
columnsCurStage,
onCellUpdateError,
onCellNumberClick,
isCellInvalid,
}) => {
const columnColors = { ...columnsConfig.colors };
const columns = structuredClone(columnsConfig.columns);
const { user } = useAuth();
const cellPropsCache = new WeakMap();
const editPropsCache = new WeakMap();
const isEditable = (row, column) => {
if (row.original?.row_type !== 'INPUT') return false;
if (user.role_id === 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;
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: isEditable(row, column),
backgroundColor: getColorCell(columnColors, column, row),
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: isEditable(row, column),
};
rowCache[key] = props;
return props;
};
// Оптимизированная функция processColumns
const processColumns = (columns) => {
columns.forEach((col) => {
col.Cell = ({ cell, table, column, row }) => {
const { vspOptions } = useRealtime();
const props = getCachedCellProps(row, column, table);
const cellProps = useMemo(() => ({
row,
column,
cell,
vspOptions,
...props,
}), [row, column, cell, props, vspOptions,]);
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.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(() => {
onCellNumberClick(row.id);
}, [row.id, onCellNumberClick]);
return ;
},
};
return [rowNumber, ...columns];
};