'use strict'; let ucpState = { user: null, extensions: [], forward_targets: [], feature_codes: [], voicemailsByExt: {}, selectedVmExt: '', vmFolderFilter: 'all', phonebook: [], activeTab: 'extensions', }; let cdrTimer = null; let pollTimer = null; function escapeHtml(str) { return String(str ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function showUcpToast(message, type = 'info') { const container = document.getElementById('ucp-toast-container'); if (!container) return; const palette = { success: 'bg-emerald-50 border-emerald-200 text-emerald-800', error: 'bg-rose-50 border-rose-200 text-rose-800', info: 'bg-white border-slate-200 text-slate-800', }[type] || 'bg-white border-slate-200 text-slate-800'; const el = document.createElement('div'); el.className = `pointer-events-auto rounded-xl border px-4 py-3 text-xs font-semibold shadow-lg transition-all duration-200 ${palette}`; el.textContent = message; container.appendChild(el); setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 220); }, 3500); } function resolveUcpAvatarSrc(photoUrl) { const raw = String(photoUrl || '').trim(); if (!raw) return ''; if (raw.startsWith('/avatars/')) { const file = raw.slice('/avatars/'.length); return `/api.php?action=avatar&file=${encodeURIComponent(file)}`; } return raw; } async function ucpApiCall(action, method = 'GET', body = null) { const opts = { method, credentials: 'same-origin', headers: {}, }; let url = `/api.php?action=${encodeURIComponent(action)}`; if (method === 'GET' && body && typeof body === 'object') { const params = new URLSearchParams(body); url += `&${params.toString()}`; } else if (method === 'POST') { opts.headers['X-CSRF-Token'] = window.UCP_CSRF_TOKEN || ''; if (body instanceof FormData) { opts.body = body; } else { opts.headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(body || {}); } } const res = await fetch(url, opts); if (res.status === 401 && action !== 'login') { window.location.reload(); return null; } const json = await res.json(); if (!res.ok || !json.success) { throw new Error(json.message || 'Erreur lors de la requête.'); } return json; } // ----------------------------------------------------------------------------- // AUTHENTIFICATION // ----------------------------------------------------------------------------- const loginForm = document.getElementById('ucp-login-form'); if (loginForm) { loginForm.addEventListener('submit', async (e) => { e.preventDefault(); const errBox = document.getElementById('ucp-login-error'); const btn = document.getElementById('ucp-login-btn'); if (errBox) errBox.classList.add('hidden'); if (btn) btn.disabled = true; try { const res = await ucpApiCall('login', 'POST', { username: loginForm.elements['username'].value.trim(), password: loginForm.elements['password'].value, }); if (res && res.csrf_token) { window.UCP_CSRF_TOKEN = res.csrf_token; } window.location.reload(); } catch (err) { if (errBox) { errBox.textContent = err.message; errBox.classList.remove('hidden'); } } finally { if (btn) btn.disabled = false; } }); } async function ucpLogout() { try { await ucpApiCall('logout', 'POST', {}); } catch (_) {} window.location.reload(); } // ----------------------------------------------------------------------------- // NAVIGATION & ÉTAT GLOBAL // ----------------------------------------------------------------------------- function switchUcpTab(tabName) { ucpState.activeTab = tabName; document.querySelectorAll('.ucp-panel').forEach(p => p.classList.add('hidden')); const activePanel = document.getElementById(`ucp-panel-${tabName}`); if (activePanel) activePanel.classList.remove('hidden'); document.querySelectorAll('.ucp-tab-btn').forEach(btn => { const isMe = btn.dataset.ucpTab === tabName; btn.className = isMe ? 'ucp-tab-btn px-3.5 py-2 rounded-xl text-xs font-semibold transition flex items-center gap-2 bg-indigo-50 text-indigo-700' : 'ucp-tab-btn px-3.5 py-2 rounded-xl text-xs font-semibold transition flex items-center gap-2 text-slate-600 hover:bg-slate-100'; if (isMe && typeof btn.scrollIntoView === 'function') { btn.scrollIntoView({ behavior: 'smooth', inline: 'nearest', block: 'nearest' }); } }); setTimeout(updateUcpNavScrollHints, 120); if (tabName === 'voicemail') { loadVoicemailsForSelected(); } else if (tabName === 'cdr') { loadPersonalCdr(); } else if (tabName === 'phonebook') { loadPhonebook(); } else if (tabName === 'guide') { renderFeatureGuide(); } } async function refreshUcpState(manual = false) { try { const res = await ucpApiCall('get_state', 'GET'); if (!res || !res.data) return; ucpState.user = res.data.user; ucpState.extensions = res.data.extensions || []; ucpState.forward_targets = res.data.forward_targets || []; ucpState.feature_codes = res.data.feature_codes || []; if (res.data.csrf_token) { window.UCP_CSRF_TOKEN = res.data.csrf_token; } // Sélection par défaut du poste pour la messagerie vocale const vmExts = ucpState.extensions.filter(e => Number(e.voicemail_enabled) === 1); if (!ucpState.selectedVmExt || !vmExts.some(e => e.extension === ucpState.selectedVmExt)) { ucpState.selectedVmExt = vmExts.length ? vmExts[0].extension : (ucpState.extensions[0]?.extension || ''); } updateBadges(); renderExtensionsGrid(); renderVmExtPills(); syncVmPrefsForm(); renderFeatureGuide(); const accExts = document.getElementById('ucp-account-exts'); if (accExts) { accExts.textContent = ucpState.extensions.map(e => `${e.extension} (${e.name})`).join(', ') || 'Aucun poste lié'; } const guideExts = document.getElementById('ucp-guide-user-exts'); if (guideExts) { guideExts.textContent = ucpState.extensions.map(e => e.extension).join(', ') || 'Aucun'; } if (manual) { showUcpToast('État actualisé.', 'info'); if (ucpState.activeTab === 'voicemail') await loadVoicemailsForSelected(); if (ucpState.activeTab === 'cdr') await loadPersonalCdr(); if (ucpState.activeTab === 'phonebook') await loadPhonebook(); } } catch (err) { if (manual) showUcpToast(err.message, 'error'); } } function updateBadges() { const extBadge = document.getElementById('ucp-badge-exts'); if (extBadge) extBadge.textContent = String(ucpState.extensions.length); const totalNewVm = ucpState.extensions.reduce((acc, e) => acc + Number(e.vm_inbox_count || 0), 0); const vmBadge = document.getElementById('ucp-badge-vm'); if (vmBadge) { vmBadge.textContent = String(totalNewVm); vmBadge.className = totalNewVm > 0 ? 'px-1.5 py-0.5 rounded-full bg-rose-100 text-rose-700 text-[10px] font-bold' : 'px-1.5 py-0.5 rounded-full bg-slate-200 text-slate-700 text-[10px] font-bold'; } } // ----------------------------------------------------------------------------- // 1. MES POSTES & REDIRECTIONS (DND / CF / CFU) // ----------------------------------------------------------------------------- function buildForwardOptions(extObj, selectedValue) { const val = String(selectedValue || ''); let html = ``; if (Number(extObj.voicemail_enabled) === 1) { const vmVal = `vm-${extObj.extension}`; html += ``; } const others = (ucpState.forward_targets || []).filter(t => t.extension !== extObj.extension); if (others.length > 0) { html += ``; others.forEach(t => { html += ``; }); html += ``; } const otherVms = (ucpState.forward_targets || []).filter(t => t.extension !== extObj.extension && Number(t.voicemail_enabled) === 1); if (otherVms.length > 0) { html += ``; otherVms.forEach(t => { const vVal = `vm-${t.extension}`; html += ``; }); html += ``; } return html; } function renderExtensionsGrid() { const grid = document.getElementById('ucp-extensions-grid'); if (!grid) return; if (!ucpState.extensions.length) { grid.innerHTML = `
Aucun poste SIP n'est encore lié à votre compte. Contactez votre administrateur.
`; return; } grid.innerHTML = ucpState.extensions.map(ext => { const avatarSrc = resolveUcpAvatarSrc(ext.photo_url); const initials = (ext.name || ext.extension).slice(0, 2).toUpperCase(); const statusPill = ext.online ? ` En ligne ${ext.rtt_ms !== null ? `• ${ext.rtt_ms} ms` : ''} ` : ` Hors ligne `; const vmPill = Number(ext.voicemail_enabled) === 1 ? `` : ''; return `
${avatarSrc ? `` : `
${escapeHtml(initials)}
`}
${escapeHtml(ext.extension)} ${escapeHtml(ext.name)}
${statusPill} ${vmPill}
${ext.ip ? `
${escapeHtml(ext.ip)} ${ext.user_agent ? '• ' + escapeHtml(ext.user_agent) : ''}
` : ''}
Ne Pas Déranger (DND)
${ext.dnd ? 'Activé — les appels entrants sont refusés / envoyés en messagerie' : 'Désactivé — le poste sonne normalement'}
Renvoi limité aux postes internes & répondeurs
`; }).join(''); } async function saveExtensionFeatures(extNum) { const dndEl = document.getElementById(`ucp-dnd-${extNum}`); const cfEl = document.getElementById(`ucp-cf-${extNum}`); const cfuEl = document.getElementById(`ucp-cfu-${extNum}`); try { const res = await ucpApiCall('save_call_features', 'POST', { extension: extNum, dnd: dndEl ? dndEl.checked : false, cf: cfEl ? cfEl.value : '', cfu: cfuEl ? cfuEl.value : '', }); if (res && res.extensions) { ucpState.extensions = res.extensions; renderExtensionsGrid(); } showUcpToast(res.message || 'Enregistré.', 'success'); } catch (err) { showUcpToast(err.message, 'error'); refreshUcpState(false); } } function openExtensionVoicemail(extNum) { ucpState.selectedVmExt = extNum; switchUcpTab('voicemail'); } // ----------------------------------------------------------------------------- // 2. MESSAGERIE VOCALE // ----------------------------------------------------------------------------- function renderVmExtPills() { const container = document.getElementById('ucp-vm-ext-pills'); if (!container) return; const vmExts = ucpState.extensions.filter(e => Number(e.voicemail_enabled) === 1); if (!vmExts.length) { container.innerHTML = `Aucune boîte vocale activée sur vos postes`; return; } container.innerHTML = vmExts.map(e => { const active = e.extension === ucpState.selectedVmExt; return ` `; }).join(''); } function selectVmExtension(extNum) { ucpState.selectedVmExt = extNum; renderVmExtPills(); syncVmPrefsForm(); loadVoicemailsForSelected(); } function syncVmPrefsForm() { const form = document.getElementById('ucp-vm-prefs-form'); const sub = document.getElementById('ucp-vm-prefs-subtitle'); if (!form) return; const extObj = ucpState.extensions.find(e => e.extension === ucpState.selectedVmExt); if (!extObj || Number(extObj.voicemail_enabled) !== 1) { if (sub) sub.textContent = 'Aucune boîte vocale sélectionnée'; form.classList.add('opacity-50', 'pointer-events-none'); return; } form.classList.remove('opacity-50', 'pointer-events-none'); if (sub) sub.textContent = `Poste ${extObj.extension} — ${extObj.name}`; form.elements['pin'].value = ''; form.elements['email'].value = extObj.voicemail_email || ''; } function setVmFolderFilter(folder) { ucpState.vmFolderFilter = folder; ['all', 'INBOX', 'Old'].forEach(f => { const btn = document.getElementById(`vm-filter-${f}`); if (!btn) return; btn.className = f === folder ? 'px-3 py-1.5 rounded-lg bg-indigo-50 text-indigo-700' : 'px-3 py-1.5 rounded-lg text-slate-600 hover:bg-slate-50'; }); renderVoicemailsList(); } async function loadVoicemailsForSelected() { const listEl = document.getElementById('ucp-vm-list'); const extObj = ucpState.extensions.find(e => e.extension === ucpState.selectedVmExt); if (!extObj || Number(extObj.voicemail_enabled) !== 1) { if (listEl) { listEl.innerHTML = `
La messagerie vocale n'est activée sur aucun de vos postes liés.
`; } return; } try { const res = await ucpApiCall('get_voicemails', 'GET', { extension: extObj.extension }); ucpState.voicemailsByExt[extObj.extension] = res.messages || []; renderVoicemailsList(); } catch (err) { if (listEl) { listEl.innerHTML = `
${escapeHtml(err.message)}
`; } } } function formatDuration(sec) { const s = Math.max(0, Number(sec || 0)); const m = Math.floor(s / 60); const r = s % 60; return `${m}:${String(r).padStart(2, '0')}`; } function renderVoicemailsList() { const listEl = document.getElementById('ucp-vm-list'); if (!listEl) return; const all = ucpState.voicemailsByExt[ucpState.selectedVmExt] || []; const filtered = ucpState.vmFolderFilter === 'all' ? all : all.filter(m => m.folder === ucpState.vmFolderFilter); if (!filtered.length) { listEl.innerHTML = `
Aucun message vocal dans ce dossier.
`; return; } listEl.innerHTML = filtered.map(msg => { const streamUrl = `/api.php?action=stream_voicemail&extension=${encodeURIComponent(msg.extension)}&folder=${encodeURIComponent(msg.folder)}&id=${encodeURIComponent(msg.id)}`; const dlUrl = `${streamUrl}&download=1`; const badge = msg.is_new ? `Nouveau` : `Lu / Archivé`; return `
${escapeHtml(msg.callerid)} ${badge}
📅 ${escapeHtml(msg.date_human)} • ⏱️ ${formatDuration(msg.duration)}
⬇️ Télécharger
`; }).join(''); } async function toggleVmRead(ext, folder, id) { try { const res = await ucpApiCall('toggle_voicemail_read', 'POST', { extension: ext, folder, id }); ucpState.voicemailsByExt[ext] = res.messages || []; if (res.extensions) { ucpState.extensions = res.extensions; updateBadges(); renderVmExtPills(); renderExtensionsGrid(); } renderVoicemailsList(); showUcpToast(res.message, 'success'); } catch (err) { showUcpToast(err.message, 'error'); } } async function deleteVm(ext, folder, id) { if (!confirm('Supprimer définitivement ce message vocal ?')) return; try { const res = await ucpApiCall('delete_voicemail', 'POST', { extension: ext, folder, id }); ucpState.voicemailsByExt[ext] = res.messages || []; if (res.extensions) { ucpState.extensions = res.extensions; updateBadges(); renderVmExtPills(); renderExtensionsGrid(); } renderVoicemailsList(); showUcpToast(res.message, 'success'); } catch (err) { showUcpToast(err.message, 'error'); } } const vmPrefsForm = document.getElementById('ucp-vm-prefs-form'); if (vmPrefsForm) { vmPrefsForm.addEventListener('submit', async (e) => { e.preventDefault(); if (!ucpState.selectedVmExt) return; const btn = document.getElementById('ucp-vm-prefs-btn'); if (btn) btn.disabled = true; try { const res = await ucpApiCall('save_voicemail_prefs', 'POST', { extension: ucpState.selectedVmExt, pin: vmPrefsForm.elements['pin'].value.trim(), email: vmPrefsForm.elements['email'].value.trim(), }); if (res.extensions) { ucpState.extensions = res.extensions; syncVmPrefsForm(); } showUcpToast(res.message, 'success'); } catch (err) { showUcpToast(err.message, 'error'); } finally { if (btn) btn.disabled = false; } }); } // ----------------------------------------------------------------------------- // 3. JOURNAL D'APPELS PERSONNEL // ----------------------------------------------------------------------------- function debounceLoadCdr() { clearTimeout(cdrTimer); cdrTimer = setTimeout(loadPersonalCdr, 300); } async function loadPersonalCdr() { const tbody = document.getElementById('ucp-cdr-tbody'); const mobileBox = document.getElementById('ucp-cdr-mobile-list'); if (!tbody && !mobileBox) return; try { const res = await ucpApiCall('get_personal_cdr', 'GET', { direction: document.getElementById('ucp-cdr-direction')?.value || 'all', disposition: document.getElementById('ucp-cdr-disposition')?.value || 'all', period: document.getElementById('ucp-cdr-period')?.value || 'all', search: document.getElementById('ucp-cdr-search')?.value || '', }); const data = res.data || {}; const calls = data.calls || []; const stats = data.stats || { total: 0, answered: 0, missed: 0 }; document.getElementById('ucp-cdr-total').textContent = String(stats.total); document.getElementById('ucp-cdr-answered').textContent = String(stats.answered); document.getElementById('ucp-cdr-missed').textContent = String(stats.missed); if (!calls.length) { if (tbody) tbody.innerHTML = `Aucun appel correspondant.`; if (mobileBox) mobileBox.innerHTML = `
Aucun appel correspondant.
`; return; } const dirMap = { inbound: '↘ Entrant', outbound: '↗ Sortant', internal: '⇄ Interne', }; const formatParty = (name, num) => { if (name && num && name !== num) { return `
${escapeHtml(name)}
${escapeHtml(num)}
`; } return `${escapeHtml(num || name || '—')}`; }; const formatInlineParty = (name, num) => { if (name && num && name !== num) { return `${escapeHtml(name)} (${escapeHtml(num)})`; } return `${escapeHtml(num || name || '—')}`; }; const rowsHtml = []; const cardsHtml = []; calls.forEach(c => { const ok = c.disposition === 'ANSWERED'; const stBadge = ok ? `${escapeHtml(c.status_label || 'Répondu')}` : `${escapeHtml(c.status_label || 'Manqué')}`; const dirBadge = dirMap[c.direction] || escapeHtml(c.direction_label || c.direction); const srcNum = String(c.caller_number ?? c.src ?? ''); const srcName = String(c.caller_name ?? ''); const dstNum = String(c.callee_number ?? c.dst ?? ''); const dstName = String(c.callee_name ?? ''); const durText = escapeHtml(c.billsec_formatted || formatDuration(c.billsec || c.duration || 0)); const dtText = escapeHtml(c.datetime_formatted || c.datetime || ''); rowsHtml.push(` ${dtText} ${dirBadge} ${formatParty(srcName, srcNum)} ${formatParty(dstName, dstNum)} ${durText} ${stBadge} `); cardsHtml.push(`
${dirBadge} ${stBadge}
⏱ ${durText}
${formatInlineParty(srcName, srcNum)} → ${formatInlineParty(dstName, dstNum)}
${dtText}
`); }); if (tbody) tbody.innerHTML = rowsHtml.join(''); if (mobileBox) mobileBox.innerHTML = cardsHtml.join(''); } catch (err) { if (tbody) tbody.innerHTML = `${escapeHtml(err.message)}`; if (mobileBox) mobileBox.innerHTML = `
${escapeHtml(err.message)}
`; } } // ----------------------------------------------------------------------------- // 4. ANNUAIRE & PROFIL UTILISATEUR // ----------------------------------------------------------------------------- async function loadPhonebook() { try { const res = await ucpApiCall('get_phonebook', 'GET'); ucpState.phonebook = res.contacts || []; renderPhonebookGrid(); } catch (err) { showUcpToast(err.message, 'error'); } } function renderPhonebookGrid() { const grid = document.getElementById('ucp-pb-grid'); if (!grid) return; const q = (document.getElementById('ucp-pb-search')?.value || '').trim().toLowerCase(); const list = (ucpState.phonebook || []).filter(c => { 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)); }); if (!list.length) { grid.innerHTML = `
Aucun contact trouvé.
`; return; } grid.innerHTML = list.map(c => { const avatarSrc = resolveUcpAvatarSrc(c.photo_url); const initials = (c.display_name || '?').slice(0, 2).toUpperCase(); const mineBadge = c.is_mine ? `Mon poste` : ''; return `
${avatarSrc ? `` : `
${escapeHtml(initials)}
`}
${escapeHtml(c.display_name)} ${mineBadge}
${escapeHtml(c.group_name || 'Général')}
${c.internal_number ? `
📱 Interne : ${escapeHtml(c.internal_number)}
` : ''} ${c.mobile_number ? `
📲 Mobile : ${escapeHtml(c.mobile_number)}
` : ''} ${c.other_number ? `
☎️ Autre : ${escapeHtml(c.other_number)}
` : ''} ${c.email ? `
✉️ ${escapeHtml(c.email)}
` : ''}
${c.is_mine ? ` ` : ''}
`; }).join(''); } function openProfileModal(extNum) { const contact = (ucpState.phonebook || []).find(c => c.is_mine && String(c.mine_extension) === String(extNum)); if (!contact) return; document.getElementById('ucp-profile-ext').value = extNum; document.getElementById('ucp-profile-modal-sub').textContent = `Poste ${extNum} — ${contact.display_name}`; document.getElementById('ucp-profile-email-input').value = contact.email || ''; updateProfileModalPreview(contact.photo_url, contact.display_name); document.getElementById('ucp-profile-modal').classList.remove('hidden'); } function updateProfileModalPreview(photoUrl, name) { const img = document.getElementById('ucp-profile-preview'); const init = document.getElementById('ucp-profile-initials'); const src = resolveUcpAvatarSrc(photoUrl); if (src) { img.src = src; img.classList.remove('hidden'); init.classList.add('hidden'); } else { img.classList.add('hidden'); init.textContent = (name || '?').slice(0, 2).toUpperCase(); init.classList.remove('hidden'); } } function closeProfileModal() { document.getElementById('ucp-profile-modal').classList.add('hidden'); } async function uploadSelectedAvatar() { const input = document.getElementById('ucp-avatar-input'); const ext = document.getElementById('ucp-profile-ext').value; if (!input || !input.files || !input.files[0] || !ext) return; const fd = new FormData(); fd.append('extension', ext); fd.append('avatar', input.files[0]); try { const res = await ucpApiCall('upload_my_avatar', 'POST', fd); if (res.contacts) ucpState.phonebook = res.contacts; if (res.extensions) ucpState.extensions = res.extensions; updateProfileModalPreview(res.photo_url, ext); renderPhonebookGrid(); renderExtensionsGrid(); showUcpToast(res.message, 'success'); } catch (err) { showUcpToast(err.message, 'error'); } finally { input.value = ''; } } const profileEmailForm = document.getElementById('ucp-profile-email-form'); if (profileEmailForm) { profileEmailForm.addEventListener('submit', async (e) => { e.preventDefault(); const ext = document.getElementById('ucp-profile-ext').value; const email = document.getElementById('ucp-profile-email-input').value.trim(); try { const res = await ucpApiCall('save_my_email', 'POST', { extension: ext, email }); if (res.contacts) ucpState.phonebook = res.contacts; if (res.extensions) ucpState.extensions = res.extensions; renderPhonebookGrid(); closeProfileModal(); showUcpToast(res.message, 'success'); } catch (err) { showUcpToast(err.message, 'error'); } }); } // ----------------------------------------------------------------------------- // 5. NOTICE D'UTILISATION, CODES DE FONCTION & EXPORT PDF // ----------------------------------------------------------------------------- function printFeatureGuide() { if (ucpState.activeTab !== 'guide') { switchUcpTab('guide'); } const dateEl = document.getElementById('ucp-guide-print-date'); if (dateEl) { dateEl.textContent = new Date().toLocaleDateString('fr-FR'); } setTimeout(() => window.print(), 80); } function renderFeatureGuide() { const quickBox = document.getElementById('ucp-guide-quickcards'); const catBox = document.getElementById('ucp-guide-categories'); if (!quickBox || !catBox) return; const allCodes = ucpState.feature_codes || []; const byKey = new Map(allCodes.map(c => [String(c.feature_key), c])); const codeOf = (key, fallback = '') => escapeHtml(byKey.get(key)?.code || fallback); // 1. Encarts "Notice pratique pas à pas" const vmOwn = byKey.get('voicemail_own'); const vmOther = byKey.get('voicemail_general'); const cfOn = byKey.get('cf_activate'); const cfOff = byKey.get('cf_deactivate'); const cfuOn = byKey.get('cfu_activate'); const dndTog = byKey.get('dnd_toggle'); const dndOn = byKey.get('dnd_activate'); const pkBlf = byKey.get('pickup_blf'); const pkGrp = byKey.get('pickup_group'); quickBox.innerHTML = `
📬 Utiliser la Messagerie Vocale
↪️ Gérer Renvois & Ne Pas Déranger
🎧 Interception & Prise d'Appel
`; // 2. Regroupement des codes actifs par catégorie const q = (document.getElementById('ucp-guide-search')?.value || '').trim().toLowerCase(); const filtered = allCodes.filter(c => { if (!q) return true; return String(c.code || '').toLowerCase().includes(q) || String(c.name || '').toLowerCase().includes(q) || String(c.description || '').toLowerCase().includes(q) || String(c.category || '').toLowerCase().includes(q); }); if (!filtered.length) { catBox.innerHTML = `
Aucun code de fonction correspondant à « ${escapeHtml(q)} ».
`; return; } const catMeta = { voicemail: { label: 'Messagerie Vocale', icon: '📬', badge: 'bg-indigo-50 text-indigo-700 border-indigo-200', header: 'bg-indigo-50/60 border-indigo-100 text-indigo-950' }, forward: { label: "Renvois d'Appel", icon: '↪️', badge: 'bg-amber-50 text-amber-800 border-amber-200', header: 'bg-amber-50/60 border-amber-100 text-amber-950' }, dnd: { label: 'Ne Pas Déranger (DND)', icon: '🔕', badge: 'bg-rose-50 text-rose-700 border-rose-200', header: 'bg-rose-50/60 border-rose-100 text-rose-950' }, pickup: { label: "Interception d'Appels", icon: '📲', badge: 'bg-emerald-50 text-emerald-700 border-emerald-200', header: 'bg-emerald-50/60 border-emerald-100 text-emerald-950' }, security: { label: 'Liste Noire & Blocage Appels', icon: '🛡️', badge: 'bg-slate-100 text-slate-700 border-slate-300', header: 'bg-slate-100/80 border-slate-200 text-slate-900' }, tools: { label: 'Outils, Tests Audio & Services', icon: '🛠️', badge: 'bg-sky-50 text-sky-700 border-sky-200', header: 'bg-sky-50/60 border-sky-100 text-sky-950' }, }; const orderedCats = ['voicemail', 'forward', 'dnd', 'pickup', 'security', 'tools']; const grouped = new Map(); filtered.forEach(c => { const key = c.category || 'tools'; if (!grouped.has(key)) grouped.set(key, []); grouped.get(key).push(c); }); const catKeys = [ ...orderedCats.filter(k => grouped.has(k)), ...[...grouped.keys()].filter(k => !orderedCats.includes(k)), ]; catBox.innerHTML = catKeys.map(catKey => { const meta = catMeta[catKey] || { label: catKey, icon: '📌', badge: 'bg-slate-100 text-slate-700 border-slate-300', header: 'bg-slate-50 border-slate-200 text-slate-900' }; const items = grouped.get(catKey) || []; const rows = items.map(item => `
${escapeHtml(item.code)}
${escapeHtml(item.name)}

