ui(admin): v2.6.6 selecteur de destinations personnalise avec icones SVG style menu
This commit is contained in:
1 parent
b8cf932651
commit
70a021ef63
2 files changed
+211
-7
No files matched your search
+209
-6
@@ -1882,7 +1882,7 @@ function buildDestinationOptionsHtml(selectedValue = 'ext-101') {
|
||||
|
||||
let html = '';
|
||||
if (exts.length > 0) {
|
||||
html += `<optgroup label="📱 Postes SIP (Extensions)">`;
|
||||
html += `<optgroup label="Postes SIP (Extensions)">`;
|
||||
exts.forEach(e => {
|
||||
const val = `ext-${e.extension}`;
|
||||
html += `<option value="${escapeHtml(val)}" ${normVal === val ? 'selected' : ''}>Poste: ${escapeHtml(e.extension)} — ${escapeHtml(e.name)}</option>`;
|
||||
@@ -1891,7 +1891,7 @@ function buildDestinationOptionsHtml(selectedValue = 'ext-101') {
|
||||
}
|
||||
|
||||
if (grps.length > 0) {
|
||||
html += `<optgroup label="👥 Ring Groups (Groupes d'appel)">`;
|
||||
html += `<optgroup label="Ring Groups (Groupes d'appel)">`;
|
||||
grps.forEach(g => {
|
||||
const val = `grp-${g.extension}`;
|
||||
html += `<option value="${escapeHtml(val)}" ${normVal === val ? 'selected' : ''}>Ring Group: ${escapeHtml(g.extension)} — ${escapeHtml(g.name)}</option>`;
|
||||
@@ -1900,7 +1900,7 @@ function buildDestinationOptionsHtml(selectedValue = 'ext-101') {
|
||||
}
|
||||
|
||||
if (paging.length > 0) {
|
||||
html += `<optgroup label="📢 Téléavertissement & Interphonie (Paging)">`;
|
||||
html += `<optgroup label="Téléavertissement & Interphonie (Paging)">`;
|
||||
paging.forEach(pg => {
|
||||
const val = `page-${pg.extension}`;
|
||||
html += `<option value="${escapeHtml(val)}" ${normVal === val ? 'selected' : ''}>Paging: ${escapeHtml(pg.extension)} — ${escapeHtml(pg.name)}</option>`;
|
||||
@@ -1909,7 +1909,7 @@ function buildDestinationOptionsHtml(selectedValue = 'ext-101') {
|
||||
}
|
||||
|
||||
if (ivrs.length > 0) {
|
||||
html += `<optgroup label="🎙️ Serveurs Vocaux (SVI / IVR)">`;
|
||||
html += `<optgroup label="Serveurs Vocaux (SVI / IVR)">`;
|
||||
ivrs.forEach(i => {
|
||||
const val = i.name;
|
||||
html += `<option value="${escapeHtml(val)}" ${normVal === val ? 'selected' : ''}>SVI / IVR: ${escapeHtml(i.name)} ${i.description ? '(' + escapeHtml(i.description) + ')' : ''}</option>`;
|
||||
@@ -1920,7 +1920,7 @@ function buildDestinationOptionsHtml(selectedValue = 'ext-101') {
|
||||
const vmExts = exts.filter(e => Number(e.voicemail_enabled) === 1);
|
||||
const hasMissingVm = normVal.startsWith('vm-') && !vmExts.some(e => `vm-${e.extension}` === normVal);
|
||||
if (vmExts.length > 0 || hasMissingVm) {
|
||||
html += `<optgroup label="📬 Messagerie Vocale Directe (Répondeur)">`;
|
||||
html += `<optgroup label="Messagerie Vocale Directe (Répondeur)">`;
|
||||
if (hasMissingVm) {
|
||||
const missingExt = normVal.slice(3);
|
||||
html += `<option value="${escapeHtml(normVal)}" selected>Répondeur: ${escapeHtml(missingExt)} (messagerie désactivée)</option>`;
|
||||
@@ -1960,7 +1960,7 @@ function buildDestinationOptionsHtml(selectedValue = 'ext-101') {
|
||||
}
|
||||
|
||||
html += `
|
||||
<optgroup label="🚫 Terminer l'appel (Terminate Call)">
|
||||
<optgroup label="Terminer l'appel (Terminate Call)">
|
||||
<option value="terminate-hangup" ${normVal === 'terminate-hangup' || normVal === 'hangup' ? 'selected' : ''}>Raccrocher immédiatement (Hangup)</option>
|
||||
<option value="terminate-busy" ${normVal === 'terminate-busy' || normVal === 'busy' ? 'selected' : ''}>Tonalité d'occupation (Busy)</option>
|
||||
<option value="terminate-congestion" ${normVal === 'terminate-congestion' || normVal === 'congestion' ? 'selected' : ''}>Tonalité de congestion / réseau saturé (Congestion)</option>
|
||||
@@ -1972,6 +1972,209 @@ function buildDestinationOptionsHtml(selectedValue = 'ext-101') {
|
||||
return html;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SÉLECTEUR DE DESTINATION CUSTOM AVEC ICÔNES SVG (STYLE MENU LATÉRAL)
|
||||
// -----------------------------------------------------------------------------
|
||||
let activeDestDropdownMenu = null;
|
||||
let activeDestDropdownSelect = null;
|
||||
|
||||
function getDestinationIconSvg(val, label = '') {
|
||||
const v = String(val || '');
|
||||
const l = String(label || '').toLowerCase();
|
||||
const mk = (cls, path) => `<svg class="w-4 h-4 shrink-0 ${cls}" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">${path}</svg>`;
|
||||
|
||||
if (v.startsWith('ext-') || l.includes('postes sip')) {
|
||||
return mk('text-indigo-400', '<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z"/>');
|
||||
}
|
||||
if (v.startsWith('grp-') || l.includes('ring groups')) {
|
||||
return mk('text-cyan-400', '<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z"/>');
|
||||
}
|
||||
if (v.startsWith('page-') || l.includes('paging') || l.includes('interphonie')) {
|
||||
return mk('text-purple-400', '<path stroke-linecap="round" stroke-linejoin="round" d="M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 110-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 01-1.44-4.282m3.102.069a18.03 18.03 0 01-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 018.835 2.535M10.34 6.66a23.847 23.847 0 008.835-2.535m0 0A23.74 23.74 0 0018.795 3m.38 1.125a23.91 23.91 0 011.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 001.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 010 3.46"/>');
|
||||
}
|
||||
if (v.startsWith('vm-') || l.includes('messagerie') || l.includes('répondeur')) {
|
||||
return mk('text-amber-400', '<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 9v.906a2.25 2.25 0 01-1.183 1.981l-6.478 3.488M2.25 9v.906a2.25 2.25 0 001.183 1.981l6.478 3.488m8.839 2.51l-4.66-2.51m0 0l-1.023-.55a2.25 2.25 0 00-2.134 0l-1.022.55m0 0l-4.661 2.51m16.5 1.615a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V8.844a2.25 2.25 0 011.183-1.98l7.5-4.04a2.25 2.25 0 012.134 0l7.5 4.04a2.25 2.25 0 011.183 1.98V19.5z"/>');
|
||||
}
|
||||
if (v.startsWith('moh-') || l.includes('musique')) {
|
||||
return mk('text-sky-400', '<path stroke-linecap="round" stroke-linejoin="round" d="M9 9l10.5-3m0 6.553v3.75a2.25 2.25 0 01-1.632 2.163l-1.32.377a1.803 1.803 0 11-.99-3.467l2.31-.66a2.25 2.25 0 001.632-2.163zm0 0V2.25L9 5.25v10.303m0 0v3.75a2.25 2.25 0 01-1.632 2.163l-1.32.377a1.803 1.803 0 01-.99-3.467l2.31-.66A2.25 2.25 0 009 15.553z"/>');
|
||||
}
|
||||
if (v.startsWith('sound-loop-') || l.includes('boucle continue')) {
|
||||
return mk('text-violet-400', '<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"/>');
|
||||
}
|
||||
if (v.startsWith('sound-') || l.includes('annonces vocales')) {
|
||||
return mk('text-teal-400', '<path stroke-linecap="round" stroke-linejoin="round" d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z"/>');
|
||||
}
|
||||
if (v.startsWith('terminate-') || ['hangup', 'busy', 'congestion', 'zapateller', 'ringforever'].includes(v) || l.includes('terminer')) {
|
||||
return mk('text-rose-400', '<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/>');
|
||||
}
|
||||
return mk('text-emerald-400', '<path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z"/>');
|
||||
}
|
||||
|
||||
function closeDestinationDropdown() {
|
||||
if (activeDestDropdownMenu) {
|
||||
activeDestDropdownMenu.remove();
|
||||
activeDestDropdownMenu = null;
|
||||
activeDestDropdownSelect = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateDestinationSelectTrigger(selectEl) {
|
||||
const wrap = selectEl._nexusDestWrap;
|
||||
if (!wrap) return;
|
||||
const btn = wrap.querySelector('.nexus-dest-trigger');
|
||||
if (!btn) return;
|
||||
const opt = selectEl.options[selectEl.selectedIndex] || selectEl.options[0];
|
||||
const val = opt ? opt.value : '';
|
||||
const txt = opt ? opt.textContent : 'Sélectionner…';
|
||||
btn.innerHTML = `
|
||||
<span class="flex items-center gap-2 min-w-0">
|
||||
${getDestinationIconSvg(val)}
|
||||
<span class="truncate">${escapeHtml(txt)}</span>
|
||||
</span>
|
||||
<svg class="w-4 h-4 shrink-0 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"/>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
|
||||
function openDestinationDropdown(selectEl, triggerBtn) {
|
||||
if (activeDestDropdownSelect === selectEl) {
|
||||
closeDestinationDropdown();
|
||||
return;
|
||||
}
|
||||
closeDestinationDropdown();
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'fixed z-[120] bg-anthracite-900 border border-anthracite-700 rounded-xl shadow-2xl shadow-black/70 overflow-hidden flex flex-col';
|
||||
|
||||
const listEl = document.createElement('div');
|
||||
listEl.className = 'overflow-y-auto p-1.5 space-y-0.5 max-h-72';
|
||||
|
||||
const curVal = selectEl.value;
|
||||
const appendOptionBtn = (opt) => {
|
||||
const isSel = opt.value === curVal;
|
||||
const item = document.createElement('button');
|
||||
item.type = 'button';
|
||||
item.className = `w-full flex items-center justify-between gap-2 px-2.5 py-1.5 rounded-lg text-left text-xs transition ${
|
||||
isSel ? 'bg-indigo-600/25 text-white font-semibold' : 'text-slate-200 hover:bg-anthracite-800 hover:text-white'
|
||||
}`;
|
||||
item.innerHTML = `
|
||||
<span class="flex items-center gap-2 min-w-0">
|
||||
${getDestinationIconSvg(opt.value)}
|
||||
<span class="truncate">${escapeHtml(opt.textContent)}</span>
|
||||
</span>
|
||||
${isSel ? '<svg class="w-3.5 h-3.5 shrink-0 text-indigo-400" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/></svg>' : ''}
|
||||
`;
|
||||
item.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
selectEl.value = opt.value;
|
||||
updateDestinationSelectTrigger(selectEl);
|
||||
selectEl.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
closeDestinationDropdown();
|
||||
});
|
||||
listEl.appendChild(item);
|
||||
};
|
||||
|
||||
Array.from(selectEl.children).forEach(node => {
|
||||
if (node.tagName === 'OPTGROUP') {
|
||||
const hdr = document.createElement('div');
|
||||
hdr.className = 'flex items-center gap-1.5 px-2 pt-2 pb-1 text-[10px] font-bold uppercase tracking-wider text-slate-400 select-none';
|
||||
hdr.innerHTML = `${getDestinationIconSvg('', node.label)}<span>${escapeHtml(node.label)}</span>`;
|
||||
listEl.appendChild(hdr);
|
||||
Array.from(node.children).forEach(opt => {
|
||||
if (opt.tagName === 'OPTION') appendOptionBtn(opt);
|
||||
});
|
||||
} else if (node.tagName === 'OPTION') {
|
||||
appendOptionBtn(node);
|
||||
}
|
||||
});
|
||||
|
||||
menu.appendChild(listEl);
|
||||
document.body.appendChild(menu);
|
||||
|
||||
const rect = triggerBtn.getBoundingClientRect();
|
||||
const width = Math.max(rect.width, 280);
|
||||
const spaceBelow = window.innerHeight - rect.bottom - 12;
|
||||
const menuHeight = Math.min(296, menu.scrollHeight || 296);
|
||||
const openUp = spaceBelow < 180 && rect.top > spaceBelow;
|
||||
menu.style.width = `${Math.min(width, window.innerWidth - 16)}px`;
|
||||
menu.style.left = `${Math.max(8, Math.min(rect.left, window.innerWidth - width - 8))}px`;
|
||||
if (openUp) {
|
||||
menu.style.bottom = `${window.innerHeight - rect.top + 4}px`;
|
||||
} else {
|
||||
menu.style.top = `${rect.bottom + 4}px`;
|
||||
}
|
||||
|
||||
activeDestDropdownMenu = menu;
|
||||
activeDestDropdownSelect = selectEl;
|
||||
}
|
||||
|
||||
function enhanceDestinationSelect(selectEl) {
|
||||
if (!selectEl || selectEl.tagName !== 'SELECT') return;
|
||||
if (!selectEl.querySelector('option[value="terminate-hangup"]')) return;
|
||||
|
||||
if (!selectEl._nexusDestWrap) {
|
||||
selectEl.classList.add('sr-only');
|
||||
selectEl.setAttribute('tabindex', '-1');
|
||||
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'relative w-full';
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'nexus-dest-trigger w-full flex items-center justify-between gap-2 px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 hover:border-indigo-500/60 focus:border-indigo-500 focus:outline-none text-xs text-white text-left transition';
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openDestinationDropdown(selectEl, btn);
|
||||
});
|
||||
|
||||
selectEl.parentNode.insertBefore(wrap, selectEl);
|
||||
wrap.appendChild(selectEl);
|
||||
wrap.appendChild(btn);
|
||||
selectEl._nexusDestWrap = wrap;
|
||||
|
||||
selectEl.addEventListener('change', () => updateDestinationSelectTrigger(selectEl));
|
||||
const optObs = new MutationObserver(() => updateDestinationSelectTrigger(selectEl));
|
||||
optObs.observe(selectEl, { childList: true, subtree: true });
|
||||
}
|
||||
updateDestinationSelectTrigger(selectEl);
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined' && !window._nexusDestSelectInit) {
|
||||
window._nexusDestSelectInit = true;
|
||||
document.addEventListener('click', (e) => {
|
||||
if (activeDestDropdownMenu && !activeDestDropdownMenu.contains(e.target)) {
|
||||
closeDestinationDropdown();
|
||||
}
|
||||
});
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') closeDestinationDropdown();
|
||||
});
|
||||
window.addEventListener('resize', closeDestinationDropdown);
|
||||
document.addEventListener('scroll', (e) => {
|
||||
if (activeDestDropdownMenu && !activeDestDropdownMenu.contains(e.target)) {
|
||||
closeDestinationDropdown();
|
||||
}
|
||||
}, true);
|
||||
|
||||
const domObs = new MutationObserver((mutations) => {
|
||||
for (const m of mutations) {
|
||||
if (m.target && m.target.tagName === 'SELECT') {
|
||||
enhanceDestinationSelect(m.target);
|
||||
}
|
||||
m.addedNodes?.forEach(n => {
|
||||
if (n.nodeType !== 1) return;
|
||||
if (n.tagName === 'SELECT') enhanceDestinationSelect(n);
|
||||
else n.querySelectorAll?.('select').forEach(enhanceDestinationSelect);
|
||||
});
|
||||
}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('select').forEach(enhanceDestinationSelect);
|
||||
domObs.observe(document.body, { childList: true, subtree: true });
|
||||
});
|
||||
}
|
||||
|
||||
function updateInboundRoutesBadge() {
|
||||
const count = document.querySelectorAll('#trunk-did-container .trunk-did-row').length;
|
||||
const badge = document.getElementById('trunk-inbound-rules-badge');
|
||||
|
||||
Reference in new issue
Block a user