/** * Project Selector Component * Reusable dropdown for selecting active project context in admin pages * * Features: * - Loads active projects from API * - Persists selection to localStorage * - Emits change events * - Supports callback functions * - Responsive design with icons */ class ProjectSelector { constructor(containerId, options = {}) { this.containerId = containerId; this.projects = []; this.selectedProjectId = null; // Options this.options = { showAllOption: options.showAllOption !== undefined ? options.showAllOption : true, allOptionText: options.allOptionText || 'All Projects (Template View)', onChange: options.onChange || null, storageKey: options.storageKey || 'selected_project_id', placeholder: options.placeholder || 'Select a project...', label: options.label || 'Active Project Context', showLabel: options.showLabel !== undefined ? options.showLabel : true, compact: options.compact || false, // Compact mode for navbar autoLoad: options.autoLoad !== undefined ? options.autoLoad : true }; // Auth token this.token = localStorage.getItem('admin_token'); if (this.options.autoLoad) { this.init(); } } /** * Initialize the component */ async init() { try { // Load saved project from localStorage const savedProjectId = localStorage.getItem(this.options.storageKey); if (savedProjectId) { this.selectedProjectId = savedProjectId; } // Load projects from API await this.loadProjects(); // Render the selector this.render(); // Attach event listeners this.attachEventListeners(); // Trigger initial change event if project was pre-selected if (this.selectedProjectId && this.options.onChange) { this.options.onChange(this.selectedProjectId, this.getSelectedProject()); } } catch (error) { console.error('Failed to initialize project selector:', error); this.renderError(); } } /** * Load projects from API */ async loadProjects() { const response = await fetch('/api/admin/projects?active=true', { headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' } }); if (response.status === 401) { localStorage.removeItem('admin_token'); window.location.href = '/admin/login.html'; return; } const data = await response.json(); if (data.success) { this.projects = data.projects || []; // Sort by name this.projects.sort((a, b) => a.name.localeCompare(b.name)); } else { throw new Error(data.message || 'Failed to load projects'); } } /** * Render the selector component */ render() { const container = document.getElementById(this.containerId); if (!container) { console.error(`Container #${this.containerId} not found`); return; } // Determine selected project const selectedProject = this.getSelectedProject(); // Build HTML based on compact or full mode if (this.options.compact) { container.innerHTML = this.renderCompact(selectedProject); } else { container.innerHTML = this.renderFull(selectedProject); } } /** * Render compact mode (for navbar) */ renderCompact(selectedProject) { const displayText = selectedProject ? selectedProject.name : this.options.placeholder; const displayColor = selectedProject ? 'text-indigo-700' : 'text-gray-500'; return `
${escapeHtml(selectedProject.description)}
` : ''}Viewing template text with variable placeholders. Select a project to see rendered values.
Please refresh the page to try again.