92 lines
2.4 KiB
TypeScript
92 lines
2.4 KiB
TypeScript
|
|
import { type Category } from '../types/Category';
|
||
|
|
|
||
|
|
// Interfaz para la categoría "Aplanada" y enriquecida para UI
|
||
|
|
export interface FlatCategory {
|
||
|
|
id: number;
|
||
|
|
name: string;
|
||
|
|
level: number;
|
||
|
|
parentId: number | null;
|
||
|
|
path: string; // "Vehículos > Autos"
|
||
|
|
isSelectable: boolean; // True si no tiene hijos (es hoja)
|
||
|
|
hasChildren: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Interfaz auxiliar para el árbol
|
||
|
|
interface CategoryNode extends Category {
|
||
|
|
children: CategoryNode[];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Convierte una lista plana de BD en un árbol recursivo
|
||
|
|
*/
|
||
|
|
export const buildTree = (categories: Category[]): CategoryNode[] => {
|
||
|
|
const map = new Map<number, CategoryNode>();
|
||
|
|
const roots: CategoryNode[] = [];
|
||
|
|
|
||
|
|
// 1. Inicializar nodos
|
||
|
|
categories.forEach(cat => {
|
||
|
|
map.set(cat.id, { ...cat, children: [] });
|
||
|
|
});
|
||
|
|
|
||
|
|
// 2. Construir relaciones
|
||
|
|
categories.forEach(cat => {
|
||
|
|
const node = map.get(cat.id);
|
||
|
|
if (node) {
|
||
|
|
if (cat.parentId && map.has(cat.parentId)) {
|
||
|
|
map.get(cat.parentId)!.children.push(node);
|
||
|
|
} else {
|
||
|
|
roots.push(node);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return roots;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Aplana el árbol para usarlo en Selects/Autocompletes
|
||
|
|
* Calcula breadcrumbs y deshabilita padres
|
||
|
|
*/
|
||
|
|
export const flattenCategoriesForSelect = (
|
||
|
|
nodes: CategoryNode[],
|
||
|
|
level = 0,
|
||
|
|
parentPath = ""
|
||
|
|
): FlatCategory[] => {
|
||
|
|
let result: FlatCategory[] = [];
|
||
|
|
|
||
|
|
// Ordenar alfabéticamente para facilitar búsqueda visual
|
||
|
|
const sortedNodes = [...nodes].sort((a, b) => a.name.localeCompare(b.name));
|
||
|
|
|
||
|
|
for (const node of sortedNodes) {
|
||
|
|
const currentPath = parentPath ? `${parentPath} > ${node.name}` : node.name;
|
||
|
|
const hasChildren = node.children && node.children.length > 0;
|
||
|
|
|
||
|
|
// Agregamos el nodo actual
|
||
|
|
result.push({
|
||
|
|
id: node.id,
|
||
|
|
name: node.name,
|
||
|
|
level: level,
|
||
|
|
parentId: node.parentId || null,
|
||
|
|
path: currentPath,
|
||
|
|
hasChildren: hasChildren,
|
||
|
|
// REGLA DE ORO: Solo seleccionable si NO tiene hijos
|
||
|
|
isSelectable: !hasChildren
|
||
|
|
});
|
||
|
|
|
||
|
|
// Recursión para los hijos
|
||
|
|
if (hasChildren) {
|
||
|
|
const childrenFlat = flattenCategoriesForSelect(node.children, level + 1, currentPath);
|
||
|
|
result = [...result, ...childrenFlat];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Función Helper que hace todo el proceso desde la respuesta de la API
|
||
|
|
*/
|
||
|
|
export const processCategories = (rawCategories: Category[]): FlatCategory[] => {
|
||
|
|
const tree = buildTree(rawCategories);
|
||
|
|
return flattenCategoriesForSelect(tree);
|
||
|
|
};
|