freealberta/influence/app/public/js/representatives-display.js

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();
});