Merge origin/feat/236-aurora-1843-svod2-server-paging-web: база обновлена на исправленный 1842
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
85769602b9
@ -1,8 +1,9 @@
|
|||||||
import RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';
|
import RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';
|
||||||
import { Alert, Box, Button, Paper, Tab, Tabs, Typography } from '@mui/material';
|
import { Alert, Box, Button, Paper, Tab, Tabs, Typography } from '@mui/material';
|
||||||
import { MaterialReactTable, useMaterialReactTable } from 'material-react-table';
|
import { MRT_TableHead, MaterialReactTable, useMaterialReactTable } from 'material-react-table';
|
||||||
import { MRT_Localization_RU } from 'material-react-table/locales/ru';
|
import { MRT_Localization_RU } from 'material-react-table/locales/ru';
|
||||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
||||||
@ -13,7 +14,7 @@ import useDenomination from '../../components/common/useDenomination';
|
|||||||
import { useSvod } from '../SvodPage/SvodContext';
|
import { useSvod } from '../SvodPage/SvodContext';
|
||||||
import { SvodFilters } from '../SvodPage/SvodFilters';
|
import { SvodFilters } from '../SvodPage/SvodFilters';
|
||||||
import { EMPTY_FACET_MAP, getSvod2RowId, isPlaceholderRow, shouldShowFound } from './svod2Paging';
|
import { EMPTY_FACET_MAP, getSvod2RowId, isPlaceholderRow, shouldShowFound } from './svod2Paging';
|
||||||
import { SVOD2_TABLE_CONTAINER_SX, createColumnClassMap, getEstimateTypeClassName } from './svod2Styles';
|
import { SVOD2_LAYOUT_OPTIONS, SVOD2_TABLE_CONTAINER_SX, createColumnClassMap, getEstimateTypeClassName } from './svod2Styles';
|
||||||
import { useSvod2Pages } from './useSvod2Pages';
|
import { useSvod2Pages } from './useSvod2Pages';
|
||||||
|
|
||||||
const SHEETS = [
|
const SHEETS = [
|
||||||
@ -273,17 +274,20 @@ const getColumnConfig = (sheet) => {
|
|||||||
return COLUMN_CONFIG_CACHE.get(sheet);
|
return COLUMN_CONFIG_CACHE.get(sheet);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scaleNumericColumns = (columns, divisor) => columns.map((column) => {
|
const scaleNumericColumns = (columns, divisor) =>
|
||||||
const isText = ['name', 'org_name', 'smeta_type', 'smeta_direction', 'section_code', 'contract_number'].includes(column.id);
|
columns.map((column) => {
|
||||||
const children = column.columns ? scaleNumericColumns(column.columns, divisor) : undefined;
|
const isText = ['name', 'org_name', 'smeta_type', 'smeta_direction', 'section_code', 'contract_number'].includes(column.id);
|
||||||
if (isText || children) return { ...column, ...(children ? { columns: children } : {}) };
|
const children = column.columns ? scaleNumericColumns(column.columns, divisor) : undefined;
|
||||||
return { ...column, Cell: ({ cell }) => formatValue(cell.getValue(), divisor) };
|
if (isText || children) return { ...column, ...(children ? { columns: children } : {}) };
|
||||||
});
|
return { ...column, Cell: ({ cell }) => formatValue(cell.getValue(), divisor) };
|
||||||
|
});
|
||||||
|
|
||||||
export default function Svod2Page() {
|
export default function Svod2Page() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { getSheetFilters, updateSheetFilters } = useSvod();
|
const { getSheetFilters, updateSheetFilters } = useSvod();
|
||||||
const [sheet, setSheet] = useState('GO');
|
const [sheet, setSheet] = useState('GO');
|
||||||
|
// <table> MRT: заголовок рисуем сами через портал (см. комментарий у enableTableHead).
|
||||||
|
const [tableElement, setTableElement] = useState(null);
|
||||||
const denominationStorageKey = `svod2_${sheet}_denomination`;
|
const denominationStorageKey = `svod2_${sheet}_denomination`;
|
||||||
const { denomination, setDenomination: handleDenominationChange, divisor } = useDenomination(denominationStorageKey);
|
const { denomination, setDenomination: handleDenominationChange, divisor } = useDenomination(denominationStorageKey);
|
||||||
const [columnFiltersBySheet, setColumnFiltersBySheet] = useState(() => Object.fromEntries(SHEETS.map(({ value }) => [value, []])));
|
const [columnFiltersBySheet, setColumnFiltersBySheet] = useState(() => Object.fromEntries(SHEETS.map(({ value }) => [value, []])));
|
||||||
@ -356,6 +360,12 @@ export default function Svod2Page() {
|
|||||||
enablePagination: false,
|
enablePagination: false,
|
||||||
enableRowVirtualization: true,
|
enableRowVirtualization: true,
|
||||||
enableColumnVirtualization: true,
|
enableColumnVirtualization: true,
|
||||||
|
// Заголовок MRT при виртуализации колонок рисует строки по индексам листовых колонок,
|
||||||
|
// и группы (colSpan) разъезжаются. Поэтому штатный thead отключён, а тот же MRT_TableHead
|
||||||
|
// без columnVirtualizer (все заголовки сразу) вставляется в <table> порталом.
|
||||||
|
enableTableHead: false,
|
||||||
|
muiTableProps: { ref: setTableElement },
|
||||||
|
...SVOD2_LAYOUT_OPTIONS,
|
||||||
columnVirtualizerOptions: getSvod2ColumnVirtualizerOptions,
|
columnVirtualizerOptions: getSvod2ColumnVirtualizerOptions,
|
||||||
enableTopToolbar: false,
|
enableTopToolbar: false,
|
||||||
enableBottomToolbar: false,
|
enableBottomToolbar: false,
|
||||||
@ -410,7 +420,12 @@ export default function Svod2Page() {
|
|||||||
for (const element of elements ?? []) measured = Math.max(measured, Math.round(element.offsetHeight));
|
for (const element of elements ?? []) measured = Math.max(measured, Math.round(element.offsetHeight));
|
||||||
if (measured > 0 && measured !== rowHeight) setRowHeight(measured);
|
if (measured > 0 && measured !== rowHeight) setRowHeight(measured);
|
||||||
});
|
});
|
||||||
let content = <MaterialReactTable table={table} />;
|
let content = (
|
||||||
|
<>
|
||||||
|
<MaterialReactTable table={table} />
|
||||||
|
{tableElement && createPortal(<MRT_TableHead table={table} />, tableElement)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
if (!hasCurrentData && !loading && !error) {
|
if (!hasCurrentData && !loading && !error) {
|
||||||
content = (
|
content = (
|
||||||
<Alert severity='info'>
|
<Alert severity='info'>
|
||||||
@ -453,7 +468,12 @@ export default function Svod2Page() {
|
|||||||
onChange={(_event, value) => setSheet(value)}
|
onChange={(_event, value) => setSheet(value)}
|
||||||
sx={{ px: '1rem', minHeight: '3rem', borderBottom: '0.0625rem solid #edf0f2' }}>
|
sx={{ px: '1rem', minHeight: '3rem', borderBottom: '0.0625rem solid #edf0f2' }}>
|
||||||
{SHEETS.map((item) => (
|
{SHEETS.map((item) => (
|
||||||
<Tab key={item.value} value={item.value} label={item.label} sx={{ textTransform: 'none', alignItems: 'flex-start', textAlign: 'left', minHeight: '3rem' }} />
|
<Tab
|
||||||
|
key={item.value}
|
||||||
|
value={item.value}
|
||||||
|
label={item.label}
|
||||||
|
sx={{ textTransform: 'none', alignItems: 'flex-start', textAlign: 'left', minHeight: '3rem' }}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<SvodFilters
|
<SvodFilters
|
||||||
|
|||||||
@ -57,8 +57,11 @@ export const SVOD2_TABLE_CONTAINER_SX = {
|
|||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
'& thead': { position: 'sticky', top: 0, zIndex: 10 },
|
// order: -1 — thead вставлен порталом в конец <table> (grid), а должен идти до tbody.
|
||||||
|
'& thead': { position: 'sticky', top: 0, zIndex: 10, order: -1 },
|
||||||
'& td': {
|
'& td': {
|
||||||
|
// Распорки виртуализации колонок (td с inline width) не должны сжиматься во flex-строке.
|
||||||
|
flexShrink: 0,
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
borderRight: '0.0625rem solid #edf0f2',
|
borderRight: '0.0625rem solid #edf0f2',
|
||||||
fontSize: '0.8125rem',
|
fontSize: '0.8125rem',
|
||||||
@ -70,6 +73,7 @@ export const SVOD2_TABLE_CONTAINER_SX = {
|
|||||||
'& td.svod2-group-end': { borderRight: GROUP_END_BORDER },
|
'& td.svod2-group-end': { borderRight: GROUP_END_BORDER },
|
||||||
'& td[data-pinned="true"]': { zIndex: 2 },
|
'& td[data-pinned="true"]': { zIndex: 2 },
|
||||||
'& th': {
|
'& th': {
|
||||||
|
flexShrink: 0,
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
@ -86,6 +90,8 @@ export const SVOD2_TABLE_CONTAINER_SX = {
|
|||||||
'& th.svod2-band-1': { backgroundColor: '#f1f7f3' },
|
'& th.svod2-band-1': { backgroundColor: '#f1f7f3' },
|
||||||
'& th.svod2-group-end': { borderRight: GROUP_END_BORDER },
|
'& th.svod2-group-end': { borderRight: GROUP_END_BORDER },
|
||||||
'& th[data-pinned="true"]': { zIndex: 5 },
|
'& th[data-pinned="true"]': { zIndex: 5 },
|
||||||
|
// enableColumnPinning добавляет закреплённым ячейкам подложку ::before (z-index -1), она перекрывает чередование фона.
|
||||||
|
'& td[data-pinned="true"]::before, & th[data-pinned="true"]::before': { display: 'none' },
|
||||||
'& .svod2-badge': {
|
'& .svod2-badge': {
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
padding: '0.25rem 0.625rem',
|
padding: '0.25rem 0.625rem',
|
||||||
@ -122,3 +128,10 @@ export const SVOD2_TABLE_CONTAINER_SX = {
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Виртуализация колонок требует grid-no-grow (без растяжения ячеек) и enableColumnPinning
|
||||||
|
// (иначе MRT не учитывает закреплённые колонки в левом отступе виртуализатора).
|
||||||
|
export const SVOD2_LAYOUT_OPTIONS = {
|
||||||
|
layoutMode: 'grid-no-grow',
|
||||||
|
enableColumnPinning: true,
|
||||||
|
};
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import {
|
import {
|
||||||
|
SVOD2_LAYOUT_OPTIONS,
|
||||||
SVOD2_TABLE_CONTAINER_SX,
|
SVOD2_TABLE_CONTAINER_SX,
|
||||||
createColumnBandMap,
|
createColumnBandMap,
|
||||||
createColumnClassMap,
|
createColumnClassMap,
|
||||||
@ -71,3 +72,15 @@ test('заглушка свода 2 — прозрачный текст в ст
|
|||||||
assert.ok(rule, 'нет правила заглушки');
|
assert.ok(rule, 'нет правила заглушки');
|
||||||
assert.equal(rule.color, 'transparent');
|
assert.equal(rule.color, 'transparent');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('column virtualization layout uses grid-no-grow and column pinning', () => {
|
||||||
|
assert.equal(SVOD2_LAYOUT_OPTIONS.layoutMode, 'grid-no-grow');
|
||||||
|
assert.equal(SVOD2_LAYOUT_OPTIONS.enableColumnPinning, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('container styles keep spacers unshrunk, hide pinned overlay and order portal thead first', () => {
|
||||||
|
assert.equal(SVOD2_TABLE_CONTAINER_SX['& td'].flexShrink, 0);
|
||||||
|
assert.equal(SVOD2_TABLE_CONTAINER_SX['& th'].flexShrink, 0);
|
||||||
|
assert.equal(SVOD2_TABLE_CONTAINER_SX['& thead'].order, -1);
|
||||||
|
assert.equal(SVOD2_TABLE_CONTAINER_SX['& td[data-pinned="true"]::before, & th[data-pinned="true"]::before'].display, 'none');
|
||||||
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user