// ============================================ // Sidebar — Tree Navigation Component // ============================================ const Sidebar = { init() { this.renderTree(); this.bindEvents(); this.updateStats(); }, bindEvents() { // Tree node toggles and selection (delegated for dynamic content) Helpers.$('#tree-root').addEventListener('click', (e) => { const header = e.target.closest('.tree-node__header'); if (!header) return; const nodeId = header.dataset.nodeId; const node = MockData.findNodeInHierarchy(nodeId); const isExpanded = Helpers.$('#children-' + nodeId)?.classList.contains('tree-node__children--expanded'); // If camera node clicked, open gallery with filter if (node && node.type === 'camera') { this.openGalleryForCamera(nodeId, node); return; } // Toggle expand/collapse for non-camera nodes this.toggleNode(header); }); }, openGalleryForCamera(nodeId, node) { // Navigate to gallery view App.navigate('gallery'); // Expand parent nodes to show selected camera this.expandToNode(nodeId); // Set active sidebar node Helpers.$$('#tree-root .tree-node__header--active') .forEach(h => h.classList.remove('tree-node__header--active')); Helpers.$(`[data-node-id="${nodeId}"]`)?.classList.add('tree-node__header--active'); // Update breadcrumb const breadcrumb = Helpers.buildBreadcrumb(nodeId); const breadcrumbEl = Helpers.$('#gallery-breadcrumb'); if (breadcrumbEl) breadcrumbEl.textContent = breadcrumb; // Filter gallery by camera if (Gallery && Gallery.filters) { Gallery.filters.camera = node.name; Gallery.filters.category = ''; Gallery.filters.search = ''; // Update select elements const cameraSelect = Helpers.$('#gallery-filter-camera'); if (cameraSelect) cameraSelect.value = node.name; const categorySelect = Helpers.$('#gallery-filter-category'); if (categorySelect) categorySelect.value = ''; Gallery.renderGrid(); } }, expandToNode(nodeId) { // Expand all parent nodes to show the selected camera const parts = nodeId.split('-'); let currentId = parts[0]; for (let i = 1; i < parts.length; i++) { currentId += '-' + parts[i]; const childrenEl = Helpers.$('#children-' + currentId); const toggleEl = Helpers.$(`[data-toggle-for="${currentId}"]`); if (childrenEl && !childrenEl.classList.contains('tree-node__children--expanded')) { childrenEl.classList.add('tree-node__children--expanded'); toggleEl?.classList.add('tree-node__toggle--expanded'); } } }, renderTree() { const root = Helpers.$('#tree-root'); if (!root) return; root.innerHTML = this.buildTreeHTML(MockData.hierarchy); }, buildTreeHTML(nodes, depth = 0) { let html = ''; nodes.forEach(node => { const childCount = node.children ? node.children.length : 0; const imageCount = this.getImageCount(node.id); const hasChildren = childCount > 0; const nodeId = node.id; html += `
`; html += `
`; // Toggle arrow if (hasChildren) { html += ``; } else { html += ``; } // Icon html += ``; // Label html += `${node.name}`; // Badge if (imageCount > 0) { html += `${imageCount}`; } html += `
`; // Children if (hasChildren) { html += `
`; html += this.buildTreeHTML(node.children, depth + 1); html += `
`; } html += `
`; }); return html; }, getImageCount(nodeId) { const parts = nodeId.split('-'); let customerFilter = null; if (parts[0] === 'west') customerFilter = 'west'; if (parts[0] === 'pharma') customerFilter = 'pharma'; if (!customerFilter) return 0; const customerImages = MockData.images.filter(img => img.customer === customerFilter); if (nodeId.includes('c') && nodeId.split('-').length > 4) { // Camera node - count by camera name const cameraNameMatch = nodeId.split('-').slice(-1)[0].toUpperCase(); // Get camera name from hierarchy const node = MockData.findNodeInHierarchy(nodeId); if (node && node.name) { return customerImages.filter(img => img.camera === node.name).length; } } return customerImages.length; }, toggleNode(header) { const nodeId = header.dataset.nodeId; const childrenEl = Helpers.$('#children-' + nodeId); const toggleEl = Helpers.$(`[data-toggle-for="${nodeId}"]`); if (!childrenEl) return; const isExpanded = childrenEl.classList.contains('tree-node__children--expanded'); if (isExpanded) { childrenEl.classList.remove('tree-node__children--expanded'); if (toggleEl) toggleEl.classList.remove('tree-node__toggle--expanded'); } else { childrenEl.classList.add('tree-node__children--expanded'); if (toggleEl) toggleEl.classList.add('tree-node__toggle--expanded'); } }, updateStats() { const totalImages = MockData.images.length; const totalCameras = MockData.hierarchy.reduce((count, customer) => { return count + this.countCameras(customer.children || []); }, 0); Helpers.$('#stat-total-images').textContent = totalImages; Helpers.$('#stat-cameras').textContent = totalCameras; }, countCameras(nodes) { let count = 0; nodes.forEach(n => { if (n.type === 'camera') count++; if (n.children) count += this.countCameras(n.children); }); return count; } };