import React, { ReactNode, useMemo } from 'react'; import { INestNode } from './interface'; import { IField } from '../../interfaces'; import { MinusCircleIcon, PlusCircleIcon } from '@heroicons/react/24/outline'; import { formatDate, getMeaAggName } from '@/utils'; import { parsedOffsetDate } from '@/lib/op/offset'; function getChildCount(node: INestNode): number { if (node.isCollapsed || node.children.length === 0) { return 1; } return node.children.map(getChildCount).reduce((a, b) => a + b, 0); } /** * render pivot table left tree table * @param node * @param dimsInCol * @param depth * @param cellRows * @returns */ function renderTree( node: INestNode, dimsInCol: IField[], depth: number, cellRows: ReactNode[][], meaNumber: number, onHeaderCollapse: (node: INestNode) => void, enableCollapse: boolean, displayOffset?: number ) { const childrenSize = getChildCount(node); const { isCollapsed } = node; if (depth > dimsInCol.length) { return; } const field = depth > 0 ? dimsInCol[depth - 1] : undefined; const formatter = node.kind === 'value' && field?.semanticType === 'temporal' ? (x) => formatDate(parsedOffsetDate(displayOffset, field.offset)(x)) : (x) => `${x}`; cellRows[depth].push(
{formatter(node.value)}
{node.kind === 'value' && node.height > 0 && enableCollapse && ( )}
); if (isCollapsed) return; for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; renderTree(child, dimsInCol, depth + 1, cellRows, meaNumber, onHeaderCollapse, enableCollapse, displayOffset); } } export interface TreeProps { data: INestNode; dimsInCol: IField[]; measInCol: IField[]; dimsInRow: IField[]; measInRow: IField[]; onHeaderCollapse: (node: INestNode) => void; enableCollapse: boolean; defaultAggregated: boolean; displayOffset?: number; } const TopTree: React.FC = (props) => { const { data, dimsInCol, measInCol, dimsInRow, measInRow, onHeaderCollapse } = props; const nodeCells: ReactNode[][] = useMemo(() => { const cellRows: ReactNode[][] = new Array(dimsInCol.length + 1).fill(0).map(() => []); renderTree(data, dimsInCol, 0, cellRows, measInCol.length, onHeaderCollapse, props.enableCollapse, props.displayOffset); const totalChildrenSize = getChildCount(data); // if all children in one layer are collapsed, then we need to reset the rowSpan of all children to 1 cellRows.forEach((row: ReactNode[], rowIdx: number) => { const rowSpanArr = row.map((child) => (React.isValidElement(child) ? (child.props as { rowSpan: number }).rowSpan : 0)); if (rowSpanArr.length > 0 && rowSpanArr[0] > 1 && rowSpanArr.every((v) => v === rowSpanArr[0])) { row.forEach((childObj, childIdx) => { if (React.isValidElement(childObj)) { const newChild = React.cloneElement(childObj, { ...(childObj.props as any), rowSpan: 1 }); cellRows[rowIdx][childIdx] = newChild; } }); } }); cellRows.push( new Array(totalChildrenSize).fill(0).flatMap((ele, idx) => measInCol.map((m) => ( {props.defaultAggregated ? getMeaAggName(m.name, m.aggName) : m.name} )) ) ); cellRows.shift(); const visibleRows = cellRows.filter((row) => row.length > 0); if (visibleRows.length === 0) { visibleRows.push([ Value , ]); } const rowHeaderColumnCount = dimsInRow.length + (measInRow.length > 0 ? 1 : 0); return visibleRows.map((row, rowIndex) => { if (rowHeaderColumnCount === 0) { return row; } if (rowIndex < visibleRows.length - 1) { return [ , ...row, ]; } return [ ...dimsInRow.map((field) => ( {field.name} )), ...(measInRow.length > 0 ? [ Measure , ] : []), ...row, ]; }); }, [data, dimsInCol, measInCol, dimsInRow, measInRow, onHeaderCollapse, props.defaultAggregated, props.enableCollapse, props.displayOffset]); return ( {nodeCells.map((row, rIndex) => ( {row} ))} ); }; export default TopTree;