386 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
import ColumnResizer from './ColumnResizer';
const getSortedColumns = (table) => {
const allLeafColumns = table.getAllLeafColumns();
const pinnedColumns = table.getState().columnPinning.left || [];
return [...allLeafColumns].sort((a, b) => {
const aIsPinned = pinnedColumns.includes(a.id);
const bIsPinned = pinnedColumns.includes(b.id);
if (aIsPinned && bIsPinned) {
return pinnedColumns.indexOf(a.id) - pinnedColumns.indexOf(b.id);
}
if (aIsPinned) return -1;
if (bIsPinned) return 1;
return 0;
});
};
const calculateLeftOffset = (table, columnOrHeader) => {
const columnId =
columnOrHeader.id.match(/data\..*$/)?.[0] || columnOrHeader.id;
const pinnedColumns = table.getState().columnPinning.left || [];
const currentPinnedIndex = pinnedColumns.indexOf(columnId);
let leftOffset = 0;
if (currentPinnedIndex > 0) {
for (let i = 0; i < currentPinnedIndex; i++) {
const prevColumn = table.getColumn(pinnedColumns[i]);
if (prevColumn) {
leftOffset += prevColumn.getSize();
}
}
}
return leftOffset;
};
const isColumnPin = (table, header) => {
const pinningColumn = table.getState().columnPinning.left;
//в header.id содержаться колонки по типу depth_idColParent_idChild, а в pinningColumn только idCol, которые закреплены
//header.id.split('_').at(-1) - получение самой нижней колонки
const columnId = header.id.match(/data\..*$/)?.[0] || header.id;
const isPinned = pinningColumn.includes(columnId);
return isPinned;
};
const ColumnNumbersRow = ({ table }) => {
const sortedColumns = getSortedColumns(table);
const tableState = table.getState();
const filteredColumns = sortedColumns.filter(
(col) =>
!(col.id in tableState.columnVisibility) ||
(col.id in tableState.columnVisibility &&
tableState.columnVisibility[col.id]),
);
return (
<div
style={{
display: 'flex',
position: 'sticky',
top: 0,
zIndex: (theme) => theme.zIndex.table + 1,
backgroundColor: 'white',
}}
>
{filteredColumns.map((column) => (
<ColumnNumberCell
key={`col-number-${column.id}`}
column={column}
table={table}
/>
))}
</div>
);
};
const ColumnNumberCell = ({ column, table }) => {
const isPinned = isColumnPin(table, column);
const allLeafColumns = table.getAllLeafColumns();
const originalIndex = allLeafColumns.findIndex((col) => col.id === column.id);
const leftOffset = calculateLeftOffset(table, column);
return (
<div
key={`col-number-${column.id}`}
style={{
width: column.getSize(),
minWidth: column.getSize(),
maxWidth: column.getSize(),
padding: '2px 6px',
textAlign: 'center',
backgroundColor: isPinned ? '#f5f5f5' : '#F3F4F6',
boxSizing: 'border-box',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '1.5rem',
border: '.063rem solid rgba(209, 213, 220, 1)',
borderBottom: 'none',
position: isPinned ? 'sticky' : 'relative',
left: isPinned ? leftOffset : 'auto',
zIndex: isPinned ? 20 : 'auto',
}}
>
{originalIndex}
</div>
);
};
const HeaderCell = ({ header, table, onClick, hightlight, onChangeWidth }) => {
const column = header.column;
const isPinned = isColumnPin(table, header);
const leftOffset = calculateLeftOffset(table, header);
const customBgColor = column.columnDef?.muiTableHeadCellProps?.sx?.backgroundColor;
const color = column.columnDef?.muiTableHeadCellProps?.sx?.color || '#000000';
const backgroundColor = customBgColor
? customBgColor
: (hightlight ? '#cbccce' : 'rgba(243, 244, 246, 1)');
const handleChangeWidth = (deltaWidth) => {
const size = header.getSize();
onChangeWidth(header, size + deltaWidth);
};
// Вычисляем отступ для текста внутри ячейки
// Если колонка закреплена, текст должен быть привязан к левому краю ячейки
// Если колонка не закреплена, текст должен начинаться после всех закрепленных колонок
const getTextLeftOffset = () => {
if (isPinned) {
return '0';
}
// Для незакрепленных колонок текст должен начинаться после всех закрепленных
// Получаем общую ширину всех закрепленных колонок
const pinnedColumns = table.getState().columnPinning.left || [];
let totalPinnedWidth = 0;
for (const pinnedId of pinnedColumns) {
const pinnedCol = table.getColumn(pinnedId);
if (pinnedCol) {
totalPinnedWidth += pinnedCol.getSize();
}
}
return `${totalPinnedWidth}px`;
};
return (
<div
style={{
position: isPinned ? 'sticky' : 'relative',
left: isPinned ? leftOffset : 'auto',
zIndex: isPinned ? 21 : 'auto',
}}
>
<div style={{ position: 'relative' }}>
<div
key={header.id}
onClick={() => onClick(header)}
style={{
width: header.getSize() + 'px',
minWidth: header.getSize() + 'px',
maxWidth: header.getSize() + 'px',
position: 'relative',
padding: '4px 6px',
textAlign: 'center',
fontWeight: 600,
fontSize: '12px',
backgroundColor: backgroundColor,
color: '#424242',
boxSizing: 'border-box',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '3rem',
border: '.063rem solid rgba(209, 213, 220, 1)',
}}
>
<div
style={{
textAlign: 'center',
position: 'sticky',
left: getTextLeftOffset(),
right: 0,
paddingRight: '0.5rem',
paddingLeft: '0.5rem',
backgroundColor: backgroundColor,
color: color,
}}
>
{header.isPlaceholder ? null : column.columnDef.header}
</div>
<ColumnResizer reportDragDeltaX={handleChangeWidth} />
</div>
</div>
</div>
);
};
const FilterRow = ({ table }) => {
const sortedColumns = getSortedColumns(table);
const tableState = table.getState();
const filteredColumns = sortedColumns.filter(
(col) =>
!(col.id in tableState.columnVisibility) ||
(col.id in tableState.columnVisibility &&
tableState.columnVisibility[col.id]),
);
return (
<div
style={{
display: 'flex',
position: 'sticky',
top: '1.5rem',
zIndex: 20,
backgroundColor: 'white',
}}
>
{filteredColumns.map((column) => (
<FilterCell key={`filter-${column.id}`} column={column} table={table} />
))}
</div>
);
};
// Компонент ячейки фильтра
const FilterCell = ({ column, table }) => {
const isPinned = isColumnPin(table, column);
const leftOffset = calculateLeftOffset(table, column);
// Определяем тип фильтра на основе колонки
const filterVariant = column.columnDef.filterVariant || 'text';
// Для select фильтра
const selectOptions = column.columnDef.filterSelectOptions || [];
const handleFilterChange = (value) => {
column.setFilterValue(value);
};
// Рендер разных типов фильтров
const renderFilterInput = () => {
switch (filterVariant) {
case 'select':
return (
<select
value={column.getFilterValue() ?? ''}
onChange={(e) => handleFilterChange(e.target.value)}
style={{
width: '100%',
padding: '4px 6px',
fontSize: '11px',
border: '1px solid #ddd',
borderRadius: '4px',
backgroundColor: 'white',
}}
>
<option value="">Все</option>
{selectOptions.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
);
case 'range': {
// Для диапазона можно реализовать два поля
const [min, max] = column.getFilterValue() || ['', ''];
return (
<div style={{ display: 'flex', gap: '4px' }}>
<input
type="number"
placeholder="От"
value={min}
onChange={(e) => column.setFilterValue([e.target.value, max])}
style={{ width: '50%', padding: '4px', fontSize: '11px' }}
/>
<input
type="number"
placeholder="До"
value={max}
onChange={(e) => column.setFilterValue([min, e.target.value])}
style={{ width: '50%', padding: '4px', fontSize: '11px' }}
/>
</div>
);
}
default: // 'text'
return (
<input
type="text"
value={column.getFilterValue() ?? ''}
onChange={(e) => handleFilterChange(e.target.value)}
placeholder="Поиск..."
style={{
width: '100%',
padding: '4px 6px',
fontSize: '11px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box',
}}
/>
);
}
};
return (
<div
style={{
width: column.getSize(),
minWidth: column.getSize(),
maxWidth: column.getSize(),
padding: '4px 6px',
backgroundColor: isPinned ? '#f5f5f5' : 'white',
boxSizing: 'border-box',
height: '2.5rem',
border: '.063rem solid rgba(209, 213, 220, 1)',
borderTop: 'none',
position: isPinned ? 'sticky' : 'relative',
left: isPinned ? leftOffset : 'auto',
zIndex: isPinned ? 19 : 'auto',
}}
>
{renderFilterInput()}
</div>
);
};
export const TableHead = ({
table,
onHeaderSelect,
selectHeader,
onChangeWidth,
isLoadingData,
}) => {
const handleHeaderClick = (header) => {
onHeaderSelect(header);
};
if (!isLoadingData) {
return <div></div>
}
return (
<>
<div
style={{
zIndex: (theme) => theme.zIndex.table,
position: 'relative',
width: 'max-content',
}}
>
{/* Строка с номерами колонок */}
<ColumnNumbersRow table={table} />
{table.getState().showColumnFilters && <FilterRow table={table} />}
{/* Существующие строки заголовков */}
{table.getHeaderGroups().map((headerGroup) => (
<div key={headerGroup.id} style={{ display: 'flex' }}>
{headerGroup.headers.map((header) => (
<React.Fragment key={header.id}>
<HeaderCell
header={header}
hightlight={
selectHeader ? header.id == selectHeader.id : false
}
table={table}
onClick={handleHeaderClick}
onChangeWidth={onChangeWidth}
/>
</React.Fragment>
))}
</div>
))}
</div>
</>
);
};