diff --git a/ucp/assets/ucp.js b/ucp/assets/ucp.js index d384766..a8b8ba8 100644 --- a/ucp/assets/ucp.js +++ b/ucp/assets/ucp.js @@ -9,6 +9,7 @@ let ucpState = { selectedVmExt: '', vmFolderFilter: 'all', phonebook: [], + selectedPbGroup: '__all__', activeTab: 'extensions', }; @@ -755,12 +756,73 @@ async function loadPhonebook() { } } +function renderPhonebookGroupTabs(allContacts) { + const tabsEl = document.getElementById('ucp-pb-group-tabs'); + if (!tabsEl) return; + + const counts = {}; + for (const c of allContacts) { + const grp = String(c.group_name || '').trim() || 'Général'; + counts[grp] = (counts[grp] || 0) + 1; + } + + const groups = Object.keys(counts).sort((a, b) => { + if (a === 'Général') return -1; + if (b === 'Général') return 1; + return a.localeCompare(b, 'fr', { sensitivity: 'base' }); + }); + + if (ucpState.selectedPbGroup !== '__all__' && !counts[ucpState.selectedPbGroup]) { + ucpState.selectedPbGroup = '__all__'; + } + + if (groups.length <= 1 && allContacts.length === 0) { + tabsEl.innerHTML = ''; + return; + } + + const renderPill = (key, label, count) => { + const isActive = ucpState.selectedPbGroup === key; + const btnCls = isActive + ? 'bg-indigo-600 text-white border-indigo-600 shadow-sm' + : 'bg-white hover:bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300'; + const badgeCls = isActive + ? 'bg-white/20 text-white' + : 'bg-slate-100 text-slate-600'; + return ` + + `; + }; + + tabsEl.innerHTML = [ + renderPill('__all__', 'Tous', allContacts.length), + ...groups.map(g => renderPill(g, g, counts[g])), + ].join(''); + + tabsEl.querySelectorAll('.ucp-pb-group-btn').forEach(btn => { + btn.addEventListener('click', () => { + ucpState.selectedPbGroup = btn.getAttribute('data-pb-group') || '__all__'; + renderPhonebookGrid(); + }); + }); +} + function renderPhonebookGrid() { const grid = document.getElementById('ucp-pb-grid'); if (!grid) return; + const allContacts = ucpState.phonebook || []; + renderPhonebookGroupTabs(allContacts); + + const activeGroup = ucpState.selectedPbGroup || '__all__'; const q = (document.getElementById('ucp-pb-search')?.value || '').trim().toLowerCase(); - const list = (ucpState.phonebook || []).filter(c => { + const list = allContacts.filter(c => { + const grp = String(c.group_name || '').trim() || 'Général'; + if (activeGroup !== '__all__' && grp !== activeGroup) return false; if (!q) return true; return [c.display_name, c.internal_number, c.mobile_number, c.other_number, c.email, c.group_name] .some(v => String(v || '').toLowerCase().includes(q)); diff --git a/ucp/index.php b/ucp/index.php index d01281a..01af38b 100644 --- a/ucp/index.php +++ b/ucp/index.php @@ -388,6 +388,8 @@ $csrfToken = UcpAuth::getCsrfToken(); class="w-full sm:w-72 px-3.5 py-2 rounded-xl bg-white border border-slate-300 text-xs text-slate-900 focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500 focus:outline-none"> +
+
Chargement de l'annuaire…
diff --git a/version.json b/version.json index ea88a57..a5f0fa5 100644 --- a/version.json +++ b/version.json @@ -1,9 +1,10 @@ { - "version": "2.6.2", + "version": "2.6.3", "codename": "Pulsar UCP", "release_date": "2026-11-12", "min_php_version": "8.2", "changelog": [ + "UI v2.6.3 : onglets dynamiques par groupe (Tous, Général, Paris, New-York...) avec compteurs dans l'Annuaire UCP", "Correctif v2.6.2 : sélection stricte de GroundWire (NAT si renseigné, sinon VIP HA si cluster actif, sinon IP locale + port d'écoute local)", "UI v2.6.1 : suppression de l'icône smartphone sur les éléments GroundWire et refonte des boutons d'action du tableau Postes (Redémarrer / Modifier / Supprimer)", "Nouveauté v2.6.0 : provisionnement dynamique XML Acrobits GroundWire par poste (option Admin « GroundWire XML », deep link mobile provlinkbs:, téléchargement XML signé HMAC sans session, liens App Store / Google Play dans l'UCP)",