import React from 'react'; import type {TableColumnSetupItem, TableColumnSetupProps} from '@gravity-ui/uikit'; import {useSetting} from './useSetting'; type OrderedColumn = {id: string; selected?: boolean}; function parseSavedColumn(saved: unknown): OrderedColumn | undefined { if (typeof saved === 'string') { return {id: saved, selected: true}; } if (saved && typeof saved === 'object' && 'id' in saved && typeof saved.id === 'string') { const selected = 'selected' in saved ? Boolean(saved.selected) : false; return {id: saved.id, selected}; } return undefined; } export function mergeColumnsPreservingHiddenPositions( visibleColumns: T[], allColumns: T[], hiddenColumnIds: readonly string[], ): T[] { const hiddenColumnIdSet = new Set(hiddenColumnIds); const visibleColumnIdSet = new Set(visibleColumns.map(({id}) => id)); const mergedColumns = [...visibleColumns]; let visibleColumnsBefore = 0; let insertedHiddenColumns = 0; for (const column of allColumns) { if (hiddenColumnIdSet.has(column.id)) { const insertionIndex = Math.min( visibleColumnsBefore + insertedHiddenColumns, mergedColumns.length, ); mergedColumns.splice(insertionIndex, 0, column); insertedHiddenColumns += 1; } else if (visibleColumnIdSet.has(column.id)) { visibleColumnsBefore += 1; } } return mergedColumns; } export const useSelectedColumns = ( columns: T[], storageKey: string, columnsTitles: Record, defaultColumnsIds: string[], requiredColumnsIds?: string[], stickyColumnsIds: string[] | undefined = requiredColumnsIds, ) => { const [savedColumns, setSavedColumns] = useSetting(storageKey, defaultColumnsIds); const normalizedSavedColumns = React.useMemo(() => { const rawValue = Array.isArray(savedColumns) ? savedColumns : defaultColumnsIds; return rawValue.map(parseSavedColumn).filter((c): c is OrderedColumn => c !== undefined); }, [defaultColumnsIds, savedColumns]); const orderedColumns = React.useMemo(() => { // Use saved columns order if user has customized it, otherwise use columns definition order const hasSavedOrder = savedColumns !== defaultColumnsIds && Array.isArray(savedColumns); if (hasSavedOrder) { // Preserve user's custom order from savedColumns const columnsMap = new Map(columns.map((col) => [col.name, col])); const result: OrderedColumn[] = []; const processedIds = new Set(); // First, add columns in saved order for (const savedCol of normalizedSavedColumns) { if (columnsMap.has(savedCol.id)) { result.push(savedCol); processedIds.add(savedCol.id); } } // Then, append any new columns that weren't in saved data (in columns definition order) for (const column of columns) { if (!processedIds.has(column.name)) { result.push({ id: column.name, selected: false, }); } } return result; } // No saved order - use columns definition order with saved selection state const savedColumnsMap = new Map(normalizedSavedColumns.map((col) => [col.id, col])); return columns.map((column) => { const savedCol = savedColumnsMap.get(column.name); return { id: column.name, selected: savedCol?.selected ?? false, }; }); }, [columns, normalizedSavedColumns, savedColumns, defaultColumnsIds]); const columnsToSelect = React.useMemo(() => { const preparedColumns = orderedColumns.reduce< (TableColumnSetupItem & {column: T; originalIndex: number})[] >((acc, {id, selected}, index) => { const isRequired = requiredColumnsIds?.includes(id); const isSticky = isRequired && stickyColumnsIds?.includes(id); const column = columns.find((c) => c.name === id); if (column) { acc.push({ id, title: columnsTitles[id], selected: Boolean(selected) || Boolean(isRequired), required: isRequired, sticky: isSticky ? 'start' : undefined, column, originalIndex: index, }); } return acc; }, []); // Sticky columns should be first to properly render columns settings. // Preserve the original order for all other columns, including non-sticky required ones. const sorted = preparedColumns.toSorted((a, b) => { const aSticky = Number(Boolean(a.sticky)); const bSticky = Number(Boolean(b.sticky)); if (aSticky !== bSticky) { return bSticky - aSticky; } return a.originalIndex - b.originalIndex; }); // Remove originalIndex from the public return value return sorted.map(({originalIndex: _, ...item}) => item); }, [columns, columnsTitles, requiredColumnsIds, stickyColumnsIds, orderedColumns]); const columnsToShow = React.useMemo(() => { return columnsToSelect.filter((c) => c.selected).map((c) => c.column); }, [columnsToSelect]); const setColumns: TableColumnSetupProps['onUpdate'] = React.useCallback( (value) => { const preparedColumns = value.map(({id, selected}) => ({id, selected})); setSavedColumns(preparedColumns); }, [setSavedColumns], ); // Get set of column IDs that are explicitly selected by user const selectedColumnIds = React.useMemo(() => { return new Set(normalizedSavedColumns.filter((col) => col.selected).map((col) => col.id)); }, [normalizedSavedColumns]); return { columnsToShow, columnsToSelect, setColumns, selectedColumnIds, }; };