/** * Representatives Map Module * Handles map initialization, office location display, and popup cards */ // Map state let representativesMap = null; let representativeMarkers = []; let currentPostalCode = null; // Office location icons const officeIcons = { federal: L.divIcon({ className: 'office-marker federal', html: '
🏛️
', iconSize: [40, 40], iconAnchor: [20, 40], popupAnchor: [0, -40] }), provincial: L.divIcon({ className: 'office-marker provincial', html: '
🏢
', iconSize: [40, 40], iconAnchor: [20, 40], popupAnchor: [0, -40] }), municipal: L.divIcon({ className: 'office-marker municipal', html: '
🏛️
', iconSize: [40, 40], iconAnchor: [20, 40], popupAnchor: [0, -40] }) }; // Initialize the representatives map function initializeRepresentativesMap() { const mapContainer = document.getElementById('main-map'); if (!mapContainer) { console.warn('Map container not found'); return; } // Avoid double initialization if (representativesMap) { console.log('Map already initialized, invalidating size instead'); representativesMap.invalidateSize(); return; } // Check if Leaflet is available if (typeof L === 'undefined') { console.error('Leaflet (L) is not defined. Map initialization failed.'); return; } // We'll initialize the map even if not visible, then invalidate size when needed console.log('Initializing representatives map...'); // Center on Alberta representativesMap = L.map('main-map').setView([53.9333, -116.5765], 6); // Add tile layer L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19, minZoom: 2 }).addTo(representativesMap); // Trigger size invalidation after a brief moment to ensure proper rendering setTimeout(() => { if (representativesMap) { representativesMap.invalidateSize(); } }, 200); } // Clear all representative markers from the map function clearRepresentativeMarkers() { representativeMarkers.forEach(marker => { representativesMap.removeLayer(marker); }); representativeMarkers = []; } // Add representative offices to the map function displayRepresentativeOffices(representatives, postalCode) { // Initialize map if not already done if (!representativesMap) { console.log('Map not initialized, initializing now...'); initializeRepresentativesMap(); } if (!representativesMap) { console.error('Failed to initialize map'); return; } clearRepresentativeMarkers(); currentPostalCode = postalCode; const validOffices = []; let bounds = []; console.log('Processing representatives for map display:', representatives.length); // Group representatives by office location to handle shared addresses const locationGroups = new Map(); representatives.forEach((rep, index) => { console.log(`Processing representative ${index + 1}:`, rep.name, rep.representative_set_name); // Try to get office location from various sources const offices = getOfficeLocations(rep); console.log(`Found ${offices.length} offices for ${rep.name}:`, offices); offices.forEach((office, officeIndex) => { console.log(`Office ${officeIndex + 1} for ${rep.name}:`, office); if (office.lat && office.lng) { const locationKey = `${office.lat.toFixed(6)},${office.lng.toFixed(6)}`; if (!locationGroups.has(locationKey)) { locationGroups.set(locationKey, { lat: office.lat, lng: office.lng, address: office.address, representatives: [], offices: [] }); } locationGroups.get(locationKey).representatives.push(rep); locationGroups.get(locationKey).offices.push(office); validOffices.push({ rep, office }); } else { console.log(`No coordinates found for ${rep.name} office:`, office); } }); }); // Create markers for each location group let offsetIndex = 0; locationGroups.forEach((locationGroup, locationKey) => { console.log(`Creating markers for location ${locationKey} with ${locationGroup.representatives.length} representatives`); if (locationGroup.representatives.length === 1) { // Single representative at this location const rep = locationGroup.representatives[0]; const office = locationGroup.offices[0]; const marker = createOfficeMarker(rep, office); if (marker) { representativeMarkers.push(marker); marker.addTo(representativesMap); bounds.push([office.lat, office.lng]); } } else { // Multiple representatives at same location - create offset markers locationGroup.representatives.forEach((rep, repIndex) => { const office = locationGroup.offices[repIndex]; // Add small offset to avoid exact overlap const offsetDistance = 0.0005; // About 50 meters const angle = (repIndex * 2 * Math.PI) / locationGroup.representatives.length; const offsetLat = office.lat + (offsetDistance * Math.cos(angle)); const offsetLng = office.lng + (offsetDistance * Math.sin(angle)); const offsetOffice = { ...office, lat: offsetLat, lng: offsetLng }; console.log(`Creating offset marker for ${rep.name} at ${offsetLat}, ${offsetLng}`); const marker = createOfficeMarker(rep, offsetOffice, locationGroup.representatives.length > 1); if (marker) { representativeMarkers.push(marker); marker.addTo(representativesMap); bounds.push([offsetLat, offsetLng]); } }); } }); console.log(`Total markers created: ${representativeMarkers.length}`); console.log(`Bounds array:`, bounds); // Fit map to show all offices, or center on Alberta if no offices found if (bounds.length > 0) { representativesMap.fitBounds(bounds, { padding: [20, 20] }); } else { // If no office locations found, show a message and keep Alberta view console.log('No office locations with coordinates found, showing message'); showMapMessage('Office locations not available for representatives in this area.'); } console.log(`Displayed ${validOffices.length} office locations on map`); } // Extract office locations from representative data function getOfficeLocations(representative) { const offices = []; console.log(`Getting office locations for ${representative.name}`); console.log('Representative offices data:', representative.offices); // Check various sources for office location data if (representative.offices && Array.isArray(representative.offices)) { representative.offices.forEach((office, index) => { console.log(`Processing office ${index + 1}:`, office); // Use the 'postal' field which contains the address if (office.postal || office.address) { const officeData = { type: office.type || 'office', address: office.postal || office.address || 'Office Address', postal_code: office.postal_code, phone: office.tel || office.phone, fax: office.fax, lat: office.lat, lng: office.lng }; console.log('Created office data:', officeData); offices.push(officeData); } }); } // For all offices without coordinates, add approximate coordinates offices.forEach(office => { if (!office.lat || !office.lng) { console.log(`Adding coordinates to office for ${representative.name}`); const approxLocation = getApproximateLocationByDistrict(representative.district_name, representative.representative_set_name); console.log('Approximate location:', approxLocation); if (approxLocation) { office.lat = approxLocation.lat; office.lng = approxLocation.lng; console.log('Updated office with coordinates:', office); } } }); // If no offices found at all, create a fallback office if (offices.length === 0 && representative.representative_set_name) { console.log(`No offices found, creating fallback office for ${representative.name}`); const approxLocation = getApproximateLocationByDistrict(representative.district_name, representative.representative_set_name); console.log('Approximate location:', approxLocation); if (approxLocation) { const fallbackOffice = { type: 'representative', address: `${representative.name} - ${representative.district_name || representative.representative_set_name}`, lat: approxLocation.lat, lng: approxLocation.lng }; console.log('Created fallback office:', fallbackOffice); offices.push(fallbackOffice); } } console.log(`Total offices found for ${representative.name}:`, offices.length); return offices; } // Get approximate location based on district and government level function getApproximateLocationByDistrict(district, level) { // Specific locations for Edmonton officials const edmontonLocations = { // City Hall for municipal officials 'Edmonton': { lat: 53.5444, lng: -113.4909 }, // Edmonton City Hall "O-day'min": { lat: 53.5444, lng: -113.4909 }, // Edmonton City Hall // Provincial Legislature 'Edmonton-Glenora': { lat: 53.5344, lng: -113.5065 }, // Alberta Legislature // Federal offices (approximate downtown Edmonton) 'Edmonton Centre': { lat: 53.5461, lng: -113.4938 } }; // Try specific district first if (district && edmontonLocations[district]) { return edmontonLocations[district]; } // Fallback based on government level const levelLocations = { 'House of Commons': { lat: 53.5461, lng: -113.4938 }, // Downtown Edmonton 'Legislative Assembly of Alberta': { lat: 53.5344, lng: -113.5065 }, // Alberta Legislature 'Edmonton City Council': { lat: 53.5444, lng: -113.4909 } // Edmonton City Hall }; return levelLocations[level] || { lat: 53.9333, lng: -116.5765 }; // Default to Alberta center } // Create a marker for an office location function createOfficeMarker(representative, office, isSharedLocation = false) { if (!office.lat || !office.lng) { return null; } // Determine icon based on government level let icon = officeIcons.municipal; // default if (representative.representative_set_name) { if (representative.representative_set_name.includes('House of Commons')) { icon = officeIcons.federal; } else if (representative.representative_set_name.includes('Legislative Assembly')) { icon = officeIcons.provincial; } } const marker = L.marker([office.lat, office.lng], { icon }); // Create popup content const popupContent = createOfficePopupContent(representative, office, isSharedLocation); marker.bindPopup(popupContent, { maxWidth: 300, className: 'office-popup' }); return marker; } // Create popup content for office markers function createOfficePopupContent(representative, office, isSharedLocation = false) { const level = getRepresentativeLevel(representative.representative_set_name); const levelClass = level.toLowerCase().replace(' ', '-'); return `
${representative.photo_url ? `${representative.name}` : ''}

