255 lines
9.5 KiB
JavaScript
255 lines
9.5 KiB
JavaScript
// Representatives Display Module
|
|
class RepresentativesDisplay {
|
|
constructor() {
|
|
this.container = document.getElementById('representatives-container');
|
|
}
|
|
|
|
displayRepresentatives(representatives) {
|
|
if (!representatives || representatives.length === 0) {
|
|
this.container.innerHTML = `
|
|
<div class="rep-category">
|
|
<h3>No Representatives Found</h3>
|
|
<p>No representatives were found for this postal code. This might be due to:</p>
|
|
<ul>
|
|
<li>The postal code is not in our database</li>
|
|
<li>Temporary API issues</li>
|
|
<li>The postal code is not currently assigned to electoral districts</li>
|
|
</ul>
|
|
<p>Please try again later or verify your postal code.</p>
|
|
</div>
|
|
`;
|
|
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 `
|
|
<div class="rep-category">
|
|
<h3>${categoryName} Representatives</h3>
|
|
<div class="rep-cards">
|
|
${cards}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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 ?
|
|
`<button class="btn btn-primary compose-email"
|
|
data-email="${email}"
|
|
data-name="${name}"
|
|
data-office="${office}"
|
|
data-district="${district}">
|
|
Send Email
|
|
</button>` :
|
|
'<span class="text-muted">No email available</span>';
|
|
|
|
// Add call button if phone number is available
|
|
const callButton = primaryPhone ?
|
|
`<button class="btn btn-success call-representative"
|
|
data-phone="${primaryPhone.number}"
|
|
data-office-type="${primaryPhone.type}"
|
|
data-name="${name}"
|
|
data-office="${office}">
|
|
📞 Call
|
|
</button>` : '';
|
|
|
|
const profileUrl = rep.url ?
|
|
`<a href="${rep.url}" target="_blank" class="btn btn-secondary">View Profile</a>` : '';
|
|
|
|
// Generate initials for fallback
|
|
const initials = name.split(' ')
|
|
.map(word => word.charAt(0))
|
|
.join('')
|
|
.toUpperCase()
|
|
.slice(0, 2);
|
|
|
|
const photoElement = photoUrl ?
|
|
`<div class="rep-photo">
|
|
<img src="${photoUrl}"
|
|
alt="${name}"
|
|
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';"
|
|
loading="lazy">
|
|
<div class="rep-photo-fallback" style="display: none;">
|
|
${initials}
|
|
</div>
|
|
</div>` :
|
|
`<div class="rep-photo">
|
|
<div class="rep-photo-fallback">
|
|
${initials}
|
|
</div>
|
|
</div>`;
|
|
|
|
return `
|
|
<div class="rep-card">
|
|
${photoElement}
|
|
<div class="rep-content">
|
|
<div class="rep-header">
|
|
<h4>${name}</h4>
|
|
</div>
|
|
<div class="rep-info">
|
|
<p><strong>Office:</strong> ${office}</p>
|
|
<p><strong>District:</strong> ${district}</p>
|
|
${party !== 'Party not specified' ? `<p><strong>Party:</strong> ${party}</p>` : ''}
|
|
${email ? `<p><strong>Email:</strong> ${email}</p>` : ''}
|
|
${primaryPhone ? `<p><strong>Phone:</strong> ${primaryPhone.number} ${primaryPhone.type ? `(${primaryPhone.type})` : ''}</p>` : ''}
|
|
</div>
|
|
<div class="rep-actions">
|
|
${emailButton}
|
|
${callButton}
|
|
${profileUrl}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
extractPhoneNumbers(offices) {
|
|
const phoneNumbers = [];
|
|
|
|
if (Array.isArray(offices)) {
|
|
offices.forEach(office => {
|
|
if (office.tel) {
|
|
phoneNumbers.push({
|
|
number: office.tel,
|
|
type: office.type || 'office'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
return phoneNumbers;
|
|
}
|
|
|
|
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);
|
|
});
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.representativesDisplay = new RepresentativesDisplay();
|
|
}); |