// Representatives Display Module class RepresentativesDisplay { constructor() { this.container = document.getElementById('representatives-container'); } displayRepresentatives(representatives) { if (!representatives || representatives.length === 0) { this.container.innerHTML = `

No Representatives Found

No representatives were found for this postal code. This might be due to:

Please try again later or verify your postal code.

`; return; } // Group representatives by level/type const grouped = this.groupRepresentatives(representatives); let html = ''; // Order of importance for display const displayOrder = [ 'Federal', 'Provincial', 'Municipal', 'School Board', 'Other' ]; displayOrder.forEach(level => { if (grouped[level] && grouped[level].length > 0) { html += this.renderRepresentativeCategory(level, grouped[level]); } }); this.container.innerHTML = html; this.attachEventListeners(); } groupRepresentatives(representatives) { const groups = { 'Federal': [], 'Provincial': [], 'Municipal': [], 'School Board': [], 'Other': [] }; representatives.forEach(rep => { const setName = rep.representative_set_name || ''; const office = rep.elected_office || ''; if (setName.toLowerCase().includes('house of commons') || setName.toLowerCase().includes('federal') || office.toLowerCase().includes('member of parliament') || office.toLowerCase().includes('mp')) { groups['Federal'].push(rep); } else if (setName.toLowerCase().includes('provincial') || setName.toLowerCase().includes('legislative assembly') || setName.toLowerCase().includes('mla') || office.toLowerCase().includes('mla')) { groups['Provincial'].push(rep); } else if (setName.toLowerCase().includes('municipal') || setName.toLowerCase().includes('city council') || setName.toLowerCase().includes('mayor') || office.toLowerCase().includes('councillor') || office.toLowerCase().includes('mayor')) { groups['Municipal'].push(rep); } else if (setName.toLowerCase().includes('school') || office.toLowerCase().includes('school') || office.toLowerCase().includes('trustee')) { groups['School Board'].push(rep); } else { groups['Other'].push(rep); } }); return groups; } renderRepresentativeCategory(categoryName, representatives) { const cards = representatives.map(rep => this.renderRepresentativeCard(rep)).join(''); return `

${categoryName} Representatives

${cards}
`; } renderRepresentativeCard(rep) { const name = rep.name || 'Name not available'; const email = rep.email || null; const office = rep.elected_office || 'Office not specified'; const district = rep.district_name || 'District not specified'; const party = rep.party_name || 'Party not specified'; const photoUrl = rep.photo_url || null; // Extract phone numbers from offices array const phoneNumbers = this.extractPhoneNumbers(rep.offices || []); const primaryPhone = phoneNumbers.length > 0 ? phoneNumbers[0] : null; const emailButton = email ? `` : 'No email available'; // Add call button if phone number is available const callButton = primaryPhone ? `` : ''; // Add visit buttons for all available office addresses const visitButtons = this.createVisitButtons(rep.offices || [], name, office); const profileUrl = rep.url ? `πŸ‘€ View Profile` : ''; // Generate initials for fallback const initials = name.split(' ') .map(word => word.charAt(0)) .join('') .toUpperCase() .slice(0, 2); const photoElement = photoUrl ? `
${name}
` : `
${initials}
`; return `
${photoElement}

${name}

Office: ${office}

District: ${district}

