let map = L.map('map').setView([15.5527, 48.5164], 6); // Define the two tile layers const voyager = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', { attribution: '© OpenStreetMap contributors © CARTO', subdomains: 'abcd', maxZoom: 19 }); const openStreetMap = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: 'Map data © OpenStreetMap contributors', maxZoom: 19 }); // Add the default layer (Voyager) voyager.addTo(map); // Group the layers for switching const baseMaps = { "English Map (CartoDB Voyager)": voyager, "Original Map (OpenStreetMap)": openStreetMap }; // Add the layer control to the map L.control.layers(baseMaps).addTo(map); async function searchFamily() { const familyName = document.getElementById('searchInput').value.trim(); if (!familyName) { alert('Please enter a family name.'); return; } try { const response = await fetch(`/api/search?family=${encodeURIComponent(familyName)}`); const familyResult = await response.json(); if (!familyResult.length) { alert('No matching family found.'); return; } clearMarkers(); familyResult.forEach(record => { L.marker([record.lat, record.lng]).addTo(map) .bindPopup(`${record.family}
City: ${record.city}`) .openPopup(); }); const first = familyResult[0]; map.setView([first.lat, first.lng], 7); } catch (error) { console.error('Search error:', error); alert('Something went wrong while searching. Please try again later.'); } } function clearMarkers() { map.eachLayer(layer => { if (layer instanceof L.Marker) { map.removeLayer(layer); } }); } // Autocomplete data - loaded from database let familyNames = []; let fuse; // Load families from database for autocomplete async function loadFamiliesForAutocomplete() { try { console.log('📚 Loading families from database for autocomplete...'); const response = await fetch('/api/families'); const families = await response.json(); // Extract unique family names familyNames = [...new Set(families.map(f => f.family))]; console.log('✅ Loaded', familyNames.length, 'family names for autocomplete'); // Initialize or update Fuse.js fuse = new Fuse(familyNames, { includeScore: true, threshold: 0.4 // Lower = stricter matching }); } catch (error) { console.error('❌ Failed to load families for autocomplete:', error); } } const searchInput = document.getElementById('searchInput'); const suggestionsBox = document.createElement('div'); suggestionsBox.classList.add('suggestions'); searchInput.parentNode.style.position = 'relative'; // Make parent relative searchInput.parentNode.appendChild(suggestionsBox); searchInput.addEventListener('input', () => { const value = searchInput.value.trim(); suggestionsBox.innerHTML = ''; if (!value || !fuse) return; const results = fuse.search(value); results.forEach(result => { const option = document.createElement('div'); option.textContent = result.item; option.onclick = () => { searchInput.value = result.item; suggestionsBox.innerHTML = ''; }; suggestionsBox.appendChild(option); }); }); L.control.logo = function (opts) { return new L.Control.Logo(opts); }; L.Control.Logo = L.Control.extend({ onAdd: function () { const div = L.DomUtil.create('div', 'custom-logo'); div.innerHTML = ` Logo Shevach `; return div; }, onRemove: function () { // Nothing to clean up } }); L.control.logo({ position: 'bottomleft' }).addTo(map); // Add Family Form Functions function toggleAddFamilyForm() { const modal = document.getElementById('addFamilyModal'); const form = document.getElementById('addFamilyForm'); const message = document.getElementById('formMessage'); if (modal.style.display === 'block') { modal.style.display = 'none'; form.reset(); message.textContent = ''; message.className = 'form-message'; } else { modal.style.display = 'block'; } } async function addFamily(event) { event.preventDefault(); const familyName = document.getElementById('familyName').value.trim(); const cityName = document.getElementById('cityName').value.trim(); const latitude = parseFloat(document.getElementById('latitude').value); const longitude = parseFloat(document.getElementById('longitude').value); const messageEl = document.getElementById('formMessage'); messageEl.textContent = 'Adding family...'; messageEl.className = 'form-message info'; console.log('📝 Adding family:', { familyName, cityName, latitude, longitude }); try { console.log('🌐 Sending POST request to /api/families'); const response = await fetch('/api/families', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ family: familyName, city: cityName, lat: latitude, lng: longitude }) }); console.log('📡 Response status:', response.status, response.statusText); console.log('📡 Response headers:', Object.fromEntries(response.headers.entries())); const contentType = response.headers.get('content-type'); let data; if (contentType && contentType.includes('application/json')) { data = await response.json(); console.log('📦 Response data:', data); } else { const text = await response.text(); console.error('⚠️ Non-JSON response:', text); data = { error: 'Server returned non-JSON response', details: text.substring(0, 200) }; } if (response.ok) { console.log('✅ Family added successfully!'); messageEl.textContent = '✅ Family added successfully!'; messageEl.className = 'form-message success'; // Reload autocomplete with updated family list await loadFamiliesForAutocomplete(); // Add marker to map L.marker([latitude, longitude]).addTo(map) .bindPopup(`${familyName}
City: ${cityName}`) .openPopup(); map.setView([latitude, longitude], 10); // Reset form after 2 seconds setTimeout(() => { toggleAddFamilyForm(); }, 2000); } else { console.error('❌ Server error:', data); messageEl.textContent = `❌ Error (${response.status}): ${data.error || data.message || 'Unknown error'}`; messageEl.className = 'form-message error'; } } catch (error) { console.error('❌ Add family error:', error); messageEl.textContent = `❌ Failed: ${error.message || 'Network error'}`; messageEl.className = 'form-message error'; } } // Close modal when clicking outside window.onclick = function(event) { const modal = document.getElementById('addFamilyModal'); if (event.target === modal) { toggleAddFamilyForm(); } } // Allow Enter key to trigger search document.getElementById('searchInput').addEventListener('keypress', (e) => { if (e.key === 'Enter') { searchFamily(); } }); // Load families for autocomplete when page loads loadFamiliesForAutocomplete();