mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-07-06 09:51:14 +02:00
feat(contacts): compact pending filters with batch ignore
- Remove redundant 'Contact Types:' label - Move type badges above search input - Place search, Approve, and Ignore buttons in single responsive row - Add tooltip (i) on Filters header with usage hint - Add batch Ignore button to ignore all filtered pending contacts - Remove duplicate filtered count badge from Approve button Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+121
-6
@@ -794,6 +794,14 @@ function attachPendingEventListeners() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore Filtered button - show batch ignore modal
|
||||||
|
const ignoreFilteredBtn = document.getElementById('ignoreFilteredBtn');
|
||||||
|
if (ignoreFilteredBtn) {
|
||||||
|
ignoreFilteredBtn.addEventListener('click', () => {
|
||||||
|
showBatchIgnoreModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Confirm Batch Approval button - approve all filtered contacts
|
// Confirm Batch Approval button - approve all filtered contacts
|
||||||
const confirmBatchBtn = document.getElementById('confirmBatchApprovalBtn');
|
const confirmBatchBtn = document.getElementById('confirmBatchApprovalBtn');
|
||||||
if (confirmBatchBtn) {
|
if (confirmBatchBtn) {
|
||||||
@@ -801,6 +809,19 @@ function attachPendingEventListeners() {
|
|||||||
batchApproveContacts();
|
batchApproveContacts();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Confirm Batch Ignore button - ignore all filtered contacts
|
||||||
|
const confirmBatchIgnoreBtn = document.getElementById('confirmBatchIgnoreBtn');
|
||||||
|
if (confirmBatchIgnoreBtn) {
|
||||||
|
confirmBatchIgnoreBtn.addEventListener('click', () => {
|
||||||
|
batchIgnoreContacts();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Bootstrap tooltips
|
||||||
|
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
|
||||||
|
new bootstrap.Tooltip(el);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -1500,12 +1521,6 @@ function applyPendingFilters() {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update filtered count badge
|
|
||||||
const countBadge = document.getElementById('filteredCountBadge');
|
|
||||||
if (countBadge) {
|
|
||||||
countBadge.textContent = filteredPendingContacts.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render filtered list
|
// Render filtered list
|
||||||
renderPendingList(filteredPendingContacts);
|
renderPendingList(filteredPendingContacts);
|
||||||
}
|
}
|
||||||
@@ -1631,6 +1646,106 @@ async function batchApproveContacts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showBatchIgnoreModal() {
|
||||||
|
if (filteredPendingContacts.length === 0) {
|
||||||
|
showToast('No contacts to ignore', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('batchIgnoreModal'));
|
||||||
|
const countEl = document.getElementById('batchIgnoreCount');
|
||||||
|
const listEl = document.getElementById('batchIgnoreList');
|
||||||
|
|
||||||
|
if (countEl) countEl.textContent = filteredPendingContacts.length;
|
||||||
|
|
||||||
|
if (listEl) {
|
||||||
|
listEl.innerHTML = '';
|
||||||
|
filteredPendingContacts.forEach(contact => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'list-group-item d-flex justify-content-between align-items-center';
|
||||||
|
|
||||||
|
const nameSpan = document.createElement('span');
|
||||||
|
nameSpan.textContent = contact.name;
|
||||||
|
|
||||||
|
const typeBadge = document.createElement('span');
|
||||||
|
typeBadge.className = 'badge';
|
||||||
|
typeBadge.textContent = contact.type_label;
|
||||||
|
|
||||||
|
switch (contact.type_label) {
|
||||||
|
case 'CLI': typeBadge.classList.add('bg-primary'); break;
|
||||||
|
case 'REP': typeBadge.classList.add('bg-success'); break;
|
||||||
|
case 'ROOM': typeBadge.classList.add('bg-info'); break;
|
||||||
|
case 'SENS': typeBadge.classList.add('bg-warning', 'text-dark'); break;
|
||||||
|
default: typeBadge.classList.add('bg-secondary');
|
||||||
|
}
|
||||||
|
|
||||||
|
item.appendChild(nameSpan);
|
||||||
|
item.appendChild(typeBadge);
|
||||||
|
listEl.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function batchIgnoreContacts() {
|
||||||
|
const modal = bootstrap.Modal.getInstance(document.getElementById('batchIgnoreModal'));
|
||||||
|
const confirmBtn = document.getElementById('confirmBatchIgnoreBtn');
|
||||||
|
|
||||||
|
if (confirmBtn) confirmBtn.disabled = true;
|
||||||
|
|
||||||
|
let successCount = 0;
|
||||||
|
let failedCount = 0;
|
||||||
|
const failures = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < filteredPendingContacts.length; i++) {
|
||||||
|
const contact = filteredPendingContacts[i];
|
||||||
|
|
||||||
|
if (confirmBtn) {
|
||||||
|
confirmBtn.innerHTML = `<i class="bi bi-hourglass-split"></i> Ignoring ${i + 1}/${filteredPendingContacts.length}...`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/contacts/${encodeURIComponent(contact.public_key)}/ignore`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ ignored: true })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
successCount++;
|
||||||
|
} else {
|
||||||
|
failedCount++;
|
||||||
|
failures.push({ name: contact.name, error: data.error });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
failedCount++;
|
||||||
|
failures.push({ name: contact.name, error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modal) modal.hide();
|
||||||
|
|
||||||
|
if (successCount > 0 && failedCount === 0) {
|
||||||
|
showToast(`Successfully ignored ${successCount} contact${successCount !== 1 ? 's' : ''}`, 'info');
|
||||||
|
} else if (successCount > 0 && failedCount > 0) {
|
||||||
|
showToast(`Ignored ${successCount}, failed ${failedCount}. Check console for details.`, 'warning');
|
||||||
|
console.error('Failed ignores:', failures);
|
||||||
|
} else {
|
||||||
|
showToast(`Failed to ignore contacts. Check console for details.`, 'danger');
|
||||||
|
console.error('Failed ignores:', failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPendingContacts();
|
||||||
|
|
||||||
|
if (confirmBtn) {
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
confirmBtn.innerHTML = '<i class="bi bi-eye-slash"></i> Ignore All';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Toast Notifications
|
// Toast Notifications
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
@@ -26,44 +26,36 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-3">
|
||||||
<h6 class="mb-3"><i class="bi bi-funnel"></i> Filters</h6>
|
<h6 class="mb-2">
|
||||||
|
<i class="bi bi-funnel"></i> Filters
|
||||||
<!-- Name Search -->
|
<i class="bi bi-info-circle text-muted ms-1" role="button" tabindex="0"
|
||||||
<div class="mb-3">
|
data-bs-toggle="tooltip" data-bs-placement="right"
|
||||||
<input type="text" class="form-control" id="pendingSearchInput"
|
title="Filter contacts by type or name, then approve or ignore them in bulk."></i>
|
||||||
placeholder="Search by name or public key...">
|
</h6>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Type Filter Badges -->
|
<!-- Type Filter Badges -->
|
||||||
<div class="mb-3">
|
<div class="d-flex flex-wrap gap-2 mb-2">
|
||||||
<label class="form-label small text-muted">Contact Types:</label>
|
<span class="type-filter-badge active" data-type="CLI" id="typeFilterCLI">CLI</span>
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<span class="type-filter-badge" data-type="REP" id="typeFilterREP">REP</span>
|
||||||
<span class="type-filter-badge active" data-type="CLI" id="typeFilterCLI">CLI</span>
|
<span class="type-filter-badge" data-type="ROOM" id="typeFilterROOM">ROOM</span>
|
||||||
<span class="type-filter-badge" data-type="REP" id="typeFilterREP">REP</span>
|
<span class="type-filter-badge" data-type="SENS" id="typeFilterSENS">SENS</span>
|
||||||
<span class="type-filter-badge" data-type="ROOM" id="typeFilterROOM">ROOM</span>
|
|
||||||
<span class="type-filter-badge" data-type="SENS" id="typeFilterSENS">SENS</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Batch Approval Button -->
|
<!-- Search + Batch Action Buttons -->
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<button class="btn btn-success flex-grow-1" id="addFilteredBtn">
|
<input type="text" class="form-control form-control-sm" id="pendingSearchInput"
|
||||||
<i class="bi bi-check-circle-fill"></i> Add Filtered
|
placeholder="Search..." style="min-width: 0;">
|
||||||
<span class="badge bg-light text-dark ms-2" id="filteredCountBadge">0</span>
|
<button class="btn btn-success btn-sm flex-shrink-0" id="addFilteredBtn">
|
||||||
|
<i class="bi bi-check-circle-fill"></i> Approve
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary btn-sm flex-shrink-0" id="ignoreFilteredBtn">
|
||||||
|
<i class="bi bi-eye-slash"></i> Ignore
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Page Description -->
|
|
||||||
<div class="mb-3">
|
|
||||||
<p class="text-muted small mb-0">
|
|
||||||
<i class="bi bi-info-circle"></i>
|
|
||||||
Approve or reject contacts waiting for manual approval.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div id="pendingLoading" class="text-center py-5" style="display: none;">
|
<div id="pendingLoading" class="text-center py-5" style="display: none;">
|
||||||
<div class="spinner-border text-primary" role="status">
|
<div class="spinner-border text-primary" role="status">
|
||||||
@@ -122,4 +114,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Batch Ignore Confirmation Modal -->
|
||||||
|
<div class="modal fade" id="batchIgnoreModal" tabindex="-1" aria-labelledby="batchIgnoreModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-secondary text-white">
|
||||||
|
<h5 class="modal-title" id="batchIgnoreModalLabel">
|
||||||
|
<i class="bi bi-eye-slash"></i> Confirm Batch Ignore
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="mb-2">You are about to ignore <strong id="batchIgnoreCount">0</strong> contacts:</p>
|
||||||
|
|
||||||
|
<div class="list-group mb-3" id="batchIgnoreList" style="max-height: 300px; overflow-y: auto;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning mb-0">
|
||||||
|
<i class="bi bi-info-circle"></i> Ignored contacts will not trigger new pending requests. You can unignore them later from the Existing Contacts page.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" id="confirmBatchIgnoreBtn">
|
||||||
|
<i class="bi bi-eye-slash"></i> Ignore All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user