diff --git a/.gitignore b/.gitignore index f65ac59..5d6cbb7 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/app/routes/api.py b/app/routes/api.py index 3168b20..51a2346 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -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, diff --git a/app/static/js/contacts.js b/app/static/js/contacts.js index 9440fa7..9b15977 100644 --- a/app/static/js/contacts.js +++ b/app/static/js/contacts.js @@ -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} 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} 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} 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} 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);