265 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { 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(<VspDropdownEditCell {...editCellProps} />, refTbody);
}
return createPortal(<EditCell {...editCellProps} />, refTbody);
}, [refTbody, editCellProps, EditCell, editType]);
return (
<>
<div ref={ref} style={{ width: '100%', height: '100%' }} />
{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 <CellComponent {...cellProps} vspOptions={vspOptions} />;
});
export const getTableColumns = ({
Cell,
EditCell,
columnsConfig,
columnsCurStage,
userRoleId,
onCellUpdateError,
onCellNumberClick,
isCellInvalid,
denomination = 'rub',
}) => {
const displayDivisor = denomination === 'billion' ? 1000000000 : denomination === 'million' ? 1000000 : denomination === 'thousand' ? 1000 : 1;
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 ? <CellWithVspOptions CellComponent={Cell} cellProps={cellProps} /> : <Cell {...cellProps} />;
};
col.Edit = ({ cell, table, row, column }) => {
const props = getCachedEditProps(row, column);
const editComponent = useMemo(
() => <EditCellPortal cell={cell} table={table} EditCell={EditCell} onError={onCellUpdateError} isEditable={props.isEditable} />,
[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 <button type='button' onClick={handleClick}>{`${cell.getValue()} `}</button>;
},
};
return [rowNumber, ...columns];
};