diff --git a/public/api.php b/public/api.php index e6ae4db..76d0a56 100644 --- a/public/api.php +++ b/public/api.php @@ -90,6 +90,7 @@ try { $settings = $db->query('SELECT * FROM settings WHERE id = 1')->fetch(); $extensions = $db->query('SELECT * FROM extensions ORDER BY CAST(extension AS INTEGER) ASC, extension ASC')->fetchAll(); $groups = $db->query('SELECT * FROM groups ORDER BY CAST(extension AS INTEGER) ASC, extension ASC')->fetchAll(); + $pagingGroups = $db->query('SELECT * FROM paging_groups ORDER BY CAST(extension AS INTEGER) ASC, extension ASC')->fetchAll(); $extensionGroups = $db->query('SELECT * FROM extension_groups ORDER BY id ASC')->fetchAll(); $trunks = $db->query('SELECT * FROM trunks ORDER BY id ASC')->fetchAll(); $ivrs = $db->query('SELECT * FROM ivrs ORDER BY id ASC')->fetchAll(); @@ -102,6 +103,11 @@ try { } unset($g); + foreach ($pagingGroups as &$pg) { + $pg['members'] = json_decode((string)$pg['members'], true) ?: []; + } + unset($pg); + foreach ($extensionGroups as &$eg) { $eg['members'] = json_decode((string)$eg['members'], true) ?: []; } @@ -136,6 +142,7 @@ try { 'settings' => $settings, 'extensions' => $extensions, 'groups' => $groups, + 'paging_groups' => $pagingGroups, 'extension_groups' => $extensionGroups, 'trunks' => $trunks, 'ivrs' => $ivrs, @@ -441,6 +448,106 @@ try { ]); break; + case 'save_paging_group': + $id = isset($payload['id']) && $payload['id'] !== '' ? (int)$payload['id'] : null; + $extension = trim((string)($payload['extension'] ?? '')); + $name = trim((string)($payload['name'] ?? '')); + $desc = trim((string)($payload['description'] ?? '')); + $members = is_array($payload['members'] ?? null) ? array_values(array_filter(array_map('trim', $payload['members']))) : []; + $duplex = !empty($payload['duplex']) ? 1 : 0; + $busyMode = in_array($payload['busy_mode'] ?? '', ['skip', 'force'], true) ? $payload['busy_mode'] : 'skip'; + $playBeep = isset($payload['play_beep']) ? (!empty($payload['play_beep']) ? 1 : 0) : 1; + $timeout = max(10, min(600, (int)($payload['timeout'] ?? 120))); + $enabled = isset($payload['enabled']) ? (!empty($payload['enabled']) ? 1 : 0) : 1; + + if (!preg_match('/^\d{2,6}$/', $extension)) { + throw new InvalidArgumentException("Le numéro du groupe de téléavertissement doit comporter entre 2 et 6 chiffres."); + } + if ($name === '') { + throw new InvalidArgumentException("Le nom du groupe de téléavertissement est obligatoire."); + } + if (empty($members)) { + throw new InvalidArgumentException("Veuillez sélectionner au moins un poste membre pour ce groupe de téléavertissement."); + } + + // Vérification de conflit avec un poste SIP existant + $chkExt = $db->prepare('SELECT COUNT(*) FROM extensions WHERE extension = :ext'); + $chkExt->execute([':ext' => $extension]); + if ((int)$chkExt->fetchColumn() > 0) { + throw new InvalidArgumentException("Le numéro {$extension} est déjà assigné à un poste SIP individuel."); + } + + // Vérification de conflit avec un groupe de sonnerie + $chkGrp = $db->prepare('SELECT COUNT(*) FROM groups WHERE extension = :ext'); + $chkGrp->execute([':ext' => $extension]); + if ((int)$chkGrp->fetchColumn() > 0) { + throw new InvalidArgumentException("Le numéro {$extension} est déjà assigné à un groupe de sonnerie."); + } + + if ($id) { + $stmt = $db->prepare(' + UPDATE paging_groups + SET extension = :ext, name = :name, description = :desc, + members = :members, duplex = :duplex, busy_mode = :busy_mode, + play_beep = :play_beep, timeout = :timeout, enabled = :enabled, + updated_at = CURRENT_TIMESTAMP + WHERE id = :id + '); + $stmt->execute([ + ':id' => $id, + ':ext' => $extension, + ':name' => $name, + ':desc' => $desc, + ':members' => json_encode($members), + ':duplex' => $duplex, + ':busy_mode' => $busyMode, + ':play_beep' => $playBeep, + ':timeout' => $timeout, + ':enabled' => $enabled, + ]); + } else { + $stmt = $db->prepare(' + INSERT INTO paging_groups (extension, name, description, members, duplex, busy_mode, play_beep, timeout, enabled) + VALUES (:ext, :name, :desc, :members, :duplex, :busy_mode, :play_beep, :timeout, :enabled) + ON CONFLICT(extension) DO UPDATE SET + name = excluded.name, + description = excluded.description, + members = excluded.members, + duplex = excluded.duplex, + busy_mode = excluded.busy_mode, + play_beep = excluded.play_beep, + timeout = excluded.timeout, + enabled = excluded.enabled, + updated_at = CURRENT_TIMESTAMP + '); + $stmt->execute([ + ':ext' => $extension, + ':name' => $name, + ':desc' => $desc, + ':members' => json_encode($members), + ':duplex' => $duplex, + ':busy_mode' => $busyMode, + ':play_beep' => $playBeep, + ':timeout' => $timeout, + ':enabled' => $enabled, + ]); + } + + echo json_encode([ + 'success' => true, + 'message' => "Groupe de téléavertissement {$extension} ({$name}) sauvegardé.", + ]); + break; + + case 'delete_paging_group': + $id = (int)($payload['id'] ?? 0); + $db->prepare('DELETE FROM paging_groups WHERE id = :id')->execute([':id' => $id]); + echo json_encode([ + 'success' => true, + 'message' => "Groupe de téléavertissement supprimé.", + ]); + break; + case 'save_extension_group': $id = isset($payload['id']) && $payload['id'] !== '' ? (int)$payload['id'] : null; $name = trim((string)($payload['name'] ?? '')); diff --git a/public/assets/app.js b/public/assets/app.js index 4461a9a..e941bd2 100644 --- a/public/assets/app.js +++ b/public/assets/app.js @@ -4,6 +4,7 @@ let appState = { settings: {}, extensions: [], groups: [], + paging_groups: [], extension_groups: [], trunks: [], ivrs: [], @@ -200,6 +201,7 @@ function renderAll() { renderExtensionsTable(); renderGroupsTable(); renderExtensionGroupsTable(); + renderPagingTable(); renderPhonebookTable(); renderTrunksTable(); renderOutboundRoutesTable(); @@ -341,6 +343,8 @@ function renderSummaryCards() { document.getElementById('nav-badge-groups').textContent = grps.length; const egBadge = document.getElementById('nav-badge-extgroups'); if (egBadge) egBadge.textContent = (appState.extension_groups || []).length; + const pagingBadge = document.getElementById('nav-badge-paging'); + if (pagingBadge) pagingBadge.textContent = (appState.paging_groups || []).length; const pbBadge = document.getElementById('nav-badge-phonebook'); if (pbBadge) pbBadge.textContent = (appState.phonebook_contacts || []).length; document.getElementById('nav-badge-trunks').textContent = trks.length; @@ -1379,6 +1383,178 @@ async function deleteExtGroup(id, optName = null) { } } +// --- 2C. Rendu & CRUD Groupes de Téléavertissement & Interphonie (Paging) --- +function renderPagingTable() { + const tbody = document.getElementById('paging-tbody'); + if (!tbody) return; + const rows = appState.paging_groups || []; + + if (rows.length === 0) { + tbody.innerHTML = `