AURORA-1842: свод 2 — виртуализация колонок и статичные стили #227
@ -1,16 +1,19 @@
|
||||
import RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';
|
||||
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 { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
||||
import { getColumnVirtualizerOptions } from '../../components/RealtimeTable/utils/columnLayoutUtils';
|
||||
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
||||
import DenominationSelect from '../../components/common/DenominationSelect';
|
||||
import useDenomination from '../../components/common/useDenomination';
|
||||
import { useSvod } from '../SvodPage/SvodContext';
|
||||
import { SvodFilters } from '../SvodPage/SvodFilters';
|
||||
import { SVOD2_LAYOUT_OPTIONS, SVOD2_TABLE_CONTAINER_SX, createColumnClassMap, getEstimateTypeClassName } from './svod2Styles';
|
||||
|
||||
const SHEETS = [
|
||||
{ value: 'GO', label: 'ССП' },
|
||||
@ -27,10 +30,9 @@ const ROW_VIRTUALIZER_OPTIONS = {
|
||||
estimateSize: () => ROW_HEIGHT,
|
||||
measureElement: (element) => element?.offsetHeight || ROW_HEIGHT,
|
||||
};
|
||||
const COLUMN_BAND_COLORS = {
|
||||
head: ['#f8f9fa', '#f1f7f3'],
|
||||
body: ['#ffffff', '#f8fbf9'],
|
||||
};
|
||||
|
||||
const COLUMN_VIRTUALIZER_OVERSCAN = 3;
|
||||
const getSvod2ColumnVirtualizerOptions = (args) => ({ ...getColumnVirtualizerOptions(args), overscan: COLUMN_VIRTUALIZER_OVERSCAN });
|
||||
|
||||
const valueAt = (row, path) => path.reduce((value, key) => value?.[key], row.data);
|
||||
|
||||
@ -73,11 +75,6 @@ const CARRYOVER_HEADERS = {
|
||||
2: 'Корректировка из 2 квартала в 3–4 кварталы',
|
||||
3: 'Корректировка из 3 квартала в 4 квартал',
|
||||
};
|
||||
const ESTIMATE_TYPE_STYLES = {
|
||||
АХР: { backgroundColor: '#ecf9f0', borderColor: '#a7d9b8', color: '#08783e' },
|
||||
Операц: { backgroundColor: '#eff6ff', borderColor: '#93c5fd', color: '#1d4ed8' },
|
||||
КВ: { backgroundColor: '#fff7ed', borderColor: '#fdba74', color: '#c2410c' },
|
||||
};
|
||||
|
||||
const createCorrectionColumns = (quarter) => ({
|
||||
id: `q${quarter}.correction`,
|
||||
@ -165,28 +162,7 @@ const createColumns = (sheet) => [
|
||||
Cell: ({ cell }) => {
|
||||
const value = cell.getValue();
|
||||
if (!value) return '—';
|
||||
const colors = ESTIMATE_TYPE_STYLES[value] || {
|
||||
backgroundColor: '#f3f4f6',
|
||||
borderColor: '#d1d5db',
|
||||
color: '#4b5563',
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
component='span'
|
||||
sx={{
|
||||
display: 'inline-block',
|
||||
px: '0.625rem',
|
||||
py: '0.25rem',
|
||||
border: '0.0625rem solid',
|
||||
borderRadius: '0.5rem',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.35,
|
||||
whiteSpace: 'nowrap',
|
||||
...colors,
|
||||
}}>
|
||||
{value}
|
||||
</Box>
|
||||
);
|
||||
return <span className={getEstimateTypeClassName(value)}>{value}</span>;
|
||||
},
|
||||
}),
|
||||
textColumn('smeta_direction', 'Направление', ['header', 'smeta_direction'], 10, { enableColumnFilter: true, filterVariant: 'select' }),
|
||||
@ -196,28 +172,7 @@ const createColumns = (sheet) => [
|
||||
Cell: ({ cell }) => {
|
||||
const value = cell.getValue();
|
||||
if (!value) return '—';
|
||||
return (
|
||||
<Box
|
||||
component='span'
|
||||
sx={{
|
||||
display: 'inline-block',
|
||||
maxWidth: '100%',
|
||||
px: '0.625rem',
|
||||
py: '0.25rem',
|
||||
border: '0.0625rem solid #a7d9b8',
|
||||
borderRadius: '0.5rem',
|
||||
backgroundColor: '#ecf9f0',
|
||||
color: '#08783e',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.35,
|
||||
textTransform: 'uppercase',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{value}
|
||||
</Box>
|
||||
);
|
||||
return <span className='svod2-org-badge'>{value}</span>;
|
||||
},
|
||||
}),
|
||||
textColumn('name', 'Наименование статьи', ['header', 'name'], 20),
|
||||
@ -295,30 +250,6 @@ const createColumns = (sheet) => [
|
||||
createActualColumns(4),
|
||||
];
|
||||
|
||||
const createColumnBandMap = (columns) => {
|
||||
const bands = new Map();
|
||||
const addColumn = (column, band) => {
|
||||
bands.set(column.id, band);
|
||||
column.columns?.forEach((childColumn) => addColumn(childColumn, band));
|
||||
};
|
||||
columns.forEach((column, index) => addColumn(column, index % 2));
|
||||
return bands;
|
||||
};
|
||||
|
||||
const createColumnGroupEndSet = (columns) => {
|
||||
const groupEnds = new Set();
|
||||
const getLastLeafId = (column) => {
|
||||
if (!column.columns?.length) return column.id;
|
||||
return getLastLeafId(column.columns.at(-1));
|
||||
};
|
||||
columns.forEach((column) => {
|
||||
if (!column.columns?.length) return;
|
||||
groupEnds.add(column.id);
|
||||
groupEnds.add(getLastLeafId(column));
|
||||
});
|
||||
return groupEnds;
|
||||
};
|
||||
|
||||
const disableGroupColumnFilters = (columns) =>
|
||||
columns.map((column) => {
|
||||
if (!column.columns?.length) return column;
|
||||
@ -336,20 +267,20 @@ const getColumnConfig = (sheet) => {
|
||||
const columns = disableGroupColumnFilters(createColumns(sheet));
|
||||
COLUMN_CONFIG_CACHE.set(sheet, {
|
||||
columns,
|
||||
columnBands: createColumnBandMap(columns),
|
||||
columnGroupEnds: createColumnGroupEndSet(columns),
|
||||
columnClassNames: createColumnClassMap(columns),
|
||||
});
|
||||
}
|
||||
|
||||
return COLUMN_CONFIG_CACHE.get(sheet);
|
||||
};
|
||||
|
||||
const scaleNumericColumns = (columns, divisor) => columns.map((column) => {
|
||||
const isText = ['name', 'org_name', 'smeta_type', 'smeta_direction', 'section_code', 'contract_number'].includes(column.id);
|
||||
const children = column.columns ? scaleNumericColumns(column.columns, divisor) : undefined;
|
||||
if (isText || children) return { ...column, ...(children ? { columns: children } : {}) };
|
||||
return { ...column, Cell: ({ cell }) => formatValue(cell.getValue(), divisor) };
|
||||
});
|
||||
const scaleNumericColumns = (columns, divisor) =>
|
||||
columns.map((column) => {
|
||||
const isText = ['name', 'org_name', 'smeta_type', 'smeta_direction', 'section_code', 'contract_number'].includes(column.id);
|
||||
const children = column.columns ? scaleNumericColumns(column.columns, divisor) : undefined;
|
||||
if (isText || children) return { ...column, ...(children ? { columns: children } : {}) };
|
||||
return { ...column, Cell: ({ cell }) => formatValue(cell.getValue(), divisor) };
|
||||
});
|
||||
|
||||
const matchesSearch = (row, query) => {
|
||||
const searchableData = {
|
||||
@ -368,6 +299,8 @@ export default function Svod2Page() {
|
||||
const navigate = useNavigate();
|
||||
const { getSheetFilters, updateSheetFilters, getSummary, invalidateSummary } = useSvod();
|
||||
const [sheet, setSheet] = useState('GO');
|
||||
// <table> MRT: заголовок рисуем сами через портал (см. комментарий у enableTableHead).
|
||||
const [tableElement, setTableElement] = useState(null);
|
||||
const denominationStorageKey = `svod2_${sheet}_denomination`;
|
||||
const { denomination, setDenomination: handleDenominationChange, divisor } = useDenomination(denominationStorageKey);
|
||||
const [tableStateBySheet, setTableStateBySheet] = useState(createInitialTableState);
|
||||
@ -460,7 +393,7 @@ export default function Svod2Page() {
|
||||
const normalizedSearch = search.trim().toLowerCase();
|
||||
return normalizedSearch ? rows.filter((row) => matchesSearch(row, normalizedSearch)) : rows;
|
||||
}, [hasCurrentData, rows, search]);
|
||||
const { columns: rawColumns, columnBands, columnGroupEnds } = useMemo(() => getColumnConfig(sheet), [sheet]);
|
||||
const { columns: rawColumns, columnClassNames } = useMemo(() => getColumnConfig(sheet), [sheet]);
|
||||
const columns = useMemo(() => scaleNumericColumns(rawColumns, divisor), [rawColumns, divisor]);
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
@ -475,6 +408,14 @@ export default function Svod2Page() {
|
||||
enableHiding: false,
|
||||
enablePagination: false,
|
||||
enableRowVirtualization: true,
|
||||
enableColumnVirtualization: true,
|
||||
// Заголовок MRT при виртуализации колонок рисует строки по индексам листовых колонок,
|
||||
// и группы (colSpan) разъезжаются. Поэтому штатный thead отключён, а тот же MRT_TableHead
|
||||
// без columnVirtualizer (все заголовки сразу) вставляется в <table> порталом.
|
||||
enableTableHead: false,
|
||||
muiTableProps: { ref: setTableElement },
|
||||
...SVOD2_LAYOUT_OPTIONS,
|
||||
columnVirtualizerOptions: getSvod2ColumnVirtualizerOptions,
|
||||
enableTopToolbar: false,
|
||||
enableBottomToolbar: false,
|
||||
enableSorting: false,
|
||||
@ -497,15 +438,7 @@ export default function Svod2Page() {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
muiTableContainerProps: {
|
||||
sx: {
|
||||
flex: 1,
|
||||
overflow: 'auto',
|
||||
minHeight: 0,
|
||||
height: '100%',
|
||||
'& thead': { position: 'sticky', top: 0, zIndex: 10 },
|
||||
},
|
||||
},
|
||||
muiTableContainerProps: { sx: SVOD2_TABLE_CONTAINER_SX },
|
||||
muiTableHeadProps: {
|
||||
sx: {
|
||||
'& tr:not(:last-of-type) > th > .MuiCollapse-root': {
|
||||
@ -513,43 +446,20 @@ export default function Svod2Page() {
|
||||
},
|
||||
},
|
||||
},
|
||||
muiTableHeadCellProps: ({ column }) => ({
|
||||
sx: {
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
boxShadow: 'none',
|
||||
backgroundColor: COLUMN_BAND_COLORS.head[columnBands.get(column.id) ?? 0],
|
||||
borderRight: columnGroupEnds.has(column.id) ? '0.1875rem solid #9eafa3' : '0.0625rem solid #e5e7eb',
|
||||
color: '#4a5565',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.2,
|
||||
px: '0.75rem',
|
||||
py: '0.5rem',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: 1,
|
||||
zIndex: column.getIsPinned() ? 5 : undefined,
|
||||
},
|
||||
}),
|
||||
muiTableHeadCellProps: ({ column }) => ({ className: columnClassNames.get(column.id) }),
|
||||
muiTableHeadRowProps: { sx: { boxShadow: 'none' } },
|
||||
muiTableBodyCellProps: ({ column }) => ({
|
||||
sx: {
|
||||
backgroundColor: COLUMN_BAND_COLORS.body[columnBands.get(column.id) ?? 0],
|
||||
borderRight: columnGroupEnds.has(column.id) ? '0.1875rem solid #9eafa3' : '0.0625rem solid #edf0f2',
|
||||
fontSize: '0.8125rem',
|
||||
px: '0.75rem',
|
||||
py: '0.5rem',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: 1,
|
||||
zIndex: column.getIsPinned() ? 2 : undefined,
|
||||
},
|
||||
}),
|
||||
muiTableBodyCellProps: ({ column }) => ({ className: columnClassNames.get(column.id) }),
|
||||
localization: {
|
||||
...MRT_Localization_RU,
|
||||
noRecordsToDisplay: 'Нет данных для выбранных параметров',
|
||||
},
|
||||
});
|
||||
let content = <MaterialReactTable table={table} />;
|
||||
let content = (
|
||||
<>
|
||||
<MaterialReactTable table={table} />
|
||||
{tableElement && createPortal(<MRT_TableHead table={table} />, tableElement)}
|
||||
</>
|
||||
);
|
||||
if (!hasCurrentData && !loading && !error) {
|
||||
content = (
|
||||
<Alert severity='info'>
|
||||
@ -592,7 +502,12 @@ export default function Svod2Page() {
|
||||
onChange={(_event, value) => setSheet(value)}
|
||||
sx={{ px: '1rem', minHeight: '3rem', borderBottom: '0.0625rem solid #edf0f2' }}>
|
||||
{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>
|
||||
<SvodFilters
|
||||
|
||||
130
web/src/pages/Svod2Page/svod2Styles.js
Normal file
130
web/src/pages/Svod2Page/svod2Styles.js
Normal file
@ -0,0 +1,130 @@
|
||||
// Чистые функции и статичные стили свода 2. Без JSX, чтобы тестироваться через node --test.
|
||||
|
||||
export const createColumnBandMap = (columns) => {
|
||||
const bands = new Map();
|
||||
const addColumn = (column, band) => {
|
||||
bands.set(column.id, band);
|
||||
column.columns?.forEach((childColumn) => addColumn(childColumn, band));
|
||||
};
|
||||
columns.forEach((column, index) => addColumn(column, index % 2));
|
||||
return bands;
|
||||
};
|
||||
|
||||
export const createColumnGroupEndSet = (columns) => {
|
||||
const groupEnds = new Set();
|
||||
const getLastLeafId = (column) => {
|
||||
if (!column.columns?.length) return column.id;
|
||||
return getLastLeafId(column.columns.at(-1));
|
||||
};
|
||||
columns.forEach((column) => {
|
||||
if (!column.columns?.length) return;
|
||||
groupEnds.add(column.id);
|
||||
groupEnds.add(getLastLeafId(column));
|
||||
});
|
||||
return groupEnds;
|
||||
};
|
||||
|
||||
export const getSvod2CellClassName = (columnId, bands, groupEnds) => {
|
||||
const classNames = [];
|
||||
if (bands.get(columnId) === 1) classNames.push('svod2-band-1');
|
||||
if (groupEnds.has(columnId)) classNames.push('svod2-group-end');
|
||||
return classNames.length ? classNames.join(' ') : undefined;
|
||||
};
|
||||
|
||||
// columnId -> className, считается один раз на лист, а не на каждую ячейку.
|
||||
export const createColumnClassMap = (columns) => {
|
||||
const bands = createColumnBandMap(columns);
|
||||
const groupEnds = createColumnGroupEndSet(columns);
|
||||
const classMap = new Map();
|
||||
for (const columnId of bands.keys()) classMap.set(columnId, getSvod2CellClassName(columnId, bands, groupEnds));
|
||||
return classMap;
|
||||
};
|
||||
|
||||
const ESTIMATE_TYPE_CLASSES = {
|
||||
АХР: 'svod2-badge-ahr',
|
||||
Операц: 'svod2-badge-oper',
|
||||
КВ: 'svod2-badge-kv',
|
||||
};
|
||||
|
||||
export const getEstimateTypeClassName = (value) => `svod2-badge ${ESTIMATE_TYPE_CLASSES[value] || 'svod2-badge-default'}`;
|
||||
|
||||
const GROUP_END_BORDER = '0.1875rem solid #9eafa3';
|
||||
|
||||
// Одно правило на контейнере вместо sx в каждой ячейке и заголовке.
|
||||
// Специфичность (класс контейнера + элемент) выше, чем у sx самой MRT-ячейки.
|
||||
export const SVOD2_TABLE_CONTAINER_SX = {
|
||||
flex: 1,
|
||||
overflow: 'auto',
|
||||
minHeight: 0,
|
||||
height: '100%',
|
||||
// order: -1 — thead вставлен порталом в конец <table> (grid), а должен идти до tbody.
|
||||
'& thead': { position: 'sticky', top: 0, zIndex: 10, order: -1 },
|
||||
'& td': {
|
||||
// Распорки виртуализации колонок (td с inline width) не должны сжиматься во flex-строке.
|
||||
flexShrink: 0,
|
||||
backgroundColor: '#ffffff',
|
||||
borderRight: '0.0625rem solid #edf0f2',
|
||||
fontSize: '0.8125rem',
|
||||
padding: '0.5rem 0.75rem',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: 1,
|
||||
},
|
||||
'& td.svod2-band-1': { backgroundColor: '#f8fbf9' },
|
||||
'& td.svod2-group-end': { borderRight: GROUP_END_BORDER },
|
||||
'& td[data-pinned="true"]': { zIndex: 2 },
|
||||
'& th': {
|
||||
flexShrink: 0,
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
boxShadow: 'none',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRight: '0.0625rem solid #e5e7eb',
|
||||
color: '#4a5565',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.2,
|
||||
padding: '0.5rem 0.75rem',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: 1,
|
||||
},
|
||||
'& th.svod2-band-1': { backgroundColor: '#f1f7f3' },
|
||||
'& th.svod2-group-end': { borderRight: GROUP_END_BORDER },
|
||||
'& 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': {
|
||||
display: 'inline-block',
|
||||
padding: '0.25rem 0.625rem',
|
||||
border: '0.0625rem solid',
|
||||
borderRadius: '0.5rem',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.35,
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
'& .svod2-badge-ahr': { backgroundColor: '#ecf9f0', borderColor: '#a7d9b8', color: '#08783e' },
|
||||
'& .svod2-badge-oper': { backgroundColor: '#eff6ff', borderColor: '#93c5fd', color: '#1d4ed8' },
|
||||
'& .svod2-badge-kv': { backgroundColor: '#fff7ed', borderColor: '#fdba74', color: '#c2410c' },
|
||||
'& .svod2-badge-default': { backgroundColor: '#f3f4f6', borderColor: '#d1d5db', color: '#4b5563' },
|
||||
'& .svod2-org-badge': {
|
||||
display: 'inline-block',
|
||||
maxWidth: '100%',
|
||||
padding: '0.25rem 0.625rem',
|
||||
border: '0.0625rem solid #a7d9b8',
|
||||
borderRadius: '0.5rem',
|
||||
backgroundColor: '#ecf9f0',
|
||||
color: '#08783e',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.35,
|
||||
textTransform: 'uppercase',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
};
|
||||
|
||||
// Виртуализация колонок требует grid-no-grow (без растяжения ячеек) и enableColumnPinning
|
||||
// (иначе MRT не учитывает закреплённые колонки в левом отступе виртуализатора).
|
||||
export const SVOD2_LAYOUT_OPTIONS = {
|
||||
layoutMode: 'grid-no-grow',
|
||||
enableColumnPinning: true,
|
||||
};
|
||||
80
web/src/pages/Svod2Page/svod2Styles.test.js
Normal file
80
web/src/pages/Svod2Page/svod2Styles.test.js
Normal file
@ -0,0 +1,80 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
SVOD2_LAYOUT_OPTIONS,
|
||||
SVOD2_TABLE_CONTAINER_SX,
|
||||
createColumnBandMap,
|
||||
createColumnClassMap,
|
||||
createColumnGroupEndSet,
|
||||
getEstimateTypeClassName,
|
||||
getSvod2CellClassName,
|
||||
} from './svod2Styles.js';
|
||||
|
||||
const columns = [
|
||||
{ id: 'a' },
|
||||
{ id: 'g1', columns: [{ id: 'g1.x' }, { id: 'g1.sub', columns: [{ id: 'g1.sub.y' }, { id: 'g1.sub.z' }] }] },
|
||||
{ id: 'b' },
|
||||
{ id: 'g2', columns: [{ id: 'g2.x' }, { id: 'g2.y' }] },
|
||||
];
|
||||
|
||||
test('bands alternate by top-level column and are inherited by children', () => {
|
||||
const bands = createColumnBandMap(columns);
|
||||
assert.equal(bands.get('a'), 0);
|
||||
assert.equal(bands.get('g1'), 1);
|
||||
assert.equal(bands.get('g1.sub.z'), 1);
|
||||
assert.equal(bands.get('b'), 0);
|
||||
assert.equal(bands.get('g2.y'), 1);
|
||||
});
|
||||
|
||||
test('group ends contain the group id and its last leaf only', () => {
|
||||
const ends = createColumnGroupEndSet(columns);
|
||||
assert.deepEqual([...ends].sort(), ['g1', 'g1.sub.z', 'g2', 'g2.y']);
|
||||
});
|
||||
|
||||
test('cell class name combines band and group-end', () => {
|
||||
const bands = createColumnBandMap(columns);
|
||||
const ends = createColumnGroupEndSet(columns);
|
||||
assert.equal(getSvod2CellClassName('a', bands, ends), undefined);
|
||||
assert.equal(getSvod2CellClassName('g1.x', bands, ends), 'svod2-band-1');
|
||||
assert.equal(getSvod2CellClassName('g1.sub.z', bands, ends), 'svod2-band-1 svod2-group-end');
|
||||
assert.equal(getSvod2CellClassName('unknown', bands, ends), undefined);
|
||||
});
|
||||
|
||||
test('class map is precomputed for every column id', () => {
|
||||
const map = createColumnClassMap(columns);
|
||||
assert.equal(map.size, 10);
|
||||
assert.equal(map.get('g2.y'), 'svod2-band-1 svod2-group-end');
|
||||
assert.equal(map.get('a'), undefined);
|
||||
});
|
||||
|
||||
test('estimate type class falls back to default', () => {
|
||||
assert.equal(getEstimateTypeClassName('АХР'), 'svod2-badge svod2-badge-ahr');
|
||||
assert.equal(getEstimateTypeClassName('Прочее'), 'svod2-badge svod2-badge-default');
|
||||
});
|
||||
|
||||
test('container styles define cell, band, group-end and pinned rules', () => {
|
||||
for (const key of [
|
||||
'& td',
|
||||
'& th',
|
||||
'& td.svod2-band-1',
|
||||
'& th.svod2-band-1',
|
||||
'& td.svod2-group-end',
|
||||
'& th.svod2-group-end',
|
||||
'& td[data-pinned="true"]',
|
||||
'& th[data-pinned="true"]',
|
||||
]) {
|
||||
assert.ok(SVOD2_TABLE_CONTAINER_SX[key], key);
|
||||
}
|
||||
});
|
||||
|
||||
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