import type { TreeConfig } from '../../core/types'; /** * Transforms flat data with parentId references into nested tree structure * @param data - Flat array of data with parent references * @param parentIdField - Field name containing parent ID (default: 'parentId') * @param idField - Field name containing node ID (default: 'id') * @param maxDepth - Maximum tree depth to build (default: Infinity) * @returns Array of root nodes with subRows property */ export function transformFlatToNested>( data: T[], parentIdField: keyof T | string = 'parentId', idField: keyof T | string = 'id', maxDepth = Infinity, ): T[] { // Build a map of id -> node for quick lookups const nodeMap = new Map(); const roots: (T & { subRows?: T[] })[] = []; // First pass: create map of all nodes data.forEach((item) => { nodeMap.set(item[idField], { ...item, subRows: [] }); }); // Second pass: build tree structure data.forEach((item) => { const node = nodeMap.get(item[idField])!; const parentId = item[parentIdField]; if (parentId == null || parentId === '') { // Root node (no parent or empty parent) roots.push(node); } else { const parent = nodeMap.get(parentId); if (parent) { // Add to parent's children if (!parent.subRows) { parent.subRows = []; } parent.subRows.push(node); } else { // Orphaned node (parent doesn't exist) - treat as root roots.push(node); } } }); // Enforce max depth by removing children beyond the limit if (maxDepth !== Infinity) { const enforceDepth = (nodes: (T & { subRows?: T[] })[], currentDepth: number) => { if (currentDepth >= maxDepth) { return; } nodes.forEach((node) => { if (node.subRows && node.subRows.length > 0) { if (currentDepth + 1 >= maxDepth) { // Remove children at max depth delete node.subRows; } else { enforceDepth(node.subRows, currentDepth + 1); } } }); }; enforceDepth(roots, 0); } return roots; } /** * Determines if a node has children (can be expanded) * @param row - The data row * @param config - Tree configuration * @returns true if node has or can have children */ export function hasChildren(row: any, config: TreeConfig): boolean { // If user provided hasChildren function, use it if (config.hasChildren) { return config.hasChildren(row); } // Check for children array const childrenField = (config.childrenField as string) || 'children'; if (row[childrenField] && Array.isArray(row[childrenField])) { return row[childrenField].length > 0; } // Check for subRows (TanStack Table convention) if (row.subRows && Array.isArray(row.subRows)) { return row.subRows.length > 0; } // Check for boolean flag (common pattern) if (typeof row.hasChildren === 'boolean') { return row.hasChildren; } // Check for childCount property if (typeof row.childCount === 'number') { return row.childCount > 0; } // Default: assume no children return false; } /** * Helper to insert children into data array at parent location * Used by lazy loading to update the data array with fetched children * @param data - Current data array * @param parentId - ID of parent node * @param children - Children to insert * @param idField - Field name containing node ID * @returns Updated data array with children inserted */ export function insertChildrenIntoData>( data: T[], parentId: string, children: T[], idField: keyof T | string = 'id', ): T[] { return data.map((item) => { if (item[idField] === parentId) { // Found the parent - add children as subRows return { ...item, subRows: children }; } // Recursively search in subRows if (item.subRows && Array.isArray(item.subRows)) { return { ...item, subRows: insertChildrenIntoData(item.subRows, parentId, children, idField), }; } return item; }); }