import { computed, makeObservable, observable } from 'mobx'; import { FiltersMap, KeyedItem } from '@wix/bex-core'; import { TableState } from '../../state'; import { GroupBy, RenderSection, Section } from '../CollectionSectionHeader'; const SECTION_PREFIX = '$$$wix-patterns-sections$$$-'; const INVISIBLE_ROW_ID = `${SECTION_PREFIX}invisible`; export class TableSectionsState { table: TableState; groupBy: GroupBy; renderSection: RenderSection; collapsible: boolean; onToggleCallback?: (sectionId: string, isCollapsed: boolean) => void; constructor( table: TableState, groupBy: GroupBy, renderSection: RenderSection, collapsible?: boolean, ) { this.table = table; this.groupBy = groupBy; this.renderSection = renderSection; this.collapsible = collapsible ?? false; makeObservable(this, { renderSection: observable.ref, sectionsKeyedItems: computed, sectionCounts: computed, }); } toggleSection = (sectionId: string) => { const currentCollapsed = this.isSectionCollapsed(sectionId); this.onToggleCallback?.(sectionId, !currentCollapsed); }; private isSectionCollapsed(sectionId: string): boolean { const sectionItems = this.getSectionItems(sectionId); const section = this.renderSection(sectionId, sectionItems); return section.collapsed ?? false; } private getSectionItems(sectionId: string): T[] { const items: T[] = []; for (const keyedItem of this.table.keyedItems) { if (this.groupBy(keyedItem.item) === sectionId) { items.push(keyedItem.item); } } return items; } private generateSectionId(firstItemId: string): string { return `${SECTION_PREFIX}${firstItemId}`; } private get computeSectionData(): { counts: Map; items: Map; indices: Map; keyedItems: KeyedItem[]; } { const counts = new Map(); const items = new Map(); const indices = new Map(); const keyedItems: KeyedItem[] = []; if (this.table.keyedItems.length === 0) { return { counts, items, indices, keyedItems }; } // Add invisible row for design purposes keyedItems.push({ id: INVISIBLE_ROW_ID, key: INVISIBLE_ROW_ID, item: this.table.keyedItems[0].item, index: 0, pageIndex: 0, indexWithinPage: 0, }); let currentSectionId: string | null = null; let currentSectionIndex: number = 0; let currentHeaderId: string | null = null; let count = 0; let isCurrentSectionCollapsed = false; let currentSectionItems: T[] = []; for (const keyedItem of this.table.keyedItems) { const sectionId = this.groupBy(keyedItem.item); if (sectionId !== currentSectionId) { // Store count and items for previous section if (currentHeaderId) { counts.set(currentHeaderId, { count, isComplete: true }); items.set(currentHeaderId, currentSectionItems); } // Start new section currentSectionId = sectionId; currentHeaderId = this.generateSectionId( `${sectionId}@${currentSectionIndex}`, ); indices.set(currentHeaderId, currentSectionIndex); currentSectionIndex++; count = 1; currentSectionItems = [keyedItem.item]; // Determine if section is collapsed from renderSection const section = this.renderSection( sectionId, this.getSectionItems(sectionId), ); isCurrentSectionCollapsed = this.collapsible && (section.collapsed ?? false); // Add section header to keyed items const sectionKeyedItem: KeyedItem = { id: currentHeaderId, key: currentHeaderId, item: keyedItem.item, index: keyedItems.length, pageIndex: 0, indexWithinPage: keyedItems.length, }; keyedItems.push(sectionKeyedItem); } else { count++; currentSectionItems.push(keyedItem.item); } // Add the actual data item only if section is not collapsed if (!isCurrentSectionCollapsed) { keyedItems.push(keyedItem); } } // Handle last section if (currentHeaderId) { const isLastSectionComplete = this.table.collection.fetchedLastPage; counts.set(currentHeaderId, { count, isComplete: isLastSectionComplete }); items.set(currentHeaderId, currentSectionItems); } return { counts, items, indices, keyedItems }; } get sectionCounts(): Map { return this.computeSectionData.counts; } get sectionItems(): Map { return this.computeSectionData.items; } get sectionIndices(): Map { return this.computeSectionData.indices; } get sectionsKeyedItems(): KeyedItem[] { return this.computeSectionData.keyedItems; } getSectionWithCount(sectionKeyedItem: KeyedItem): { sectionId: string; sectionIndex: number; section: Section & { count: number; hasMore: boolean; items: T[]; collapsed: boolean; }; collapsible: boolean; } { const headerId = sectionKeyedItem.id; const sectionData = headerId ? this.sectionCounts.get(headerId) || { count: 0, isComplete: true } : { count: 0, isComplete: true }; const sectionItemsList = headerId ? this.sectionItems.get(headerId) || [] : []; const sectionIndex = headerId ? this.sectionIndices.get(headerId) ?? 0 : 0; const sectionId = this.groupBy(sectionKeyedItem.item); const section = this.renderSection(sectionId, sectionItemsList); return { sectionId, sectionIndex, section: { ...section, count: sectionData.count, hasMore: !sectionData.isComplete, items: sectionItemsList, collapsed: this.collapsible && (section.collapsed ?? false), }, collapsible: this.collapsible, }; } isSectionRow(keyedItem: KeyedItem): boolean { return keyedItem.id.startsWith(SECTION_PREFIX); } isInvisibleRow(keyedItem: KeyedItem): boolean { return keyedItem.id === INVISIBLE_ROW_ID; } }