${representative.name}

${level}

${representative.district_name || 'District not specified'}

${isSharedLocation ? '

Note: Office location shared with other representatives

' : ''}
Office Information
${office.address ? `

Address: ${office.address}

` : ''} ${office.phone ? `

Phone: ${office.phone}

` : ''} ${office.fax ? `

Fax: ${office.fax}

` : ''} ${office.postal_code ? `

Postal Code: ${office.postal_code}

` : ''}
${representative.email ? `` : ''}
`; }// Get representative level for display function getRepresentativeLevel(representativeSetName) { if (!representativeSetName) return 'Representative'; if (representativeSetName.includes('House of Commons')) { return 'Federal MP'; } else if (representativeSetName.includes('Legislative Assembly')) { return 'Provincial MLA'; } else { return 'Municipal Representative'; } } // Show a message on the map function showMapMessage(message) { const mapContainer = document.getElementById('main-map'); if (!mapContainer) return; // Remove any existing message const existingMessage = mapContainer.querySelector('.map-message'); if (existingMessage) { existingMessage.remove(); } // Create and show new message const messageDiv = document.createElement('div'); messageDiv.className = 'map-message'; messageDiv.innerHTML = `

${message}

`; mapContainer.appendChild(messageDiv); // Remove message after 5 seconds setTimeout(() => { if (messageDiv.parentNode) { messageDiv.remove(); } }, 5000); } // Initialize form handlers function initializePostalForm() { const postalForm = document.getElementById('postal-form'); // Handle postal code form submission if (postalForm) { postalForm.addEventListener('submit', (event) => { event.preventDefault(); const postalCode = document.getElementById('postal-code').value.trim(); if (postalCode) { handlePostalCodeSubmission(postalCode); } }); } // Handle email button clicks in popups document.addEventListener('click', (event) => { if (event.target.classList.contains('email-btn')) { event.preventDefault(); const email = event.target.dataset.email; const name = event.target.dataset.name; const level = event.target.dataset.level; if (window.openEmailModal) { window.openEmailModal(email, name, level); } } }); } // Handle postal code submission and fetch representatives async function handlePostalCodeSubmission(postalCode) { try { showLoading(); hideError(); // Normalize postal code const normalizedPostalCode = postalCode.toUpperCase().replace(/\s/g, ''); // Fetch representatives data const response = await fetch(`/api/representatives/by-postal/${normalizedPostalCode}`); const data = await response.json(); hideLoading(); if (data.success && data.data && data.data.representatives) { // Display representatives on map displayRepresentativeOffices(data.data.representatives, normalizedPostalCode); // Also update the representatives display section using the existing system if (window.representativesDisplay) { window.representativesDisplay.displayRepresentatives(data.data.representatives); } // Update location info manually if the existing system doesn't work const locationDetails = document.getElementById('location-details'); if (locationDetails && data.data.location) { const location = data.data.location; locationDetails.textContent = `${location.city}, ${location.province} (${normalizedPostalCode})`; } else if (locationDetails) { locationDetails.textContent = `Postal Code: ${normalizedPostalCode}`; } if (window.locationInfo) { window.locationInfo.updateLocationInfo(data.data.location, normalizedPostalCode); } // Show the representatives section const representativesSection = document.getElementById('representatives-section'); representativesSection.style.display = 'block'; // Fix map rendering after section becomes visible setTimeout(() => { if (!representativesMap) { initializeRepresentativesMap(); } if (representativesMap) { representativesMap.invalidateSize(); // Try to fit bounds again if we have markers if (representativeMarkers.length > 0) { const bounds = representativeMarkers.map(marker => marker.getLatLng()); if (bounds.length > 0) { representativesMap.fitBounds(bounds, { padding: [20, 20] }); } } } }, 300); // Show refresh button const refreshBtn = document.getElementById('refresh-btn'); if (refreshBtn) { refreshBtn.style.display = 'inline-block'; // Store postal code for refresh functionality refreshBtn.dataset.postalCode = normalizedPostalCode; } // Show success message if (window.messageDisplay) { window.messageDisplay.show(`Found ${data.data.representatives.length} representatives for ${normalizedPostalCode}`, 'success', 3000); } } else { showError(data.message || 'Unable to find representatives for this postal code.'); } } catch (error) { hideLoading(); console.error('Error fetching representatives:', error); showError('An error occurred while looking up representatives. Please try again.'); } } // Utility functions for loading and error states function showLoading() { const loading = document.getElementById('loading'); if (loading) loading.style.display = 'block'; } function hideLoading() { const loading = document.getElementById('loading'); if (loading) loading.style.display = 'none'; } function showError(message) { const errorDiv = document.getElementById('error-message'); if (errorDiv) { errorDiv.textContent = message; errorDiv.style.display = 'block'; } } function hideError() { const errorDiv = document.getElementById('error-message'); if (errorDiv) { errorDiv.style.display = 'none'; } } // Initialize form handlers function initializePostalForm() { const postalForm = document.getElementById('postal-form'); const refreshBtn = document.getElementById('refresh-btn'); // Handle postal code form submission if (postalForm) { postalForm.addEventListener('submit', (event) => { event.preventDefault(); const postalCode = document.getElementById('postal-code').value.trim(); if (postalCode) { handlePostalCodeSubmission(postalCode); } }); } // Handle refresh button if (refreshBtn) { refreshBtn.addEventListener('click', () => { const postalCode = refreshBtn.dataset.postalCode || document.getElementById('postal-code').value.trim(); if (postalCode) { handlePostalCodeSubmission(postalCode); } }); } } // Initialize everything when DOM is loaded document.addEventListener('DOMContentLoaded', () => { initializeRepresentativesMap(); initializePostalForm(); }); // Global function for opening email modal from map popups window.openEmailModal = function(email, name, level) { if (window.emailComposer) { window.emailComposer.openModal({ email: email, name: name, level: level }, currentPostalCode); } }; // Export functions for use by other modules window.RepresentativesMap = { displayRepresentativeOffices, initializeRepresentativesMap, clearRepresentativeMarkers, handlePostalCodeSubmission };