fix(phonebook,ucp): v2.6.4 affichage des postes multi-groupes dans chaque onglet de groupe

This commit is contained in:
sorcier committed 2026-11-12 02:45:13 +01:00
1 parent 8cb8f1cdf7
commit f918c929f5
3 files changed
+66 -29

No files matched your search

+50 -25
View File
@@ -48,14 +48,22 @@ class PhonebookManager
CREATE INDEX IF NOT EXISTS idx_phonebook_group ON phonebook_contacts(group_name); CREATE INDEX IF NOT EXISTS idx_phonebook_group ON phonebook_contacts(group_name);
"); ");
// 1. Récupère la cartographie des groupes d'extensions // 1. Récupère la cartographie complète des groupes d'extensions (un poste peut appartenir à plusieurs groupes)
$egRows = $db->query("SELECT id, name, members FROM extension_groups")->fetchAll(); $egRows = $db->query("SELECT id, name, members FROM extension_groups ORDER BY name COLLATE NOCASE ASC")->fetchAll();
$extToGroup = []; $extToGroups = [];
foreach ($egRows as $eg) { foreach ($egRows as $eg) {
$grpName = trim((string)($eg['name'] ?? ''));
if ($grpName === '') {
continue;
}
$members = json_decode((string)$eg['members'], true) ?: []; $members = json_decode((string)$eg['members'], true) ?: [];
foreach ($members as $extNum) { foreach ($members as $extNum) {
if (!isset($extToGroup[(string)$extNum])) { $k = (string)$extNum;
$extToGroup[(string)$extNum] = trim((string)$eg['name']); if (!isset($extToGroups[$k])) {
$extToGroups[$k] = [];
}
if (!in_array($grpName, $extToGroups[$k], true)) {
$extToGroups[$k][] = $grpName;
} }
} }
} }
@@ -80,7 +88,7 @@ class PhonebookManager
$extNum = trim((string)$ext['extension']); $extNum = trim((string)$ext['extension']);
$extName = trim((string)$ext['name']); $extName = trim((string)$ext['name']);
$email = trim((string)($ext['voicemail_email'] ?? '')); $email = trim((string)($ext['voicemail_email'] ?? ''));
$group = $extToGroup[$extNum] ?? 'Général'; $group = !empty($extToGroups[$extNum]) ? implode(', ', $extToGroups[$extNum]) : 'Général';
$activeExtIds[] = $extId; $activeExtIds[] = $extId;
$findStmt->execute([':ext_id' => $extId, ':num' => $extNum]); $findStmt->execute([':ext_id' => $extId, ':num' => $extNum]);
@@ -122,33 +130,48 @@ class PhonebookManager
{ {
self::syncExtensions($db); self::syncExtensions($db);
$params = []; $stmt = $db->query("SELECT * FROM phonebook_contacts ORDER BY display_name COLLATE NOCASE ASC");
$sql = "SELECT * FROM phonebook_contacts"; $contacts = $stmt ? $stmt->fetchAll(PDO::FETCH_ASSOC) : [];
if ($group !== null && trim($group) !== '' && strtolower(trim($group)) !== 'all' && strtolower(trim($group)) !== 'tous') { $cleanGroup = ($group !== null) ? trim($group) : '';
$cleanGroup = trim($group); $filterByGroup = ($cleanGroup !== '' && !in_array(mb_strtolower($cleanGroup), ['all', 'tous'], true));
$sql .= " WHERE LOWER(group_name) = LOWER(:grp) OR LOWER(group_name) LIKE LOWER(:grp_like) OR LOWER(group_name) IN ('tous', 'all')"; $targetLower = mb_strtolower($cleanGroup);
$params[':grp'] = $cleanGroup;
$params[':grp_like'] = "%{$cleanGroup}%";
}
$sql .= " ORDER BY display_name COLLATE NOCASE ASC"; $result = [];
$stmt = $db->prepare($sql); foreach ($contacts as $c) {
$stmt->execute($params);
$contacts = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Vérification de la présence réelle du fichier sur le disque
foreach ($contacts as &$c) {
if (!empty($c['photo_url'])) { if (!empty($c['photo_url'])) {
$filename = basename((string)$c['photo_url']); $filename = basename((string)$c['photo_url']);
if (!file_exists(self::AVATARS_DIR . '/' . $filename)) { if (!file_exists(self::AVATARS_DIR . '/' . $filename)) {
$c['photo_url'] = ''; $c['photo_url'] = '';
} }
} }
}
unset($c);
return $contacts; $rawGroups = preg_split('/\s*,\s*/', trim((string)($c['group_name'] ?? ''))) ?: [];
$groupsList = array_values(array_unique(array_filter($rawGroups, fn($g) => $g !== '')));
if (empty($groupsList)) {
$groupsList = ['Général'];
}
$c['groups'] = $groupsList;
$c['group_name'] = implode(', ', $groupsList);
if ($filterByGroup) {
$matched = false;
foreach ($groupsList as $g) {
$gLower = mb_strtolower($g);
if ($gLower === $targetLower || in_array($gLower, ['tous', 'all'], true)) {
$matched = true;
break;
}
}
if (!$matched) {
continue;
}
}
$result[] = $c;
}
return $result;
} }
/** /**
@@ -163,10 +186,12 @@ class PhonebookManager
if ($g !== '') $groups[$g] = true; if ($g !== '') $groups[$g] = true;
} }
$pbRows = $db->query("SELECT DISTINCT group_name FROM phonebook_contacts WHERE group_name != ''")->fetchAll(PDO::FETCH_COLUMN); $pbRows = $db->query("SELECT DISTINCT group_name FROM phonebook_contacts WHERE group_name != ''")->fetchAll(PDO::FETCH_COLUMN);
foreach ($pbRows as $g) { foreach ($pbRows as $raw) {
foreach (preg_split('/\s*,\s*/', trim((string)$raw)) ?: [] as $g) {
$g = trim((string)$g); $g = trim((string)$g);
if ($g !== '') $groups[$g] = true; if ($g !== '') $groups[$g] = true;
} }
}
// Ajoute Issy-Les-Moulineaux si pas encore présent // Ajoute Issy-Les-Moulineaux si pas encore présent
if (!isset($groups['Issy-Les-Moulineaux'])) { if (!isset($groups['Issy-Les-Moulineaux'])) {
+14 -3
View File
@@ -756,15 +756,27 @@ async function loadPhonebook() {
} }
} }
function getContactGroups(contact) {
if (Array.isArray(contact?.groups) && contact.groups.length > 0) {
return contact.groups.map(g => String(g || '').trim()).filter(Boolean);
}
const split = String(contact?.group_name || '')
.split(',')
.map(g => g.trim())
.filter(Boolean);
return split.length > 0 ? Array.from(new Set(split)) : ['Général'];
}
function renderPhonebookGroupTabs(allContacts) { function renderPhonebookGroupTabs(allContacts) {
const tabsEl = document.getElementById('ucp-pb-group-tabs'); const tabsEl = document.getElementById('ucp-pb-group-tabs');
if (!tabsEl) return; if (!tabsEl) return;
const counts = {}; const counts = {};
for (const c of allContacts) { for (const c of allContacts) {
const grp = String(c.group_name || '').trim() || 'Général'; for (const grp of getContactGroups(c)) {
counts[grp] = (counts[grp] || 0) + 1; counts[grp] = (counts[grp] || 0) + 1;
} }
}
const groups = Object.keys(counts).sort((a, b) => { const groups = Object.keys(counts).sort((a, b) => {
if (a === 'Général') return -1; if (a === 'Général') return -1;
@@ -821,8 +833,7 @@ function renderPhonebookGrid() {
const activeGroup = ucpState.selectedPbGroup || '__all__'; const activeGroup = ucpState.selectedPbGroup || '__all__';
const q = (document.getElementById('ucp-pb-search')?.value || '').trim().toLowerCase(); const q = (document.getElementById('ucp-pb-search')?.value || '').trim().toLowerCase();
const list = allContacts.filter(c => { const list = allContacts.filter(c => {
const grp = String(c.group_name || '').trim() || 'Général'; if (activeGroup !== '__all__' && !getContactGroups(c).includes(activeGroup)) return false;
if (activeGroup !== '__all__' && grp !== activeGroup) return false;
if (!q) return true; if (!q) return true;
return [c.display_name, c.internal_number, c.mobile_number, c.other_number, c.email, c.group_name] 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)); .some(v => String(v || '').toLowerCase().includes(q));
+2 -1
View File
@@ -1,9 +1,10 @@
{ {
"version": "2.6.3", "version": "2.6.4",
"codename": "Pulsar UCP", "codename": "Pulsar UCP",
"release_date": "2026-11-12", "release_date": "2026-11-12",
"min_php_version": "8.2", "min_php_version": "8.2",
"changelog": [ "changelog": [
"Correctif v2.6.4 : prise en charge multi-groupes dans l'annuaire UCP & Admin (un poste appartenant à plusieurs groupes apparaît bien dans chacun des onglets correspondants)",
"UI v2.6.3 : onglets dynamiques par groupe (Tous, Général, Paris, New-York...) avec compteurs dans l'Annuaire UCP", "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 <host> GroundWire (NAT si renseigné, sinon VIP HA si cluster actif, sinon IP locale + port d'écoute local)", "Correctif v2.6.2 : sélection stricte de <host> 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)", "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)",