/** * AutoComplete Manager Module * Handles intelligent auto-suggestions and field auto-completion */ export class AutoCompleteManager { constructor(apiClient, uiManager) { this.apiClient = apiClient; this.uiManager = uiManager; this.searchTimeouts = {}; this.activeDropdowns = new Set(); this.selectedIndex = -1; this.availableTreeCodes = []; } async initialize() { try { this.availableTreeCodes = await this.apiClient.loadTreeCodes(); this.setupAutocomplete('localName', 'tree-suggestions'); this.setupAutocomplete('scientificName', 'tree-suggestions'); this.setupAutocomplete('commonName', 'tree-suggestions'); this.setupAutocomplete('treeCode', 'tree-codes'); } catch (error) { console.error('Error initializing auto-suggestions:', error); } } setupAutocomplete(fieldId, apiType) { const input = document.getElementById(fieldId); if (!input) return; this.createAutocompleteContainer(input, fieldId); this.attachEventListeners(input, fieldId, apiType); } createAutocompleteContainer(input, fieldId) { if (input.parentElement.classList.contains('autocomplete-container')) return; const container = document.createElement('div'); container.className = 'autocomplete-container'; input.parentNode.insertBefore(container, input); container.appendChild(input); const dropdown = document.createElement('div'); dropdown.className = 'autocomplete-dropdown'; dropdown.id = `${fieldId}-dropdown`; container.appendChild(dropdown); } attachEventListeners(input, fieldId, apiType) { input.addEventListener('input', (e) => this.handleInputChange(e, apiType)); input.addEventListener('keydown', (e) => this.handleKeyDown(e, fieldId)); input.addEventListener('blur', (e) => this.handleInputBlur(e, fieldId)); input.addEventListener('focus', (e) => this.handleInputFocus(e, fieldId, apiType)); } async handleInputChange(event, apiType) { const input = event.target; const query = input.value.trim(); const fieldId = input.id; this.clearSearchTimeout(fieldId); if (query.length < 2) { this.hideDropdown(fieldId); return; } this.showLoadingState(fieldId); this.debounceSearch(fieldId, query, apiType); } clearSearchTimeout(fieldId) { if (this.searchTimeouts[fieldId]) { clearTimeout(this.searchTimeouts[fieldId]); } } debounceSearch(fieldId, query, apiType) { this.searchTimeouts[fieldId] = setTimeout(async () => { try { const suggestions = await this.fetchSuggestions(query, apiType, fieldId); this.showSuggestions(fieldId, suggestions, query); } catch (error) { console.error('Error fetching suggestions:', error); this.hideDropdown(fieldId); } }, 300); } async fetchSuggestions(query, apiType, fieldId) { if (apiType === 'tree-codes') { return this.filterTreeCodes(query); } else { return this.searchTreeSuggestions(query, fieldId); } } filterTreeCodes(query) { return this.availableTreeCodes .filter(code => code.toLowerCase().includes(query.toLowerCase())) .slice(0, 10) .map(code => ({ primary: code, secondary: 'Tree Reference Code', type: 'code' })); } async searchTreeSuggestions(query, fieldId) { const suggestions = await this.apiClient.searchTreeSuggestions(query, 10); return suggestions.map(suggestion => ({ primary: this.getPrimaryText(suggestion, fieldId), secondary: this.getSecondaryText(suggestion, fieldId), badges: this.getBadges(suggestion), data: suggestion })); } getPrimaryText(suggestion, fieldId) { const fieldMap = { 'localName': suggestion.local_name, 'scientificName': suggestion.scientific_name, 'commonName': suggestion.common_name }; return fieldMap[fieldId] || suggestion.local_name || suggestion.scientific_name || suggestion.common_name; } getSecondaryText(suggestion, fieldId) { const parts = []; if (fieldId !== 'localName' && suggestion.local_name) { parts.push(`Local: ${suggestion.local_name}`); } if (fieldId !== 'scientificName' && suggestion.scientific_name) { parts.push(`Scientific: ${suggestion.scientific_name}`); } if (fieldId !== 'commonName' && suggestion.common_name) { parts.push(`Common: ${suggestion.common_name}`); } if (suggestion.tree_code) { parts.push(`Code: ${suggestion.tree_code}`); } return parts.join(' • '); } getBadges(suggestion) { const badges = []; if (suggestion.tree_code) { badges.push(suggestion.tree_code); } if (suggestion.fruiting_season) { badges.push(`Season: ${suggestion.fruiting_season}`); } return badges; } showLoadingState(fieldId) { const dropdown = document.getElementById(`${fieldId}-dropdown`); if (!dropdown) return; dropdown.innerHTML = '