Compare commits

..

2 Commits

Author SHA1 Message Date
905fa98ca5 Merge pull request '[AURORA-1148] - стили для таблицы' (#12) from AURORA-1148 into test
Reviewed-on: #12
Reviewed-by: PotapovaA <potapovaanna113@mail.ru>
2026-06-29 11:48:28 +03:00
f26691805a [AURORA-1148] - стили для таблицы 2026-06-29 11:10:14 +03:00
7 changed files with 288 additions and 40 deletions

View File

@ -59,13 +59,29 @@ const highlightText = (text, searchQuery) => {
); );
}; };
const INTEGER_COLUMN_IDS = new Set([
'data.header.num_group',
'data.nomenclature_group_id',
'data.header.item_id',
'data.article_id',
'data.code_razdela',
]);
// Функция для форматирования числа в финансовый формат // Функция для форматирования числа в финансовый формат
const formatNumber = (value) => { const formatNumber = (value, asInteger = false) => {
if (value === null || value === undefined || value === '') return String(value || ''); if (value === null || value === undefined || value === '') return String(value || '');
const num = Number(value); const num = Number(value);
if (isNaN(num)) return String(value); if (isNaN(num)) return String(value);
if (asInteger) {
return num.toLocaleString('ru-RU', {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
useGrouping: true,
});
}
return num.toLocaleString('ru-RU', { return num.toLocaleString('ru-RU', {
minimumFractionDigits: 1, minimumFractionDigits: 1,
maximumFractionDigits: 1, maximumFractionDigits: 1,
@ -84,7 +100,8 @@ const Cell = React.memo(({ cell, row, column, onClick, globalFilter, backgroundC
const isNumeric = !isNaN(Number(cellValue)) && cellValue !== null && cellValue !== undefined && cellValue !== ''; const isNumeric = !isNaN(Number(cellValue)) && cellValue !== null && cellValue !== undefined && cellValue !== '';
if (isNumeric) { if (isNumeric) {
textValue = formatNumber(cellValue); const asInteger = INTEGER_COLUMN_IDS.has(column.id);
textValue = formatNumber(cellValue, asInteger);
} }
const highlightedContent = useMemo(() => { const highlightedContent = useMemo(() => {

View File

@ -0,0 +1,148 @@
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
const toLocalCoord = (value, scale) => value / scale;
const getPinnedClipLeft = (container, overlayParent, columnPinning, scale) => {
const pinnedIds = (columnPinning?.left || []).filter(Boolean);
if (!pinnedIds.length) return 0;
const parentRect = overlayParent.getBoundingClientRect();
let maxRight = 0;
for (const id of pinnedIds) {
const el = container.querySelector(`[data-col-select-id="${id}"]`);
if (!el) continue;
const rect = el.getBoundingClientRect();
maxRight = Math.max(maxRight, rect.right - parentRect.left);
}
return toLocalCoord(maxRight, scale);
};
const getOverlayRect = (container, overlayParent, selectedColumnId, columnPinning, scale) => {
const columnEl = container.querySelector(
`[data-col-select-id="${selectedColumnId}"]`,
);
if (!columnEl || !overlayParent) return null;
const bodyCell = container.querySelector(
`td[data-col-id="${selectedColumnId}"]`,
);
const parentRect = overlayParent.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
const columnRect = columnEl.getBoundingClientRect();
const alignRect = bodyCell?.getBoundingClientRect() ?? columnRect;
let top = toLocalCoord(columnRect.top - parentRect.top, scale);
let left = toLocalCoord(alignRect.left - parentRect.left, scale);
let width = toLocalCoord(alignRect.width, scale);
const height = toLocalCoord(containerRect.bottom - columnRect.top, scale);
const pinnedIds = (columnPinning?.left || []).filter(Boolean);
const isSelectedPinned = pinnedIds.includes(selectedColumnId);
if (!isSelectedPinned && pinnedIds.length) {
const clipLeft = getPinnedClipLeft(container, overlayParent, columnPinning, scale);
const overlayRight = left + width;
if (overlayRight <= clipLeft) return null;
if (left < clipLeft) {
width = overlayRight - clipLeft;
left = clipLeft;
}
}
if (width <= 0 || height <= 0) return null;
return { top, left, width, height };
};
export const ColumnSelectionOverlay = ({
selectedColumnId,
containerRef,
scale = 1,
columnSizing,
columnPinning,
showColumnFilters,
}) => {
const [rect, setRect] = useState(null);
const [portalTarget, setPortalTarget] = useState(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const overlayParent = container.parentElement;
setPortalTarget(overlayParent);
}, [containerRef, selectedColumnId]);
useEffect(() => {
const container = containerRef.current;
const overlayParent = container?.parentElement;
if (!selectedColumnId || !container || !overlayParent) {
setRect(null);
return;
}
const updateRect = () => {
setRect(getOverlayRect(container, overlayParent, selectedColumnId, columnPinning, scale));
};
updateRect();
const scrollTargets = new Set([container]);
let scrollParent = container.parentElement;
for (let i = 0; i < 3 && scrollParent; i += 1) {
scrollTargets.add(scrollParent);
scrollParent = scrollParent.parentElement;
}
scrollTargets.forEach((target) => {
target.addEventListener('scroll', updateRect, { passive: true });
});
window.addEventListener('resize', updateRect);
const resizeObserver = new ResizeObserver(updateRect);
resizeObserver.observe(container);
resizeObserver.observe(overlayParent);
const header = container.querySelector('.custom-table-header');
if (header) {
resizeObserver.observe(header);
}
const tableBody = container.querySelector('.MuiTableBody-root');
if (tableBody) {
resizeObserver.observe(tableBody);
}
return () => {
scrollTargets.forEach((target) => {
target.removeEventListener('scroll', updateRect);
});
window.removeEventListener('resize', updateRect);
resizeObserver.disconnect();
};
}, [selectedColumnId, containerRef, scale, columnSizing, columnPinning, showColumnFilters]);
if (!rect || !portalTarget) return null;
return createPortal(
<div
style={{
position: 'absolute',
top: rect.top,
left: rect.left,
width: rect.width,
height: rect.height,
backgroundColor: 'rgba(88, 154, 47, 0.2)',
border: '1px solid #589A2F',
boxSizing: 'border-box',
pointerEvents: 'none',
zIndex: 1,
}}
/>,
portalTarget,
);
};

View File

@ -11,13 +11,14 @@ import { getTableColumns } from './tableColumns';
import { Cell, EditCell } from './index'; import { Cell, EditCell } from './index';
import { TableHead } from './TableHead/TableHead'; import { TableHead } from './TableHead/TableHead';
import SettingsPanel from './SettingPanel/SettingPanel'; import SettingsPanel from './SettingPanel/SettingPanel';
import { ColumnSelectionOverlay } from './ColumnSelectionOverlay/ColumnSelectionOverlay';
import { useColumnSettings } from './hooks/useColumnSettings'; import { useColumnSettings } from './hooks/useColumnSettings';
import { useTableScale } from './hooks/useTableScale'; import { useTableScale } from './hooks/useTableScale';
import { useHeaderPortal } from './hooks/useHeaderPortal'; import { useHeaderPortal } from './hooks/useHeaderPortal';
import { import {
BASE_TABLE_CONFIG, BASE_TABLE_CONFIG,
getTableHeadCellStyles, getTableBodyCellProps,
getTablePaperStyles, getTablePaperStyles,
TABLE_ROW_HEIGHT, TABLE_ROW_HEIGHT,
} from './constants/tableConfig'; } from './constants/tableConfig';
@ -29,7 +30,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction); const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction);
const [globalFilter, setGlobalFilter] = useState(''); const [globalFilter, setGlobalFilter] = useState('');
const [showColumnFilters, setShowColumnFilters] = useState(false); const [showColumnFilters, setShowColumnFilters] = useState(false);
const [selectedColumn, setSelectedColumn] = useState(); const [selectedColumnId, setSelectedColumnId] = useState();
const [rowSelection, setRowSelection] = useState({}); const [rowSelection, setRowSelection] = useState({});
const [isLoadingData, setIsLoadingData] = useState(false); const [isLoadingData, setIsLoadingData] = useState(false);
const [isPending, startTransition] = useTransition(); const [isPending, startTransition] = useTransition();
@ -102,6 +103,26 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
})); }));
}, []) }, [])
const handleColumnSelect = useCallback((columnId) => {
setSelectedColumnId((prev) => {
const next = prev === columnId ? undefined : columnId;
return next;
});
}, []);
useEffect(() => {
if (!selectedColumnId) return;
const handleDocumentClick = (e) => {
if (e.target.closest('[data-col-select-id]')) return;
if (e.target.closest('[data-pin-panel]')) return;
setSelectedColumnId(undefined);
};
document.addEventListener('mousedown', handleDocumentClick);
return () => document.removeEventListener('mousedown', handleDocumentClick);
}, [selectedColumnId]);
const columns = useMemo(() => { const columns = useMemo(() => {
if (!columnsConfig || !columnsConfig.columns) return []; if (!columnsConfig || !columnsConfig.columns) return [];
@ -121,11 +142,12 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
console.error('Error creating columns:', error); console.error('Error creating columns:', error);
return []; return [];
} }
}, [columnsConfig, handleUpdateCell]); }, [columnsConfig, handleUpdateCell, handleClickRowCell]);
const tableMeta = useMemo(() => ({ const tableMeta = useMemo(() => ({
updateCell: handleUpdateCell, updateCell: handleUpdateCell,
}), [handleUpdateCell]); selectedColumnId,
}), [handleUpdateCell, selectedColumnId]);
// Конфигурация таблицы // Конфигурация таблицы
const tableConfig = useMemo( const tableConfig = useMemo(
@ -188,7 +210,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
muiTableHeadCellProps: { muiTableHeadCellProps: {
sx: { boxSizing: 'border-box' }, sx: { boxSizing: 'border-box' },
}, },
muiTableBodyCellProps: getTableHeadCellStyles, muiTableBodyCellProps: getTableBodyCellProps,
muiTableHeadProps: { muiTableHeadProps: {
sx: { sx: {
display: 'table-header-group', display: 'table-header-group',
@ -301,7 +323,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
return ( return (
<> <>
<SettingsPanel <SettingsPanel
selectedColumn={selectedColumn} selectedColumnId={selectedColumnId}
columnPinning={columnPinning}
table={table} table={table}
columns={columns} columns={columns}
onPinColumn={handlePinColumn} onPinColumn={handlePinColumn}
@ -333,6 +356,15 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
> >
<MaterialReactTable table={table} /> <MaterialReactTable table={table} />
<ColumnSelectionOverlay
selectedColumnId={selectedColumnId}
containerRef={containerRef}
scale={sizeMult}
columnSizing={columnSizing}
columnPinning={columnPinning}
showColumnFilters={showColumnFilters}
/>
{isTableLoading && ( {isTableLoading && (
<div <div
style={{ style={{
@ -354,8 +386,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
<TableHead <TableHead
table={table} table={table}
style={{ zIndex: (theme) => theme.zIndex.modal - 1 }} style={{ zIndex: (theme) => theme.zIndex.modal - 1 }}
onHeaderSelect={setSelectedColumn} selectedColumnId={selectedColumnId}
selectHeader={selectedColumn} onColumnSelect={handleColumnSelect}
onChangeWidth={handleSetColumnWidth} onChangeWidth={handleSetColumnWidth}
isLoadingData={isLoadingData} isLoadingData={isLoadingData}
/>, />,

View File

@ -21,7 +21,8 @@ import { ExportDefaultButton } from '../../common/Buttons/ButtonsActions';
import { exportSheet } from '../../../utils/exportForm'; import { exportSheet } from '../../../utils/exportForm';
const SettingPanel = ({ const SettingPanel = ({
selectedColumn, selectedColumnId,
columnPinning,
table, table,
columns, columns,
onPinColumn, onPinColumn,
@ -44,12 +45,16 @@ const SettingPanel = ({
const [selectedColumnIds, setSelectedColumnIds] = useState(); const [selectedColumnIds, setSelectedColumnIds] = useState();
const [showColumnFilters, setShowColumnFilters] = useState(false); const [showColumnFilters, setShowColumnFilters] = useState(false);
// TODO: console.log(!!!) везде посомтреть
useEffect(() => { useEffect(() => {
if (!selectedColumn) return; if (!selectedColumnId) {
const pinningColumn = table.getState().columnPinning.left; setIsPinned(false);
const isPinned = pinningColumn.includes(selectedColumn.id); return;
setIsPinned(isPinned); }
}, [selectedColumn, table]); const pinningColumn = columnPinning?.left || table.getState().columnPinning.left || [];
const pinned = pinningColumn.includes(selectedColumnId);
setIsPinned(pinned);
}, [selectedColumnId, columnPinning, table]);
useEffect(() => { useEffect(() => {
setSelectedColumnIds( setSelectedColumnIds(
@ -79,7 +84,7 @@ const SettingPanel = ({
return ( return (
<> <>
<Panel> <Panel data-pin-panel>
<Stack direction="row" sx={{ gap: '1.25rem' }}> <Stack direction="row" sx={{ gap: '1.25rem' }}>
<GroupByObject title="Отображение"> <GroupByObject title="Отображение">
{/* <ColorPickerButton onColorApply={addColorForCells} /> */} {/* <ColorPickerButton onColorApply={addColorForCells} /> */}
@ -88,11 +93,22 @@ const SettingPanel = ({
<IconButton <IconButton
variant="outlined" variant="outlined"
data-active={isPinned} data-active={isPinned}
onClick={() => disabled={!selectedColumnId}
onMouseDown={(e) => e.stopPropagation()}
onClick={() => {
if (!selectedColumnId) {
console.warn('[ColumnPin] клик Pin: selectedColumnId пустой');
return;
}
console.log('[ColumnPin] клик Pin:', {
selectedColumnId,
isPinned,
action: isPinned ? 'unpin' : 'pin',
});
isPinned isPinned
? onUnpinColumn?.(selectedColumn.id) ? onUnpinColumn?.(selectedColumnId)
: onPinColumn?.(selectedColumn.id) : onPinColumn?.(selectedColumnId);
} }}
> >
<Pin /> <Pin />
</IconButton> </IconButton>

View File

@ -2,9 +2,12 @@ import React from 'react';
import ColumnResizer from './ColumnResizer'; import ColumnResizer from './ColumnResizer';
import { columnPinningDefault } from '../constants/columnConfig'; import { columnPinningDefault } from '../constants/columnConfig';
const getPinnedColumnIds = (table) =>
(table.getState().columnPinning.left || []).filter(Boolean);
const getSortedColumns = (table) => { const getSortedColumns = (table) => {
const allLeafColumns = table.getAllLeafColumns(); const allLeafColumns = table.getAllLeafColumns();
const pinnedColumns = table.getState().columnPinning.left || []; const pinnedColumns = getPinnedColumnIds(table);
return [...allLeafColumns].sort((a, b) => { return [...allLeafColumns].sort((a, b) => {
const aIsPinned = pinnedColumns.includes(a.id); const aIsPinned = pinnedColumns.includes(a.id);
@ -46,7 +49,7 @@ const getPinnedColumnsWidth = (header, pinningColumn) => {
const calculateLeftOffset = (table, columnOrHeader) => { const calculateLeftOffset = (table, columnOrHeader) => {
const columnId = getColumnId(columnOrHeader); const columnId = getColumnId(columnOrHeader);
const pinnedColumns = table.getState().columnPinning.left || []; const pinnedColumns = getPinnedColumnIds(table);
const currentPinnedIndex = pinnedColumns.indexOf(columnId); const currentPinnedIndex = pinnedColumns.indexOf(columnId);
let leftOffset = 0; let leftOffset = 0;
@ -64,13 +67,13 @@ const calculateLeftOffset = (table, columnOrHeader) => {
}; };
const isColumnPin = (table, header) => { const isColumnPin = (table, header) => {
const pinningColumn = table.getState().columnPinning.left; const pinningColumn = getPinnedColumnIds(table);
const columnId = getColumnId(header); const columnId = getColumnId(header);
const isPinned = pinningColumn.includes(columnId); const isPinned = pinningColumn.includes(columnId);
return isPinned; return isPinned;
}; };
const ColumnNumbersRow = ({ table }) => { const ColumnNumbersRow = ({ table, selectedColumnId, onColumnSelect }) => {
const sortedColumns = getSortedColumns(table); const sortedColumns = getSortedColumns(table);
const tableState = table.getState(); const tableState = table.getState();
const filteredColumns = sortedColumns.filter( const filteredColumns = sortedColumns.filter(
@ -95,27 +98,38 @@ const ColumnNumbersRow = ({ table }) => {
key={`col-number-${column.id}`} key={`col-number-${column.id}`}
column={column} column={column}
table={table} table={table}
selectedColumnId={selectedColumnId}
onColumnSelect={onColumnSelect}
/> />
))} ))}
</div> </div>
); );
}; };
const ColumnNumberCell = ({ column, table }) => { const ColumnNumberCell = ({ column, table, selectedColumnId, onColumnSelect }) => {
const isPinned = isColumnPin(table, column); const isPinned = isColumnPin(table, column);
const allLeafColumns = table.getAllLeafColumns(); const allLeafColumns = table.getAllLeafColumns();
const originalIndex = allLeafColumns.findIndex((col) => col.id === column.id); const originalIndex = allLeafColumns.findIndex((col) => col.id === column.id);
const leftOffset = calculateLeftOffset(table, column); const leftOffset = calculateLeftOffset(table, column);
const selected = selectedColumnId === column.id;
return ( return (
<div <div
key={`col-number-${column.id}`} key={`col-number-${column.id}`}
data-col-select-id={column.id}
onClick={(e) => {
e.stopPropagation();
onColumnSelect?.(column.id);
}}
onMouseDown={(e) => e.stopPropagation()}
style={{ style={{
width: column.getSize(), width: column.getSize(),
minWidth: column.getSize(), minWidth: column.getSize(),
maxWidth: column.getSize(), maxWidth: column.getSize(),
padding: '2px 6px', padding: '2px 6px',
textAlign: 'center', textAlign: 'center',
backgroundColor: '#e5e5e5', backgroundColor: selected ? '#589A2F' : '#FBFBFB',
color: selected ? '#ffffff' : '#5E5E5E',
boxSizing: 'border-box', boxSizing: 'border-box',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@ -125,7 +139,9 @@ const ColumnNumberCell = ({ column, table }) => {
borderBottom: 'none', borderBottom: 'none',
position: isPinned ? 'sticky' : 'relative', position: isPinned ? 'sticky' : 'relative',
left: isPinned ? leftOffset : 'auto', left: isPinned ? leftOffset : 'auto',
zIndex: isPinned ? 20 : 'auto', zIndex: selected ? 16 : isPinned ? 20 : 'auto',
cursor: 'pointer',
userSelect: 'none',
}} }}
> >
{originalIndex} {originalIndex}
@ -133,9 +149,9 @@ const ColumnNumberCell = ({ column, table }) => {
); );
}; };
const HeaderCell = ({ header, table, onClick, hightlight, onChangeWidth }) => { const HeaderCell = ({ header, table, onClick, onChangeWidth }) => {
const column = header.column; const column = header.column;
const pinningColumn = table.getState().columnPinning.left || []; const pinningColumn = getPinnedColumnIds(table);
const isGroup = header.subHeaders?.length > 0; const isGroup = header.subHeaders?.length > 0;
const isPinned = isColumnPin(table, header); const isPinned = isColumnPin(table, header);
const leftOffset = calculateLeftOffset(table, header); const leftOffset = calculateLeftOffset(table, header);
@ -148,7 +164,7 @@ const HeaderCell = ({ header, table, onClick, hightlight, onChangeWidth }) => {
const backgroundColor = customBgColor const backgroundColor = customBgColor
? customBgColor ? customBgColor
: (hightlight ? '#cbccce' : 'rgba(243, 244, 246, 1)'); : 'rgba(243, 244, 246, 1)';
const handleChangeWidth = (deltaWidth) => { const handleChangeWidth = (deltaWidth) => {
const size = header.getSize(); const size = header.getSize();
@ -164,7 +180,7 @@ const HeaderCell = ({ header, table, onClick, hightlight, onChangeWidth }) => {
} }
// Для незакрепленных колонок текст должен начинаться после всех закрепленных // Для незакрепленных колонок текст должен начинаться после всех закрепленных
// Получаем общую ширину всех закрепленных колонок // Получаем общую ширину всех закрепленных колонок
const pinnedColumns = table.getState().columnPinning.left || []; const pinnedColumns = getPinnedColumnIds(table);
let totalPinnedWidth = 0; let totalPinnedWidth = 0;
for (const pinnedId of pinnedColumns) { for (const pinnedId of pinnedColumns) {
const pinnedCol = table.getColumn(pinnedId); const pinnedCol = table.getColumn(pinnedId);
@ -188,6 +204,7 @@ const HeaderCell = ({ header, table, onClick, hightlight, onChangeWidth }) => {
textAlign: 'center', textAlign: 'center',
fontWeight: 600, fontWeight: 600,
fontSize: '12px', fontSize: '12px',
lineHeight: 1,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
color: '#424242', color: '#424242',
boxSizing: 'border-box', boxSizing: 'border-box',
@ -379,13 +396,15 @@ const FilterCell = ({ column, table }) => {
export const TableHead = ({ export const TableHead = ({
table, table,
onHeaderSelect, selectedColumnId,
selectHeader, onColumnSelect,
onChangeWidth, onChangeWidth,
isLoadingData, isLoadingData,
}) => { }) => {
const handleHeaderClick = (header) => { const handleHeaderClick = (header) => {
onHeaderSelect(header); if (!header.subHeaders?.length && header.column?.id) {
onColumnSelect?.(header.column.id);
}
}; };
if (!isLoadingData) { if (!isLoadingData) {
@ -402,9 +421,15 @@ export const TableHead = ({
}} }}
> >
{/* Строка с номерами колонок */} {/* Строка с номерами колонок */}
<ColumnNumbersRow table={table} /> <ColumnNumbersRow
table={table}
selectedColumnId={selectedColumnId}
onColumnSelect={onColumnSelect}
/>
{table.getState().showColumnFilters && <FilterRow table={table} />} {table.getState().showColumnFilters && (
<FilterRow table={table} />
)}
{/* Существующие строки заголовков */} {/* Существующие строки заголовков */}
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
@ -413,9 +438,6 @@ export const TableHead = ({
<React.Fragment key={header.id}> <React.Fragment key={header.id}>
<HeaderCell <HeaderCell
header={header} header={header}
hightlight={
selectHeader ? header.id == selectHeader.id : false
}
table={table} table={table}
onClick={handleHeaderClick} onClick={handleHeaderClick}
onChangeWidth={onChangeWidth} onChangeWidth={onChangeWidth}

View File

@ -45,6 +45,18 @@ export const getTableHeadCellStyles = () => ({
}, },
}); });
export const getTableBodyCellProps = ({ column, table }) => {
const isPinned = (table.getState().columnPinning?.left || []).includes(column.id);
return {
'data-col-id': column.id,
sx: {
...getTableHeadCellStyles().sx,
...(isPinned && { zIndex: 5 }),
},
};
};
export const getTablePaperStyles = () => ({ export const getTablePaperStyles = () => ({
sx: { sx: {
boxShadow: "none", boxShadow: "none",

View File

@ -41,6 +41,7 @@ export const useColumnSettings = (tableKey) => {
}, []); }, []);
const handleUnpinColumn = useCallback((colId) => { const handleUnpinColumn = useCallback((colId) => {
if (!colId) return;
if (columnPinningDefault.left.includes(colId)) return; if (columnPinningDefault.left.includes(colId)) return;
setColumnPinning((prev) => ({ setColumnPinning((prev) => ({
...prev, ...prev,