2026-10-16 14:55:58 +03:00

187 lines
6.5 KiB
JavaScript

import { MaterialReactTable, useMaterialReactTable } from 'material-react-table';
import { memo, useMemo } from 'react';
import DenominationSelect from '../../components/common/DenominationSelect';
import useDenomination from '../../components/common/useDenomination';
import { blueColumn, greenColumn, orangeColumn } from '../../components/RealtimeTable/constants/columnColors';
import { sectionCodeColor } from '../../components/RealtimeTable/constants/columnConfig';
import { buildRowHierarchy } from '../../components/RealtimeTable/utils/rowUtils';
const tableSize = (rem) => rem * 16;
const ROW_VIRTUALIZER_OPTIONS = {
overscan: 8,
estimateSize: () => 37,
measureElement: (element) => element?.offsetHeight || 37,
};
const SOURCE_COLORS = { ahr: greenColumn, oper: blueColumn, cap: orangeColumn };
const getSummaryRowColor = (row, colorBySource) => {
const rowType = row.original?.row_type || row.row_type;
if (colorBySource) {
const source = row.original?.data?.header?.source?.toLowerCase();
if (SOURCE_COLORS[source]) return SOURCE_COLORS[source][rowType];
}
const sectionCodeValue = row.original?.data?.section_code ?? row.original?.data?.header?.section_code;
if (sectionCodeValue === null || sectionCodeValue === undefined || sectionCodeValue === '') return undefined;
const sectionCode = String(sectionCodeValue)[0];
return (sectionCodeColor[sectionCode] || blueColumn)[rowType];
};
const getTextColor = (backgroundColor) => {
if (!backgroundColor?.startsWith('#') || backgroundColor.length !== 7) return '#000';
const red = Number.parseInt(backgroundColor.slice(1, 3), 16);
const green = Number.parseInt(backgroundColor.slice(3, 5), 16);
const blue = Number.parseInt(backgroundColor.slice(5, 7), 16);
return 0.299 * red + 0.587 * green + 0.114 * blue < 128 ? '#fff' : '#000';
};
export const buildSummaryTree = (sourceRows) =>
buildRowHierarchy(sourceRows, (row, index) => ({
...row,
_rowId: `${row.sort_order ?? index}-${row.data?.section_code ?? row.data?.header?.section_code ?? index}`,
}));
export const filterSummaryTree = (tree, query) =>
tree.flatMap((row) => {
const matches = [row.data?.section_code, row.data?.name, row.data?.header?.section_code, row.data?.header?.name].some((value) =>
String(value || '')
.toLowerCase()
.includes(query),
);
if (matches) return [row];
const subRows = filterSummaryTree(row.subRows, query);
return subRows.length ? [{ ...row, subRows }] : [];
});
export const countSummaryTree = (tree) => tree.reduce((count, row) => count + 1 + countSummaryTree(row.subRows), 0);
const denominationKey = (tableKey) => `summary_${tableKey}_denomination`;
const scaleColumns = (columns, divisor) => columns.map((column) => {
const children = column.columns ? scaleColumns(column.columns, divisor) : undefined;
const originalCell = column.Cell;
const isAmount = !['section_code', 'name', 'mrt-row-expand'].includes(column.id) && /plan|approved|fact|corrected|detail|total|quarter|residual|actual|adj|base|correction/i.test(column.id || '');
return {
...column,
...(children ? { columns: children } : {}),
...(isAmount ? { Cell: ({ cell }) => {
const value = cell.getValue();
if (value === null || value === undefined || value === '') return originalCell ? originalCell({ cell }) : '—';
const numeric = Number(value);
if (!Number.isFinite(numeric)) return originalCell ? originalCell({ cell }) : value;
if (numeric === 0) return '-';
return new Intl.NumberFormat('ru-RU', { maximumFractionDigits: 2 }).format(numeric / divisor);
} } : {}),
};
});
export const SummaryTable = memo(function SummaryTable({ columns, data, loading, colorBySource = false, tableKey = 'default' }) {
const storageKey = denominationKey(tableKey);
const { divisor } = useDenomination(storageKey);
const scaledColumns = useMemo(() => scaleColumns(columns, divisor), [columns, divisor]);
const table = useMaterialReactTable({
columns: scaledColumns,
data,
enableExpanding: true,
enableExpandAll: true,
getSubRows: (row) => row.subRows,
getRowId: (row) => row._rowId,
enableColumnActions: false,
enableColumnFilters: false,
enableDensityToggle: false,
enableFullScreenToggle: false,
enableHiding: false,
enablePagination: false,
enableRowVirtualization: true,
rowVirtualizerOptions: ROW_VIRTUALIZER_OPTIONS,
enableTopToolbar: false,
enableBottomToolbar: false,
enableSorting: false,
displayColumnDefOptions: {
'mrt-row-expand': {
size: tableSize(3.75),
minSize: tableSize(3.75),
maxSize: tableSize(3.75),
grow: false,
},
},
state: { isLoading: loading },
initialState: { columnPinning: { left: ['mrt-row-expand', 'section_code', 'name'] }, expanded: true },
muiTablePaperProps: {
elevation: 0,
sx: {
border: '0.0625rem solid #e5e7eb',
borderRadius: '0.75rem',
overflow: 'hidden',
flex: 1,
minHeight: 0,
width: '100%',
display: 'flex',
flexDirection: 'column',
},
},
muiTableContainerProps: {
sx: {
flex: 1,
overflow: 'auto',
minHeight: 0,
height: '100%',
'& thead': {
position: 'sticky',
top: 0,
zIndex: 10,
},
},
},
muiTableHeadCellProps: {
sx: {
position: 'sticky',
top: 0,
boxShadow: 'none',
backgroundColor: '#f8f9fa',
borderRight: '0.0625rem solid #e5e7eb',
color: '#4a5565',
fontSize: '0.75rem',
fontWeight: 700,
lineHeight: 1.2,
px: '0.75rem',
py: '0.5rem',
whiteSpace: 'nowrap',
},
},
muiTableHeadRowProps: {
sx: {
boxShadow: 'none',
},
},
muiTableBodyCellProps: ({ row, column }) => {
const backgroundColor = getSummaryRowColor(row, colorBySource);
return {
sx: {
backgroundColor,
borderRight: '0.0625rem solid #edf0f2',
color: getTextColor(backgroundColor),
fontSize: '0.8125rem',
px: column.id === 'mrt-row-expand' ? '0.25rem' : '0.75rem',
py: column.id === 'mrt-row-expand' ? 0 : '0.5rem',
whiteSpace: 'nowrap',
textAlign: ['section_code', 'name'].includes(column.id) ? 'left' : 'right',
},
};
},
muiExpandButtonProps: { sx: { width: '1.25rem', height: '1.25rem', p: 0, '& .MuiSvgIcon-root': { fontSize: '1.125rem' } } },
localization: { noRecordsToDisplay: 'Нет данных для выбранных параметров' },
});
return <MaterialReactTable table={table} />;
});
export function SummaryDenominationControl({ tableKey = 'default' }) {
const storageKey = denominationKey(tableKey);
const { denomination, setDenomination } = useDenomination(storageKey);
return <DenominationSelect value={denomination} onChange={setDenomination} />;
}