Dynamic search
This commit is contained in:
@@ -97,6 +97,38 @@ class MailingListApp {
|
||||
// Switch to users tab
|
||||
this.switchToUsersTab();
|
||||
});
|
||||
|
||||
// Member search functionality
|
||||
const memberSearchInput = document.getElementById('memberSearchInput');
|
||||
const memberSearchClear = document.getElementById('memberSearchClear');
|
||||
|
||||
memberSearchInput.addEventListener('input', (e) => {
|
||||
this.filterMembers(e.target.value);
|
||||
|
||||
// Show/hide clear button
|
||||
if (e.target.value.trim()) {
|
||||
memberSearchClear.style.display = 'flex';
|
||||
} else {
|
||||
memberSearchClear.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
memberSearchClear.addEventListener('click', () => {
|
||||
memberSearchInput.value = '';
|
||||
memberSearchClear.style.display = 'none';
|
||||
this.filterMembers('');
|
||||
memberSearchInput.focus();
|
||||
});
|
||||
|
||||
// Clear search when switching tabs
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
if (btn.dataset.tab !== 'members') {
|
||||
memberSearchInput.value = '';
|
||||
memberSearchClear.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -728,6 +760,149 @@ class MailingListApp {
|
||||
uiManager.setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter members by search term (name or email)
|
||||
*/
|
||||
filterMembers(searchTerm) {
|
||||
const searchInfo = document.getElementById('memberSearchInfo');
|
||||
const searchCount = document.getElementById('memberSearchCount');
|
||||
const tbody = document.getElementById('membersTableBody');
|
||||
|
||||
if (!this.members || !Array.isArray(this.members)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If no search term, show all members
|
||||
if (!searchTerm || searchTerm.trim() === '') {
|
||||
this.renderMembers();
|
||||
searchInfo.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedSearch = searchTerm.trim().toLowerCase();
|
||||
|
||||
// Filter members by name or email
|
||||
const filteredMembers = this.members.filter(member => {
|
||||
const name = (member.name || '').toLowerCase();
|
||||
const email = (member.email || '').toLowerCase();
|
||||
return name.includes(normalizedSearch) || email.includes(normalizedSearch);
|
||||
});
|
||||
|
||||
// Update search results info
|
||||
searchCount.textContent = filteredMembers.length;
|
||||
searchInfo.style.display = 'block';
|
||||
|
||||
// Render filtered results
|
||||
this.renderFilteredMembers(filteredMembers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render filtered members (similar to renderMembers but with filtered data)
|
||||
*/
|
||||
renderFilteredMembers(filteredMembers) {
|
||||
const tbody = document.getElementById('membersTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (filteredMembers.length === 0) {
|
||||
// Show no results message
|
||||
const row = tbody.insertRow();
|
||||
const cell = row.insertCell();
|
||||
cell.colSpan = 5;
|
||||
cell.className = 'no-results';
|
||||
cell.innerHTML = `
|
||||
<i class="fas fa-search"></i>
|
||||
<h3>No members found</h3>
|
||||
<p>Try adjusting your search terms or check the spelling.</p>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
filteredMembers.forEach(member => {
|
||||
const row = tbody.insertRow();
|
||||
|
||||
// Name
|
||||
const nameCell = row.insertCell();
|
||||
nameCell.textContent = member.name;
|
||||
|
||||
// Email
|
||||
const emailCell = row.insertCell();
|
||||
const emailLink = document.createElement('a');
|
||||
emailLink.href = `mailto:${member.email}`;
|
||||
emailLink.textContent = member.email;
|
||||
emailLink.style.color = 'var(--primary-color)';
|
||||
emailCell.appendChild(emailLink);
|
||||
|
||||
// Lists (show member's subscriptions)
|
||||
const listsCell = row.insertCell();
|
||||
const memberLists = [];
|
||||
this.subscriptions.forEach((members, listId) => {
|
||||
if (members.some(m => m.member_id === member.member_id)) {
|
||||
const list = this.lists.find(l => l.list_id === listId);
|
||||
if (list) {
|
||||
memberLists.push(list.list_name);
|
||||
}
|
||||
}
|
||||
});
|
||||
listsCell.textContent = memberLists.length > 0 ? memberLists.join(', ') : 'None';
|
||||
|
||||
// Status
|
||||
const statusCell = row.insertCell();
|
||||
const statusBadge = document.createElement('span');
|
||||
statusBadge.className = `status-badge ${member.active ? 'active' : 'inactive'}`;
|
||||
statusBadge.innerHTML = `<i class="fas fa-${member.active ? 'check' : 'times'}"></i> ${member.active ? 'Active' : 'Inactive'}`;
|
||||
|
||||
// Add bounce status if exists
|
||||
if (member.bounce_status && member.bounce_status !== 'clean') {
|
||||
const bounceIndicator = document.createElement('span');
|
||||
bounceIndicator.className = `bounce-badge bounce-${member.bounce_status === 'hard_bounce' ? 'hard' : 'soft'}`;
|
||||
bounceIndicator.innerHTML = `<i class="fas fa-exclamation-triangle"></i> Bounces`;
|
||||
bounceIndicator.title = `${member.bounce_count || 0} bounces - ${member.bounce_status}`;
|
||||
statusCell.appendChild(document.createElement('br'));
|
||||
statusCell.appendChild(bounceIndicator);
|
||||
}
|
||||
|
||||
statusCell.appendChild(statusBadge);
|
||||
|
||||
// Actions
|
||||
const actionsCell = row.insertCell();
|
||||
actionsCell.className = 'action-buttons';
|
||||
|
||||
// Bounces button (if member has bounce data)
|
||||
if (member.bounce_count > 0) {
|
||||
const bouncesBtn = uiManager.createActionButton('Bounces', 'exclamation-triangle', 'btn-warning', () => {
|
||||
uiManager.showBounceHistory(member);
|
||||
});
|
||||
actionsCell.appendChild(bouncesBtn);
|
||||
}
|
||||
|
||||
const subscriptionsBtn = uiManager.createActionButton('Lists', 'list', 'btn-secondary', () => {
|
||||
uiManager.showMemberSubscriptions(member);
|
||||
});
|
||||
actionsCell.appendChild(subscriptionsBtn);
|
||||
|
||||
const editBtn = uiManager.createActionButton('Edit', 'edit', 'btn-primary', () => {
|
||||
uiManager.showMemberModal(member);
|
||||
});
|
||||
editBtn.setAttribute('data-requires-write', '');
|
||||
actionsCell.appendChild(editBtn);
|
||||
|
||||
const deleteBtn = uiManager.createActionButton('Delete', 'trash', 'btn-danger', () => {
|
||||
uiManager.showConfirmation(
|
||||
`Are you sure you want to delete member "${member.name}"? This will also remove them from all mailing lists.`,
|
||||
async () => {
|
||||
await this.deleteMember(member.member_id);
|
||||
}
|
||||
);
|
||||
});
|
||||
deleteBtn.setAttribute('data-requires-write', '');
|
||||
actionsCell.appendChild(deleteBtn);
|
||||
});
|
||||
|
||||
// Update UI permissions for the filtered results
|
||||
const hasWriteAccess = this.currentUser && (this.currentUser.role === 'administrator' || this.currentUser.role === 'operator');
|
||||
this.updateUIForPermissions(hasWriteAccess);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the application when DOM is loaded
|
||||
|
||||
Reference in New Issue
Block a user