187 lines
6.5 KiB
JavaScript
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} />;
|
|
}
|