266 lines
7.5 KiB
JavaScript
266 lines
7.5 KiB
JavaScript
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(<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 = 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 ? <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];
|
||
};
|