Latest changes
This commit is contained in:
138
src/Griddy/features/tree/transformTreeData.ts
Normal file
138
src/Griddy/features/tree/transformTreeData.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user