// Category types and utilities export interface Category { id: number; name: string; slug: string; path: string; level: number; parent_id: number | null; category_count: number; children?: Category[]; } export interface CategoryTreeNode { id: string; name: string; children?: CategoryTreeNode[]; data?: Category; } export interface CategoryStats { total: number; sectors: number; business_types: number; sub_categories: number; leaf_categories: number; } // Convert flat categories to tree structure export function buildCategoryTree(categories: Category[]): CategoryTreeNode[] { const map = new Map(); const roots: CategoryTreeNode[] = []; // First pass: create all nodes for (const cat of categories) { map.set(cat.path, { id: cat.path, name: cat.name, children: [], data: cat, }); } // Second pass: link children to parents for (const cat of categories) { const node = map.get(cat.path)!; const pathParts = cat.path.split('.'); if (pathParts.length === 1) { // Root node roots.push(node); } else { // Find parent const parentPath = pathParts.slice(0, -1).join('.'); const parent = map.get(parentPath); if (parent) { parent.children!.push(node); } else { // Parent not found, add as root roots.push(node); } } } // Sort children alphabetically const sortChildren = (nodes: CategoryTreeNode[]) => { nodes.sort((a, b) => a.name.localeCompare(b.name)); for (const node of nodes) { if (node.children && node.children.length > 0) { sortChildren(node.children); } } }; sortChildren(roots); return roots; } // Convert to react-d3-tree format export function toD3Tree(nodes: CategoryTreeNode[]): any { return nodes.map((node) => ({ name: node.name, attributes: { level: node.data?.level, count: node.data?.category_count, }, children: node.children && node.children.length > 0 ? toD3Tree(node.children) : undefined, })); } // Get breadcrumb path for a category export function getCategoryBreadcrumb(path: string, categories: Category[]): Category[] { const parts = path.split('.'); const breadcrumb: Category[] = []; for (let i = 1; i <= parts.length; i++) { const partialPath = parts.slice(0, i).join('.'); const cat = categories.find((c) => c.path === partialPath); if (cat) { breadcrumb.push(cat); } } return breadcrumb; } // Search categories export function searchCategories(categories: Category[], query: string): Category[] { const lowerQuery = query.toLowerCase(); return categories.filter( (cat) => cat.name.toLowerCase().includes(lowerQuery) || cat.path.toLowerCase().includes(lowerQuery) ); } // Get level name export function getLevelName(level: number): string { switch (level) { case 1: return 'Sector'; case 2: return 'Business Type'; case 3: return 'Sub-category'; case 4: return 'Category'; default: return 'Unknown'; } } // Get level color export function getLevelColor(level: number): string { switch (level) { case 1: return 'bg-blue-500'; case 2: return 'bg-green-500'; case 3: return 'bg-yellow-500'; case 4: return 'bg-purple-500'; default: return 'bg-gray-500'; } }