import { action, computed, makeObservable, observable, reaction } from 'mobx'; import { addEventListener, CollectionState, FiltersMap, Sort, SortOrder, withoutDefaults, WixPatternsContainer, } from '@wix/bex-core'; import { ToolbarCollectionState } from './ToolbarCollectionState'; import { MultiLevelSortingStateDragAndDrop } from './MultiLevelSortingDragAndDrop'; import { Column } from '../model'; import type { ReactNode } from 'react'; import { cairoFiltersPanelUsed } from '@wix/bex-core/bi'; import { v4 as uuid } from 'uuid'; export interface SortableColumn { key: string; columnId: string; name: ReactNode; direction: SortOrder; } export interface MultiLevelSortingStateParams { readonly table: ToolbarCollectionState; readonly container: WixPatternsContainer; readonly modalsContainerRef: { current: HTMLElement | null | undefined; }; } export class MultiLevelSortingState { table: ToolbarCollectionState; container; sortingCollection; dragAndDrop; orderedSortableColumns = observable.array(); touchedColumnId = ''; lastManuallyAddedColumnId: string | null = null; constructor(props: MultiLevelSortingStateParams) { this.table = props.table; this.container = props.container; this.sortingCollection = new CollectionState({ ...this.container, events: {}, queryName: [ this.container.environment.componentId, this.table.collection.queryName, 'multiLevelSorting', ] .filter(Boolean) .join('/'), fetchData: async () => { return { items: this.orderedSortableColumns, }; }, filters: {}, itemKey: (item) => item.key, itemName: (item) => String(item.name), }); this.dragAndDrop = new MultiLevelSortingStateDragAndDrop({ multiLevelSortingState: this, modalsContainerRef: props.modalsContainerRef, }); makeObservable(this, { hasAvailableSortingColumns: computed, sortListSize: computed, sortableColumnsMap: computed, sortableColumns: computed, availableColumns: computed, addSort: action, removeSort: action, changeSortOrder: action, replaceSort: action, }); } get sortableColumns() { return this.table.columns.filter((column) => column.sortable); } get sortableColumnsMap() { return new Map(this.sortableColumns.map((column) => [column.id, column])); } get hasAvailableSortingColumns() { return this.sortableColumns.length - this.orderedSortableColumns.length > 0; } get sortListSize() { return this.orderedSortableColumns.length; } get availableColumns() { const { orderedSortableColumns, sortableColumns } = this; return new Set( sortableColumns .filter((column) => orderedSortableColumns.every( (orderedSortableColumn) => orderedSortableColumn.columnId !== column.id, ), ) .map((sortableColumn) => sortableColumn.id), ); } get currentSortOrder() { return this.orderedSortableColumns.map(({ columnId, direction }) => ({ fieldName: columnId, order: direction, })); } get reportBI() { return this.table.reportBi; } private getNextAvailableSortableColumn() { for (const column of this.sortableColumns) { if (this.availableColumns.has(column.id)) { return column; } } return null; } private toSortQuery(): Sort[] { return this.orderedSortableColumns.map(({ columnId, direction }) => ({ field: columnId, direction, })); } private isDifferentFromSortState() { const { table: { collection: { query: { sort: { value: sortStateValue }, }, }, }, orderedSortableColumns, } = this; if (sortStateValue.length !== orderedSortableColumns.length) { return true; } for (let i = 0; i < orderedSortableColumns.length; i++) { if ( sortStateValue[i].direction !== orderedSortableColumns[i].direction || sortStateValue[i].field !== orderedSortableColumns[i].columnId ) { return true; } } return false; } getAvailableOptions(columnId: string) { return this.sortableColumns .filter((column) => { return column.id === columnId || this.availableColumns.has(column.id); }) .map((column) => ({ id: column.id, value: (typeof column.title === 'string' && column.title) || column.name || column.id, })); } addSort({ column, order, manual = false, }: { column?: Column; order?: SortOrder; manual?: boolean } = {}) { const nextColumn = column ?? this.getNextAvailableSortableColumn(); if (nextColumn) { const sortableColumn = { key: uuid(), columnId: nextColumn.id, name: nextColumn.title || nextColumn.name || nextColumn.id, direction: order ?? 'asc', }; this.touchedColumnId = sortableColumn.columnId; this.orderedSortableColumns.push(sortableColumn); if (manual) { this.lastManuallyAddedColumnId = sortableColumn.columnId; } } } removeSort(columnId: string) { const index = this.orderedSortableColumns.findIndex( (sortableColumn) => sortableColumn.columnId === columnId, ); if (index !== -1) { this.touchedColumnId = columnId; this.orderedSortableColumns.splice(index, 1); } } changeSortOrder(sortableColumnId: string, direction: SortOrder) { const { orderedSortableColumns } = this; const index = orderedSortableColumns.findIndex( ({ columnId }) => columnId === sortableColumnId, ); if (index !== -1) { this.touchedColumnId = sortableColumnId; orderedSortableColumns[index].direction = direction; } } replaceSort(oldColumnId: string, newColumnId: string) { const { orderedSortableColumns } = this; const index = orderedSortableColumns.findIndex( ({ columnId }) => oldColumnId === columnId, ); if (index !== -1) { this.touchedColumnId = newColumnId; orderedSortableColumns[index].columnId = newColumnId; orderedSortableColumns[index].name = this.sortableColumnsMap.get(newColumnId)!.name; } } onSortStateChange = action(() => { const { table: { collection: { query }, }, } = this; this.orderedSortableColumns.clear(); for (const sort of query.sort.value) { const column = this.sortableColumnsMap.get(sort.field); if (column) { this.addSort({ column, order: sort.direction, }); } } }); sort() { const { collection } = this.table; const { query, emitter } = collection; emitter.emit('beforeSortStart', { origin: 'SidePanel', }); query.sort.setSortQuery(this.toSortQuery()); emitter.emit('sortStart', { col: { id: this.touchedColumnId }, }); collection.scheduleSort({ clearResult: true }); } reportSidePanelOpen() { this.reportBI( withoutDefaults(cairoFiltersPanelUsed)({ currentSortOrder: JSON.stringify(this.currentSortOrder), numSortedColumns: this.sortListSize, isOpened: true, feature: 'Sort', origin: 'Sort CTA', }), ); } reportSidePanelClose({ origin = 'X Button', }: { origin?: 'X Button' | 'Sort CTA' } = {}) { this.reportBI( withoutDefaults(cairoFiltersPanelUsed)({ currentSortOrder: JSON.stringify(this.currentSortOrder), numSortedColumns: this.sortListSize, isOpened: false, feature: 'Sort', origin, }), ); } init() { const { table: { collection: { query }, }, sortingCollection, } = this; this.onSortStateChange(); const disposers = [ reaction( () => this.orderedSortableColumns.map((item) => ({ ...item })), () => { if (!this.isDifferentFromSortState()) { return; } sortingCollection.refreshAllPages(); this.sort(); }, ), addEventListener(query.sort.events, 'change', this.onSortStateChange), this.sortingCollection.init(), this.dragAndDrop.init(), ]; return () => { disposers.forEach((disposer) => disposer?.()); }; } }