${escapeHtml(item.description || '')}

`).join(''); return `
${meta.icon} ${escapeHtml(meta.label)}
${items.length} code(s)
${rows}
`; }).join(''); } // ----------------------------------------------------------------------------- // 6. CHANGEMENT DE MOT DE PASSE UCP // ----------------------------------------------------------------------------- const passwordForm = document.getElementById('ucp-password-form'); if (passwordForm) { passwordForm.addEventListener('submit', async (e) => { e.preventDefault(); const cur = passwordForm.elements['current_password'].value; const nw = passwordForm.elements['new_password'].value; const conf = passwordForm.elements['confirm_password'].value; if (nw !== conf) { showUcpToast('Les deux nouveaux mots de passe ne correspondent pas.', 'error'); return; } try { const res = await ucpApiCall('change_password', 'POST', { current_password: cur, new_password: nw, }); passwordForm.reset(); showUcpToast(res.message, 'success'); } catch (err) { showUcpToast(err.message, 'error'); } }); } // ----------------------------------------------------------------------------- // INITIALISATION // ----------------------------------------------------------------------------- if (window.UCP_IS_AUTH) { refreshUcpState(false); initUcpNavScrollHints(); pollTimer = setInterval(() => { if (!document.hidden) { refreshUcpState(false); } }, 15000); } function updateUcpNavScrollHints() { const scroller = document.getElementById('ucp-nav-scroller'); const leftHint = document.getElementById('ucp-nav-hint-left'); const rightHint = document.getElementById('ucp-nav-hint-right'); if (!scroller || !leftHint || !rightHint) return; const hasLeft = scroller.scrollLeft > 4; const hasRight = scroller.scrollLeft + scroller.clientWidth < scroller.scrollWidth - 4; leftHint.classList.toggle('hidden', !hasLeft); leftHint.classList.toggle('flex', hasLeft); rightHint.classList.toggle('hidden', !hasRight); rightHint.classList.toggle('flex', hasRight); } function scrollUcpNav(direction) { const scroller = document.getElementById('ucp-nav-scroller'); if (!scroller) return; scroller.scrollBy({ left: direction * 170, behavior: 'smooth' }); } function initUcpNavScrollHints() { const scroller = document.getElementById('ucp-nav-scroller'); if (!scroller) return; scroller.addEventListener('scroll', updateUcpNavScrollHints, { passive: true }); window.addEventListener('resize', updateUcpNavScrollHints); requestAnimationFrame(updateUcpNavScrollHints); setTimeout(updateUcpNavScrollHints, 250); }