@@ -90,12 +95,15 @@ export function HeaderSwitch() {
Администрирование
)}
-
-
-
-
- Справочники
-
+ {(user.is_executor_rf || user.role_id === ROLES_NAME_ID.admin) &&
+
+
+
+
+ Справочники
+
+ }
+
);
diff --git a/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx b/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx
index f92fbd3..f224226 100644
--- a/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx
+++ b/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx
@@ -7,7 +7,6 @@ const BASE_CELL_STYLES = {
height: '100%',
display: 'flex',
alignItems: 'center',
- justifyContent: 'center',
boxSizing: 'border-box',
position: 'absolute',
top: 0,
@@ -98,6 +97,7 @@ const formatNumber = (value, asInteger = false) => {
if (value === null || value === undefined || value === '') return String(value || '');
const num = Number(value);
if (Number.isNaN(num)) return String(value);
+ if (num === 0) return '-';
return num.toLocaleString('ru-RU', {
minimumFractionDigits: asInteger ? 0 : 1,
maximumFractionDigits: asInteger ? 0 : 1,
@@ -141,9 +141,10 @@ const CellComponent = ({ cell, row, column, onClick, globalFilter, columnFilter,
const isLocked = useCellLock(cellKey);
const isVspDropdown = column?.columnDef?.editType === 'vsp_dropdown';
+ const nameIndent = column.id === 'data.header.name' ? 8 + row.depth * 16 : 8;
// Мемоизация значения
- const { rawValue, displayValue } = useMemo(() => {
+ const { rawValue, displayValue, isNumeric } = useMemo(() => {
const value = cell.getValue();
const isNum = value !== null && value !== undefined && value !== '' && !Number.isNaN(Number(value));
let display = String(value || '');
@@ -177,13 +178,16 @@ const CellComponent = ({ cell, row, column, onClick, globalFilter, columnFilter,
const cellStyles = useMemo(
() => ({
...BASE_CELL_STYLES,
+ paddingLeft: nameIndent,
+ justifyContent: isNumeric ? 'flex-end' : 'flex-start',
+ textAlign: isNumeric ? 'right' : 'left',
backgroundColor: isLocked ? '#f5f5f5' : backgroundColor,
color: textColor,
...(isLocked ? LOCKED_STYLES : {}),
...(isInvalid ? INVALID_STYLES : {}),
...(isEditable ? EDITABLE_STYLES : NOT_EDITABLE_STYLES),
}),
- [backgroundColor, isInvalid, isLocked, textColor, isEditable],
+ [backgroundColor, isInvalid, isLocked, textColor, isEditable, isNumeric, nameIndent],
);
const lockBadge = useMemo(() => {
@@ -211,6 +215,7 @@ const Cell = React.memo(CellComponent, (prevProps, nextProps) => {
return (
prevProps.cell.getValue() === nextProps.cell.getValue() &&
prevProps.row.id === nextProps.row.id &&
+ prevProps.row.depth === nextProps.row.depth &&
prevProps.column.id === nextProps.column.id &&
prevProps.globalFilter === nextProps.globalFilter &&
prevProps.columnFilter === nextProps.columnFilter &&
diff --git a/web/src/components/RealtimeTable/Cell/EditCell/EditCell.jsx b/web/src/components/RealtimeTable/Cell/EditCell/EditCell.jsx
index 66ce216..acdf758 100644
--- a/web/src/components/RealtimeTable/Cell/EditCell/EditCell.jsx
+++ b/web/src/components/RealtimeTable/Cell/EditCell/EditCell.jsx
@@ -1,6 +1,6 @@
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useRealtimeActions } from '../../contexts/RealtimeContext';
-import { calculateFormula, extractFormula, formatNumber, isFormula } from '../../utils/formulaUtils';
+import { calculateFormula, extractFormula, formatNumber, isFormula, normalizeNumericInput } from '../../utils/formulaUtils';
import { loadFromStorage, saveToStorage } from '../../utils/localStorageUtils';
const EDITOR_STYLES = {
@@ -144,7 +144,7 @@ const EditCell = memo(({ refCell, onChange, disabled, value, tableId, cellId, ta
displayValue: currentValue,
isFormulaMode: false,
}));
- onChange?.(currentValue);
+ onChange?.(normalizeNumericInput(currentValue));
}
}, [state, storageKey, onChange]);
diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx
index 827babb..e03d971 100644
--- a/web/src/components/RealtimeTable/RealtimeTable.jsx
+++ b/web/src/components/RealtimeTable/RealtimeTable.jsx
@@ -21,8 +21,10 @@ import { useRealtimeActions, useVspOptions } from './contexts/RealtimeContext';
import { useColumnSettings } from './hooks/useColumnSettings';
import { useHeaderPortal } from './hooks/useHeaderPortal';
import { useTableScale } from './hooks/useTableScale';
+import { useBudgetYear } from './hooks/useBudgetYear';
import { useValidationRules } from './hooks/useValidationRules';
import { getRowId } from './utils/rowUtils';
+import { getColumnVirtualizerOptions, getOrderedVisibleColumns } from './utils/columnLayoutUtils';
import { DictVspApi } from '../../api/dict-vsp';
import { useAuth } from '../../app/context/AuthProvider';
@@ -39,24 +41,6 @@ const ROW_VIRTUALIZER_OPTIONS = {
measureElement: (element) => element?.offsetHeight || TABLE_ROW_HEIGHT,
};
-const getColumnVirtualizerOptions = ({ table }) => {
- const orderedVisibleColumns = table.getVisibleLeafColumns();
- const getColumnSize = (index) => orderedVisibleColumns[index]?.getSize() ?? 150;
- const scrollPaddingStart = table.getLeftVisibleLeafColumns().reduce((width, column) => width + column.getSize(), 0);
- const scrollPaddingEnd = table.getRightVisibleLeafColumns().reduce((width, column) => width + column.getSize(), 0);
-
- return {
- overscan: 10,
- scrollPaddingStart,
- scrollPaddingEnd,
- estimateSize: getColumnSize,
- measureElement: (element) => {
- if (!element) return 150;
- return getColumnSize(Number(element.getAttribute('data-index')));
- },
- };
-};
-
const hasVspDropdown = (columns) =>
columns.some((column) => column.editType === 'vsp_dropdown' || (column.columns?.length && hasVspDropdown(column.columns)));
@@ -91,6 +75,7 @@ const loadingOverlayStyle = {
};
const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
+ const budgetYear = useBudgetYear(formId, formType, year);
const { user } = useAuth();
const vspOptions = useVspOptions();
const userRoleId = user?.role_id;
@@ -372,6 +357,20 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
columnVirtualizerOptions: getColumnVirtualizerOptions,
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
+ muiTableBodyRowProps: ({ row }) => ({
+ onClick: () => setRowSelection({ [row.id]: true }),
+ hover: false,
+ sx: {
+ '& > td::after': {
+ content: row.getIsSelected() ? '""' : 'none',
+ position: 'absolute',
+ inset: 0,
+ backgroundColor: 'rgba(0, 0, 0, 0.08)',
+ pointerEvents: 'none',
+ zIndex: 11,
+ },
+ },
+ }),
onColumnSizingChange: setColumnSizing,
onColumnPinningChange: setColumnPinning,
onExpandedChange: setExpanded,
@@ -493,7 +492,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
const pinnedIds = new Set([...(columnPinning?.left || []), ...(columnPinning?.right || [])]);
if (pinnedIds.has(columnId)) return;
- const visibleColumns = table.getVisibleLeafColumns();
+ const visibleColumns = getOrderedVisibleColumns(table);
const columnIndex = visibleColumns.findIndex((column) => column.id === columnId);
if (columnIndex < 0) return;
@@ -766,6 +765,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
return (
<>
{
{headerPortalRef.current &&
createPortal(
{columns ? (
{
return (
{
- return [...table.getLeftVisibleLeafColumns(), ...table.getCenterVisibleLeafColumns(), ...table.getRightVisibleLeafColumns()];
-};
+import { formatHeaderYear } from '../utils/headerYearUtils';
+import { getOrderedVisibleColumns } from '../utils/columnLayoutUtils';
const getColumnPinnedStyles = (column) => {
const pinningPosition = column.getIsPinned();
@@ -16,7 +14,7 @@ const getColumnPinnedStyles = (column) => {
};
const ColumnNumbersRow = ({ table, selectedColumnId, onColumnSelect }) => {
- const sortedColumns = getSortedColumns(table);
+ const sortedColumns = getOrderedVisibleColumns(table);
const tableState = table.getState();
const filteredColumns = sortedColumns.filter(
(col) => !(col.id in tableState.columnVisibility) || (col.id in tableState.columnVisibility && tableState.columnVisibility[col.id]),
@@ -104,7 +102,7 @@ const getColorBrightness = (hexColor) => {
return 0.299 * r + 0.587 * g + 0.114 * b;
};
-const HeaderCell = ({ header, table, pinningPosition, onClick, onChangeWidth }) => {
+const HeaderCell = ({ header, table, pinningPosition, onClick, onChangeWidth, budgetYear }) => {
const column = header.column;
const headerSize = header.getSize();
const isPinned = Boolean(pinningPosition);
@@ -174,7 +172,7 @@ const HeaderCell = ({ header, table, pinningPosition, onClick, onChangeWidth })
maxWidth: 'calc(100% - 1rem)',
boxSizing: 'border-box',
}}>
- {header.isPlaceholder ? null : column.columnDef.header}
+ {header.isPlaceholder ? null : formatHeaderYear(column.columnDef.header, budgetYear)}