139 lines
4.1 KiB
TypeScript
139 lines
4.1 KiB
TypeScript
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<T extends Record<string, any>>(
|
|
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<any, T & { subRows?: T[] }>();
|
|
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<T>(row: any, config: TreeConfig<T>): 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<T extends Record<string, any>>(
|
|
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;
|
|
});
|
|
}
|