// Location search functionality for the map class LocationSearch { constructor(map, apiKey) { this.map = map; this.apiKey = apiKey; this.searchResults = []; this.searchMarkersLayer = null; this.currentSearchQuery = ''; this.initializeSearchBar(); this.initializeSearchResults(); } initializeSearchBar() { // Create search toggle button using Leaflet control (positioned below settings button) const SearchToggleControl = L.Control.extend({ onAdd: function(map) { const button = L.DomUtil.create('button', 'location-search-toggle'); button.innerHTML = '🔍'; button.style.width = '48px'; button.style.height = '48px'; button.style.border = 'none'; button.style.cursor = 'pointer'; button.style.boxShadow = '0 1px 4px rgba(0,0,0,0.3)'; button.style.backgroundColor = 'white'; button.style.borderRadius = '4px'; button.style.padding = '0'; button.style.fontSize = '18px'; button.style.marginTop = '10px'; // Space below settings button button.title = 'Search locations'; button.id = 'location-search-toggle'; return button; } }); // Add the search toggle control to the map this.map.addControl(new SearchToggleControl({ position: 'topright' })); // Get reference to the created button const toggleButton = document.getElementById('location-search-toggle'); // Create search container (initially hidden) // Position it to the left of the search toggle button using fixed positioning const searchContainer = document.createElement('div'); searchContainer.className = 'location-search-container fixed z-50 w-80 hidden bg-white rounded-lg shadow-xl border p-2'; searchContainer.id = 'location-search-container'; // Create search input const searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.placeholder = 'Search locations'; searchInput.className = 'input input-bordered w-full text-sm bg-white shadow-lg'; searchInput.id = 'location-search-input'; // Create search button const searchButton = document.createElement('button'); searchButton.innerHTML = '🔍'; searchButton.className = 'btn btn-primary btn-sm absolute right-2 top-1/2 transform -translate-y-1/2'; searchButton.type = 'button'; // Create clear button const clearButton = document.createElement('button'); clearButton.innerHTML = '✕'; clearButton.className = 'btn btn-ghost btn-xs absolute right-12 top-1/2 transform -translate-y-1/2 hidden'; clearButton.id = 'location-search-clear'; // Assemble search bar const searchWrapper = document.createElement('div'); searchWrapper.className = 'relative'; searchWrapper.appendChild(searchInput); searchWrapper.appendChild(clearButton); searchWrapper.appendChild(searchButton); searchContainer.appendChild(searchWrapper); // Add search container to map container const mapContainer = document.getElementById('map'); mapContainer.appendChild(searchContainer); // Store references this.toggleButton = toggleButton; this.searchContainer = searchContainer; this.searchInput = searchInput; this.searchButton = searchButton; this.clearButton = clearButton; this.searchVisible = false; // Bind events this.bindSearchEvents(); } initializeSearchResults() { // Create results container (positioned below search container) const resultsContainer = document.createElement('div'); resultsContainer.className = 'location-search-results fixed z-40 w-80 max-h-96 overflow-y-auto bg-white rounded-lg shadow-xl border hidden'; resultsContainer.id = 'location-search-results'; const mapContainer = document.getElementById('map'); mapContainer.appendChild(resultsContainer); this.resultsContainer = resultsContainer; } bindSearchEvents() { // Toggle search bar visibility this.toggleButton.addEventListener('click', () => { this.toggleSearchBar(); }); // Search on button click this.searchButton.addEventListener('click', () => { this.performSearch(); }); // Search on Enter key this.searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.performSearch(); } }); // Clear search this.clearButton.addEventListener('click', () => { this.clearSearch(); }); // Show clear button when input has content this.searchInput.addEventListener('input', (e) => { if (e.target.value.length > 0) { this.clearButton.classList.remove('hidden'); } else { this.clearButton.classList.add('hidden'); } }); // Hide results and search bar when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.location-search-container') && !e.target.closest('.location-search-results') && !e.target.closest('#location-search-toggle')) { this.hideResults(); if (this.searchVisible) { this.hideSearchBar(); } } }); // Close search bar on Escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.searchVisible) { this.hideSearchBar(); } }); } async performSearch() { const query = this.searchInput.value.trim(); if (!query) return; this.currentSearchQuery = query; this.showLoading(); try { const response = await fetch(`/api/v1/locations?q=${encodeURIComponent(query)}`, { method: 'GET', headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`Search failed: ${response.status} ${response.statusText}`); } const data = await response.json(); this.displaySearchResults(data); } catch (error) { console.error('Location search error:', error); this.showError('Failed to search locations. Please try again.'); } } showLoading() { this.resultsContainer.innerHTML = `