mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-05-03 03:52:38 +02:00
feat: Add persistent type filter for pending contacts
- Add query parameter 'types' to GET /api/contacts/pending endpoint - Save user's type filter selection to localStorage (pendingContactsTypeFilter) - Restore filter on page reload (Pending Contacts page) - Contact Management badge shows count based on saved filter - Default filter: CLI only (type=1) Frontend changes: - Add localStorage functions (save/load/set checkboxes) - Modify loadPendingContacts() to use types from checkboxes - Modify loadContactCounts() to use filter from localStorage - Checkbox changes trigger save to localStorage + API reload Backend changes: - Add 'types' query parameter to GET /api/contacts/pending - Filter pending contacts by type before returning - Validate types parameter (must be 1-4) This allows users to customize which contact types they want to see in pending contacts list, and the selection persists across sessions. The same filter is used consistently across all pages (Pending Contacts page and Contact Management page). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
12
.gitignore
vendored
12
.gitignore
vendored
@@ -87,26 +87,16 @@ Thumbs.db
|
||||
# Miscellaneous
|
||||
# ============================================
|
||||
.claude/
|
||||
technotes/prompt-dms.md
|
||||
technotes/channels.md
|
||||
technotes/limity.md
|
||||
technotes/smart-refresh-feature.md
|
||||
technotes/
|
||||
docs/Pomoc w zwiększeniu zasięgu.pdf
|
||||
PRD.md
|
||||
.gitignore
|
||||
CLAUDE_CODE_PROMPT.md
|
||||
technotes/advert-vs-floodadv.md
|
||||
technotes/conversation-with-user-Xahgmah.md
|
||||
technotes/issue_diagnosis_missing_recv.md
|
||||
technotes/reddit_response.txt
|
||||
technotes/troubleshooting_guide_for_user.md
|
||||
technotes/reddit_response_final.txt
|
||||
docs/Mesh Core – Lista Zakupowa (repeater Dachowy).pdf
|
||||
docs/contact-management-next-step.md
|
||||
docs/response-to-xahgmah.md
|
||||
docs/UI-Contact-Management-MVP-v1.md
|
||||
docs/UI-Contact-Management-MVP-v2.md
|
||||
docs/TEST-PLAN-Contact-Management-v2.md
|
||||
technotes/API-Diagnostic-Commands-private.md
|
||||
docs/github-discussion-*.md
|
||||
docs/github-response-spaces-in-device-name.md
|
||||
|
||||
@@ -1606,6 +1606,12 @@ def get_pending_contacts_api():
|
||||
"""
|
||||
Get list of contacts awaiting manual approval.
|
||||
|
||||
Query parameters:
|
||||
types (list[int]): Filter by contact types (optional)
|
||||
Example: ?types=1&types=2 (CLI and REP only)
|
||||
Valid values: 1 (CLI), 2 (REP), 3 (ROOM), 4 (SENS)
|
||||
If not provided, returns all pending contacts
|
||||
|
||||
Returns:
|
||||
JSON with pending contacts list with enriched contact data:
|
||||
{
|
||||
@@ -1631,9 +1637,26 @@ def get_pending_contacts_api():
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# Get type filter from query params
|
||||
types_param = request.args.getlist('types', type=int)
|
||||
|
||||
# Validate types (must be 1-4)
|
||||
if types_param:
|
||||
invalid_types = [t for t in types_param if t not in [1, 2, 3, 4]]
|
||||
if invalid_types:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Invalid types: {invalid_types}. Valid types: 1 (CLI), 2 (REP), 3 (ROOM), 4 (SENS)',
|
||||
'pending': []
|
||||
}), 400
|
||||
|
||||
success, pending, error = cli.get_pending_contacts()
|
||||
|
||||
if success:
|
||||
# Filter by types if specified
|
||||
if types_param:
|
||||
pending = [contact for contact in pending if contact.get('type') in types_param]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'pending': pending,
|
||||
|
||||
@@ -136,8 +136,15 @@ function attachManageEventListeners() {
|
||||
|
||||
async function loadContactCounts() {
|
||||
try {
|
||||
// Fetch pending count
|
||||
const pendingResp = await fetch('/api/contacts/pending');
|
||||
// Get saved type filter from localStorage
|
||||
const savedTypes = loadPendingTypeFilter();
|
||||
|
||||
// Build query string with types parameter
|
||||
const params = new URLSearchParams();
|
||||
savedTypes.forEach(type => params.append('types', type));
|
||||
|
||||
// Fetch pending count (with type filter)
|
||||
const pendingResp = await fetch(`/api/contacts/pending?${params.toString()}`);
|
||||
const pendingData = await pendingResp.json();
|
||||
|
||||
const pendingBadge = document.getElementById('pendingBadge');
|
||||
@@ -378,7 +385,11 @@ async function handleCleanupConfirm() {
|
||||
function initPendingPage() {
|
||||
console.log('Initializing Pending page...');
|
||||
|
||||
// Load pending contacts
|
||||
// Load saved type filter and set checkboxes
|
||||
const savedTypes = loadPendingTypeFilter();
|
||||
setTypeFilterCheckboxes(savedTypes);
|
||||
|
||||
// Load pending contacts (will use filter from checkboxes)
|
||||
loadPendingContacts();
|
||||
|
||||
// Attach event listeners for pending page
|
||||
@@ -402,12 +413,17 @@ function attachPendingEventListeners() {
|
||||
});
|
||||
}
|
||||
|
||||
// Type filter checkboxes - filter on change
|
||||
// Type filter checkboxes - save to localStorage and reload on change
|
||||
['typeFilterCLI', 'typeFilterREP', 'typeFilterROOM', 'typeFilterSENS'].forEach(id => {
|
||||
const checkbox = document.getElementById(id);
|
||||
if (checkbox) {
|
||||
checkbox.addEventListener('change', () => {
|
||||
applyPendingFilters();
|
||||
// Save selected types to localStorage
|
||||
const selectedTypes = getSelectedTypes();
|
||||
savePendingTypeFilter(selectedTypes);
|
||||
|
||||
// Reload contacts from API with new filter
|
||||
loadPendingContacts();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -571,6 +587,82 @@ function updateApprovalUI(enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Pending Type Filter (localStorage persistence)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Save pending contacts type filter to localStorage.
|
||||
* This allows the filter to persist across page reloads and be used
|
||||
* in different parts of the app (Pending page, Contact Management badge, etc.)
|
||||
*
|
||||
* @param {Array<number>} types - Array of contact types to filter (1=CLI, 2=REP, 3=ROOM, 4=SENS)
|
||||
*/
|
||||
function savePendingTypeFilter(types) {
|
||||
try {
|
||||
localStorage.setItem('pendingContactsTypeFilter', JSON.stringify(types));
|
||||
console.log('Pending type filter saved:', types);
|
||||
} catch (e) {
|
||||
console.error('Failed to save pending type filter to localStorage:', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load pending contacts type filter from localStorage.
|
||||
*
|
||||
* @returns {Array<number>} Array of contact types (default: [1] for CLI only)
|
||||
*/
|
||||
function loadPendingTypeFilter() {
|
||||
try {
|
||||
const stored = localStorage.getItem('pendingContactsTypeFilter');
|
||||
if (stored) {
|
||||
const types = JSON.parse(stored);
|
||||
// Validate: must be array of valid types
|
||||
if (Array.isArray(types) && types.every(t => [1, 2, 3, 4].includes(t))) {
|
||||
console.log('Pending type filter loaded:', types);
|
||||
return types;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load pending type filter from localStorage:', e);
|
||||
}
|
||||
// Default: CLI only (most common use case)
|
||||
return [1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set type filter checkboxes based on types array.
|
||||
* @param {Array<number>} types - Array of contact types (1=CLI, 2=REP, 3=ROOM, 4=SENS)
|
||||
*/
|
||||
function setTypeFilterCheckboxes(types) {
|
||||
const checkboxes = {
|
||||
1: document.getElementById('typeFilterCLI'),
|
||||
2: document.getElementById('typeFilterREP'),
|
||||
3: document.getElementById('typeFilterROOM'),
|
||||
4: document.getElementById('typeFilterSENS')
|
||||
};
|
||||
|
||||
// Set checkboxes based on types array
|
||||
for (const [type, checkbox] of Object.entries(checkboxes)) {
|
||||
if (checkbox) {
|
||||
checkbox.checked = types.includes(parseInt(type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get currently selected contact types from checkboxes.
|
||||
* @returns {Array<number>} Array of selected types
|
||||
*/
|
||||
function getSelectedTypes() {
|
||||
const types = [];
|
||||
if (document.getElementById('typeFilterCLI')?.checked) types.push(1);
|
||||
if (document.getElementById('typeFilterREP')?.checked) types.push(2);
|
||||
if (document.getElementById('typeFilterROOM')?.checked) types.push(3);
|
||||
if (document.getElementById('typeFilterSENS')?.checked) types.push(4);
|
||||
return types;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Pending Contacts Management
|
||||
// =============================================================================
|
||||
@@ -590,7 +682,14 @@ async function loadPendingContacts() {
|
||||
if (countBadge) countBadge.style.display = 'none';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/contacts/pending');
|
||||
// Get selected types from checkboxes
|
||||
const selectedTypes = getSelectedTypes();
|
||||
|
||||
// Build query string with types parameter
|
||||
const params = new URLSearchParams();
|
||||
selectedTypes.forEach(type => params.append('types', type));
|
||||
|
||||
const response = await fetch(`/api/contacts/pending?${params.toString()}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (loadingEl) loadingEl.style.display = 'none';
|
||||
@@ -835,20 +934,8 @@ function applyPendingFilters() {
|
||||
const searchInput = document.getElementById('pendingSearchInput');
|
||||
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
|
||||
|
||||
// Get selected types
|
||||
const selectedTypes = [];
|
||||
if (document.getElementById('typeFilterCLI')?.checked) selectedTypes.push('CLI');
|
||||
if (document.getElementById('typeFilterREP')?.checked) selectedTypes.push('REP');
|
||||
if (document.getElementById('typeFilterROOM')?.checked) selectedTypes.push('ROOM');
|
||||
if (document.getElementById('typeFilterSENS')?.checked) selectedTypes.push('SENS');
|
||||
|
||||
// Filter contacts
|
||||
// Apply search filter locally (type filter already applied by API)
|
||||
filteredPendingContacts = pendingContacts.filter(contact => {
|
||||
// Type filter
|
||||
if (selectedTypes.length > 0 && !selectedTypes.includes(contact.type_label)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Search filter (name or public_key_prefix)
|
||||
if (searchTerm) {
|
||||
const nameMatch = contact.name.toLowerCase().includes(searchTerm);
|
||||
|
||||
Reference in New Issue
Block a user