${party !== 'Party not specified' ? `

Party: ${party}

` : ''} ${email ? `

Email: ${email}

` : ''} ${primaryPhone ? `

Phone: ${primaryPhone.number} ${primaryPhone.type ? `(${primaryPhone.type})` : ''}

` : ''}
${emailButton} ${callButton} ${profileUrl}
${visitButtons ? `
${visitButtons}
` : ''}
`; } extractPhoneNumbers(offices) { const phoneNumbers = []; if (Array.isArray(offices)) { // Priority order for office types (prefer local over remote) const officePriorities = ['constituency', 'district', 'local', 'regional', 'legislature']; // First, try to find offices with Alberta addresses (for MPs) const albertaOffices = offices.filter(office => { const address = office.postal || office.address || ''; return address.toLowerCase().includes('alberta') || address.toLowerCase().includes(' ab ') || address.toLowerCase().includes('edmonton') || address.toLowerCase().includes('calgary') || address.toLowerCase().includes('red deer') || address.toLowerCase().includes('lethbridge') || address.toLowerCase().includes('medicine hat'); }); // Add phone numbers from Alberta offices first if (albertaOffices.length > 0) { for (const priority of officePriorities) { const priorityOffice = albertaOffices.find(office => office.type === priority && office.tel ); if (priorityOffice) { phoneNumbers.push({ number: priorityOffice.tel, type: priorityOffice.type || 'office' }); } } // Add any remaining Alberta office phone numbers albertaOffices.forEach(office => { if (office.tel && !phoneNumbers.find(p => p.number === office.tel)) { phoneNumbers.push({ number: office.tel, type: office.type || 'office' }); } }); } // Then add phone numbers from other offices by priority for (const priority of officePriorities) { const priorityOffice = offices.find(office => office.type === priority && office.tel && !phoneNumbers.find(p => p.number === office.tel) ); if (priorityOffice) { phoneNumbers.push({ number: priorityOffice.tel, type: priorityOffice.type || 'office' }); } } // Finally, add any remaining phone numbers offices.forEach(office => { if (office.tel && !phoneNumbers.find(p => p.number === office.tel)) { phoneNumbers.push({ number: office.tel, type: office.type || 'office' }); } }); } return phoneNumbers; } createVisitButtons(offices, repName, repOffice) { if (!Array.isArray(offices) || offices.length === 0) { return ''; } const validOffices = offices.filter(office => office.postal || office.address); if (validOffices.length === 0) { return ''; } // Sort offices by priority (local first) const sortedOffices = validOffices.sort((a, b) => { const aAddress = (a.postal || a.address || '').toLowerCase(); const bAddress = (b.postal || b.address || '').toLowerCase(); // Check if address is in Alberta const aIsAlberta = aAddress.includes('alberta') || aAddress.includes(' ab ') || aAddress.includes('edmonton') || aAddress.includes('calgary'); const bIsAlberta = bAddress.includes('alberta') || bAddress.includes(' ab ') || bAddress.includes('edmonton') || bAddress.includes('calgary'); if (aIsAlberta && !bIsAlberta) return -1; if (!aIsAlberta && bIsAlberta) return 1; // If both are Alberta or both are not, prefer constituency over legislature const typePriority = { 'constituency': 1, 'district': 2, 'local': 3, 'regional': 4, 'legislature': 5 }; const aPriority = typePriority[a.type] || 6; const bPriority = typePriority[b.type] || 6; return aPriority - bPriority; }); return sortedOffices.map(office => { const address = office.postal || office.address; const officeType = this.getOfficeTypeLabel(office.type, address); const isLocal = this.isLocalAddress(address); return ` `; }).join(''); } getOfficeTypeLabel(type, address) { if (!type) { // Try to determine type from address const addr = address.toLowerCase(); if (addr.includes('ottawa') || addr.includes('parliament') || addr.includes('house of commons')) { return 'Ottawa'; } else if (addr.includes('legislature') || addr.includes('provincial')) { return 'Legislature'; } else if (addr.includes('city hall')) { return 'City Hall'; } return 'Office'; } const typeLabels = { 'constituency': 'Local Office', 'district': 'District Office', 'local': 'Local Office', 'regional': 'Regional Office', 'legislature': 'Legislature' }; return typeLabels[type] || type.charAt(0).toUpperCase() + type.slice(1); } isLocalAddress(address) { const addr = address.toLowerCase(); return addr.includes('alberta') || addr.includes(' ab ') || addr.includes('edmonton') || addr.includes('calgary') || addr.includes('red deer') || addr.includes('lethbridge') || addr.includes('medicine hat'); } getShortAddress(address) { // Extract city and province/state for short display const parts = address.split(','); if (parts.length >= 2) { const city = parts[parts.length - 2].trim(); const province = parts[parts.length - 1].trim(); return `${city}, ${province}`; } // Fallback: just show first part return parts[0].trim(); } attachEventListeners() { // Add event listeners for compose email buttons const composeButtons = this.container.querySelectorAll('.compose-email'); composeButtons.forEach(button => { button.addEventListener('click', (e) => { const email = e.target.dataset.email; const name = e.target.dataset.name; const office = e.target.dataset.office; const district = e.target.dataset.district; window.emailComposer.openModal({ email, name, office, district }); }); }); // Add event listeners for call buttons const callButtons = this.container.querySelectorAll('.call-representative'); callButtons.forEach(button => { button.addEventListener('click', (e) => { const phone = e.target.dataset.phone; const name = e.target.dataset.name; const office = e.target.dataset.office; const officeType = e.target.dataset.officeType; this.handleCallClick(phone, name, office, officeType); }); }); // Add event listeners for visit buttons const visitButtons = this.container.querySelectorAll('.visit-office'); visitButtons.forEach(button => { button.addEventListener('click', (e) => { e.preventDefault(); // Use currentTarget to ensure we get the button, not nested elements const address = button.dataset.address; const name = button.dataset.name; const office = button.dataset.office; this.handleVisitClick(address, name, office); }); }); // Add event listeners for image error handling const repImages = this.container.querySelectorAll('.rep-photo img'); repImages.forEach(img => { img.addEventListener('error', (e) => { // Hide the image and show the fallback e.target.style.display = 'none'; const fallback = e.target.nextElementSibling; if (fallback && fallback.classList.contains('rep-photo-fallback')) { fallback.style.display = 'flex'; } }); }); } handleCallClick(phone, name, office, officeType) { // Clean the phone number for tel: link (remove spaces, dashes, parentheses) const cleanPhone = phone.replace(/[\s\-\(\)]/g, ''); // Create tel: link const telLink = `tel:${cleanPhone}`; // Show confirmation dialog with formatted information const officeInfo = officeType ? ` (${officeType} office)` : ''; const message = `Call ${name}${officeInfo}?\n\nPhone: ${phone}`; if (confirm(message)) { // Attempt to initiate the call window.location.href = telLink; } } handleVisitClick(address, name, office) { // Clean and format the address for URL encoding const cleanAddress = address.replace(/\n/g, ', ').trim(); // Show confirmation dialog const message = `Open directions to ${name}'s office?\n\nAddress: ${cleanAddress}`; if (confirm(message)) { // Create maps URL - this will work on most platforms // For mobile devices, it will open the default maps app // For desktop, it will open Google Maps in browser const encodedAddress = encodeURIComponent(cleanAddress); // Try different map services based on user agent const userAgent = navigator.userAgent.toLowerCase(); let mapsUrl; if (userAgent.includes('iphone') || userAgent.includes('ipad')) { // iOS - use Apple Maps mapsUrl = `maps://maps.apple.com/?q=${encodedAddress}`; // Fallback to Google Maps if Apple Maps doesn't work setTimeout(() => { window.open(`https://www.google.com/maps/search/${encodedAddress}`, '_blank'); }, 500); window.location.href = mapsUrl; } else if (userAgent.includes('android')) { // Android - use Google Maps app if available mapsUrl = `geo:0,0?q=${encodedAddress}`; // Fallback to Google Maps web setTimeout(() => { window.open(`https://www.google.com/maps/search/${encodedAddress}`, '_blank'); }, 500); window.location.href = mapsUrl; } else { // Desktop or other - open Google Maps in new tab mapsUrl = `https://www.google.com/maps/search/${encodedAddress}`; window.open(mapsUrl, '_blank'); } } } } // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.representativesDisplay = new RepresentativesDisplay(); });