v2.6.7: Replace Admin & UCP emojis with uniform Heroicons SVG icons

This commit is contained in:
sorcier committed 2026-11-12 10:25:40 +01:00
1 parent 70a021ef63
commit ddbaabb657
5 files changed
+198 -124

No files matched your search

+57 -29
View File
@@ -25,6 +25,28 @@ function escapeHtml(str) {
.replace(/'/g, ''');
}
function ucpIcon(name, cls = 'w-4 h-4') {
const paths = {
voicemail: '<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"/>',
bolt: '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z"/>',
calendar: '<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5"/>',
clock: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"/>',
download: '<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/>',
trash: '<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/>',
phone: '<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"/>',
mobile: '<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"/>',
mail: '<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"/>',
pencil: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z"/>',
forward: '<path stroke-linecap="round" stroke-linejoin="round" d="M15 15l6-6m0 0l-6-6m6 6H9a6 6 0 000 12h3"/>',
pickup: '<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75v-4.5m0 4.5h4.5m-4.5 0l6-6m-3 18c-8.284 0-15-6.716-15-15V4.5A2.25 2.25 0 014.5 2.25h1.372c.516 0 .966.351 1.091.852l1.106 4.423c.11.44-.054.902-.417 1.173l-1.293.97a1.062 1.062 0 00-.38 1.21 12.035 12.035 0 007.143 7.143c.441.162.928-.004 1.21-.38l.97-1.293a1.125 1.125 0 011.173-.417l4.423 1.106c.5.125.852.575.852 1.091V19.5a2.25 2.25 0 01-2.25 2.25h-2.25z"/>',
dnd: '<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"/>',
shield: '<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"/>',
wrench: '<path stroke-linecap="round" stroke-linejoin="round" d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085"/>',
tag: '<path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z"/>',
};
return `<svg class="${cls} shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">${paths[name] || paths.tag}</svg>`;
}
function showUcpToast(message, type = 'info') {
const container = document.getElementById('ucp-toast-container');
if (!container) return;
@@ -232,14 +254,14 @@ function buildForwardOptions(extObj, selectedValue) {
if (Number(extObj.voicemail_enabled) === 1) {
const vmVal = `vm-${extObj.extension}`;
html += `<option value="${escapeHtml(vmVal)}" ${val === vmVal ? 'selected' : ''}>📬 Ma messagerie vocale (${escapeHtml(extObj.extension)})</option>`;
html += `<option value="${escapeHtml(vmVal)}" ${val === vmVal ? 'selected' : ''}>Ma messagerie vocale (${escapeHtml(extObj.extension)})</option>`;
}
const others = (ucpState.forward_targets || []).filter(t => t.extension !== extObj.extension);
if (others.length > 0) {
html += `<optgroup label="Postes internes">`;
others.forEach(t => {
html += `<option value="${escapeHtml(t.extension)}" ${val === t.extension ? 'selected' : ''}>📱 ${escapeHtml(t.extension)} — ${escapeHtml(t.name)}</option>`;
html += `<option value="${escapeHtml(t.extension)}" ${val === t.extension ? 'selected' : ''}>${escapeHtml(t.extension)} — ${escapeHtml(t.name)}</option>`;
});
html += `</optgroup>`;
}
@@ -249,7 +271,7 @@ function buildForwardOptions(extObj, selectedValue) {
html += `<optgroup label="Messageries vocales d'autres postes">`;
otherVms.forEach(t => {
const vVal = `vm-${t.extension}`;
html += `<option value="${escapeHtml(vVal)}" ${val === vVal ? 'selected' : ''}>📬 Répondeur ${escapeHtml(t.extension)} — ${escapeHtml(t.name)}</option>`;
html += `<option value="${escapeHtml(vVal)}" ${val === vVal ? 'selected' : ''}>Répondeur ${escapeHtml(t.extension)} — ${escapeHtml(t.name)}</option>`;
});
html += `</optgroup>`;
}
@@ -286,7 +308,8 @@ function renderExtensionsGrid() {
const vmPill = Number(ext.voicemail_enabled) === 1
? `<button type="button" onclick="openExtensionVoicemail('${escapeHtml(ext.extension)}')"
class="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-[11px] font-semibold border transition ${ext.vm_inbox_count > 0 ? 'bg-rose-50 border-rose-200 text-rose-700 hover:bg-rose-100' : 'bg-slate-50 border-slate-200 text-slate-600 hover:bg-slate-100'}">
📬 ${ext.vm_inbox_count} nouveau(x)
${ucpIcon('voicemail', 'w-3.5 h-3.5')}
<span>${ext.vm_inbox_count} nouveau(x)</span>
</button>`
: '';
@@ -374,13 +397,13 @@ function renderExtensionsGrid() {
<div class="flex flex-wrap items-center gap-2">
<a href="${escapeHtml(ext.groundwire_deeplink_url)}"
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-xl bg-indigo-50 hover:bg-indigo-100 text-indigo-700 border border-indigo-200 text-xs font-semibold transition flex-1 sm:flex-initial">
<span>⚡</span>
${ucpIcon('bolt', 'w-3.5 h-3.5')}
<span>Configurer dans Groundwire</span>
</a>
<a href="${escapeHtml(ext.groundwire_xml_url)}"
download="groundwire-${escapeHtml(ext.extension)}.xml"
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-xl bg-white hover:bg-slate-100 text-slate-700 border border-slate-200 text-xs font-medium transition">
<span>⬇</span>
${ucpIcon('download', 'w-3.5 h-3.5')}
<span>Télécharger le XML</span>
</a>
</div>
@@ -546,21 +569,25 @@ function renderVoicemailsList() {
<span class="font-bold text-slate-900 text-sm">${escapeHtml(msg.callerid)}</span>
${badge}
</div>
<div class="text-xs text-slate-500 mt-0.5">
📅 ${escapeHtml(msg.date_human)} • ⏱️ ${formatDuration(msg.duration)}
<div class="text-xs text-slate-500 mt-0.5 flex items-center gap-2 flex-wrap">
<span class="inline-flex items-center gap-1">${ucpIcon('calendar', 'w-3.5 h-3.5 text-slate-400')} ${escapeHtml(msg.date_human)}</span>
<span>•</span>
<span class="inline-flex items-center gap-1">${ucpIcon('clock', 'w-3.5 h-3.5 text-slate-400')} ${formatDuration(msg.duration)}</span>
</div>
</div>
<div class="flex items-center gap-1.5 flex-wrap">
<a href="${dlUrl}" class="px-2.5 py-1.5 rounded-lg border border-slate-200 bg-white hover:bg-slate-50 text-[11px] font-semibold text-slate-700 transition">
⬇️ Télécharger
<a href="${dlUrl}" class="inline-flex items-center gap-1 px-2.5 py-1.5 rounded-lg border border-slate-200 bg-white hover:bg-slate-50 text-[11px] font-semibold text-slate-700 transition">
${ucpIcon('download', 'w-3.5 h-3.5 text-slate-500')}
<span>Télécharger</span>
</a>
<button type="button" onclick="toggleVmRead('${escapeHtml(msg.extension)}', '${escapeHtml(msg.folder)}', '${escapeHtml(msg.id)}')"
class="px-2.5 py-1.5 rounded-lg border border-slate-200 bg-white hover:bg-slate-50 text-[11px] font-semibold text-slate-700 transition">
${msg.is_new ? '✓ Marquer lu' : '↺ Marquer non lu'}
</button>
<button type="button" onclick="deleteVm('${escapeHtml(msg.extension)}', '${escapeHtml(msg.folder)}', '${escapeHtml(msg.id)}')"
class="px-2.5 py-1.5 rounded-lg border border-rose-200 bg-rose-50 hover:bg-rose-100 text-[11px] font-semibold text-rose-700 transition">
🗑️ Supprimer
class="inline-flex items-center gap-1 px-2.5 py-1.5 rounded-lg border border-rose-200 bg-rose-50 hover:bg-rose-100 text-[11px] font-semibold text-rose-700 transition">
${ucpIcon('trash', 'w-3.5 h-3.5')}
<span>Supprimer</span>
</button>
</div>
</div>
@@ -722,7 +749,7 @@ async function loadPersonalCdr() {
${dirBadge}
${stBadge}
</div>
<span class="font-mono text-xs font-semibold text-slate-700">⏱ ${durText}</span>
<span class="inline-flex items-center gap-1 font-mono text-xs font-semibold text-slate-700">${ucpIcon('clock', 'w-3.5 h-3.5 text-slate-400')} ${durText}</span>
</div>
<div class="text-xs leading-snug break-words flex flex-wrap items-center gap-1.5">
<span>${formatInlineParty(srcName, srcNum)}</span>
@@ -864,17 +891,18 @@ function renderPhonebookGrid() {
</div>
<div class="text-[11px] text-slate-400">${escapeHtml(c.group_name || 'Général')}</div>
<div class="mt-2 space-y-1 text-xs text-slate-600">
${c.internal_number ? `<div class="font-mono">📱 Interne : <strong class="text-slate-900">${escapeHtml(c.internal_number)}</strong></div>` : ''}
${c.mobile_number ? `<div class="font-mono">📲 Mobile : ${escapeHtml(c.mobile_number)}</div>` : ''}
${c.other_number ? `<div class="font-mono">☎️ Autre : ${escapeHtml(c.other_number)}</div>` : ''}
${c.email ? `<div class="truncate">✉️ ${escapeHtml(c.email)}</div>` : ''}
${c.internal_number ? `<div class="font-mono flex items-center gap-1.5">${ucpIcon('phone', 'w-3.5 h-3.5 text-indigo-500')} Interne : <strong class="text-slate-900">${escapeHtml(c.internal_number)}</strong></div>` : ''}
${c.mobile_number ? `<div class="font-mono flex items-center gap-1.5">${ucpIcon('mobile', 'w-3.5 h-3.5 text-emerald-500')} Mobile : ${escapeHtml(c.mobile_number)}</div>` : ''}
${c.other_number ? `<div class="font-mono flex items-center gap-1.5">${ucpIcon('phone', 'w-3.5 h-3.5 text-slate-400')} Autre : ${escapeHtml(c.other_number)}</div>` : ''}
${c.email ? `<div class="truncate flex items-center gap-1.5">${ucpIcon('mail', 'w-3.5 h-3.5 text-slate-400')} <span class="truncate">${escapeHtml(c.email)}</span></div>` : ''}
</div>
</div>
</div>
${c.is_mine ? `
<button type="button" onclick="openProfileModal('${escapeHtml(c.mine_extension)}')"
class="w-full py-1.5 px-3 rounded-xl bg-indigo-50 hover:bg-indigo-100 text-indigo-700 text-xs font-semibold border border-indigo-200 transition">
✏️ Modifier ma photo / mon e-mail
class="w-full py-1.5 px-3 rounded-xl bg-indigo-50 hover:bg-indigo-100 text-indigo-700 text-xs font-semibold border border-indigo-200 transition inline-flex items-center justify-center gap-1.5">
${ucpIcon('pencil', 'w-3.5 h-3.5')}
<span>Modifier ma photo / mon e-mail</span>
</button>
` : ''}
</div>
@@ -996,7 +1024,7 @@ function renderFeatureGuide() {
quickBox.innerHTML = `
<div class="ucp-print-avoid-break rounded-2xl border border-indigo-200 bg-indigo-50/70 p-4 space-y-2">
<div class="flex items-center gap-2 text-xs font-bold text-indigo-900 uppercase tracking-wide">
<span>📬</span>
${ucpIcon('voicemail', 'w-4 h-4 text-indigo-600')}
<span>Utiliser la Messagerie Vocale</span>
</div>
<ul class="text-[11px] text-slate-700 space-y-1.5 leading-relaxed">
@@ -1009,7 +1037,7 @@ function renderFeatureGuide() {
<div class="ucp-print-avoid-break rounded-2xl border border-amber-200 bg-amber-50/70 p-4 space-y-2">
<div class="flex items-center gap-2 text-xs font-bold text-amber-900 uppercase tracking-wide">
<span>↪️</span>
${ucpIcon('forward', 'w-4 h-4 text-amber-600')}
<span>Gérer Renvois &amp; Ne Pas Déranger</span>
</div>
<ul class="text-[11px] text-slate-700 space-y-1.5 leading-relaxed">
@@ -1022,7 +1050,7 @@ function renderFeatureGuide() {
<div class="ucp-print-avoid-break rounded-2xl border border-emerald-200 bg-emerald-50/70 p-4 space-y-2">
<div class="flex items-center gap-2 text-xs font-bold text-emerald-900 uppercase tracking-wide">
<span>🎧</span>
${ucpIcon('pickup', 'w-4 h-4 text-emerald-600')}
<span>Interception &amp; Prise d'Appel</span>
</div>
<ul class="text-[11px] text-slate-700 space-y-1.5 leading-relaxed">
@@ -1049,12 +1077,12 @@ function renderFeatureGuide() {
}
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' },
voicemail: { label: 'Messagerie Vocale', icon: ucpIcon('voicemail', 'w-4 h-4 text-indigo-600'), 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: ucpIcon('forward', 'w-4 h-4 text-amber-600'), 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: ucpIcon('dnd', 'w-4 h-4 text-rose-600'), 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: ucpIcon('pickup', 'w-4 h-4 text-emerald-600'), 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: ucpIcon('shield', 'w-4 h-4 text-slate-600'), 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: ucpIcon('wrench', 'w-4 h-4 text-sky-600'), 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'];
@@ -1071,7 +1099,7 @@ function renderFeatureGuide() {
];
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 meta = catMeta[catKey] || { label: catKey, icon: ucpIcon('tag', 'w-4 h-4 text-slate-500'), 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 => `
+21 -9
View File
@@ -122,7 +122,9 @@ $csrfToken = UcpAuth::getCsrfToken();
<?php if (!$ucpEnabled): ?>
<div class="min-h-screen flex items-center justify-center p-6">
<div class="max-w-md w-full ucp-card rounded-2xl p-8 text-center space-y-3">
<div class="w-12 h-12 mx-auto rounded-xl bg-amber-50 border border-amber-200 flex items-center justify-center text-amber-600 text-2xl">🔒</div>
<div class="w-12 h-12 mx-auto rounded-xl bg-amber-50 border border-amber-200 flex items-center justify-center text-amber-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"/></svg>
</div>
<h1 class="text-lg font-bold text-slate-900">Portail Utilisateur désactivé</h1>
<p class="text-sm text-slate-500">Ce service a été temporairement désactivé par votre administrateur système.</p>
</div>
@@ -216,29 +218,35 @@ $csrfToken = UcpAuth::getCsrfToken();
<nav class="flex items-center gap-1 py-2 whitespace-nowrap pr-6 sm:pr-0" id="ucp-nav">
<button type="button" data-ucp-tab="extensions" onclick="switchUcpTab('extensions')"
class="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">
<span>📱 Mes Postes &amp; Renvois</span>
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"/></svg>
<span>Mes Postes &amp; Renvois</span>
<span id="ucp-badge-exts" class="px-1.5 py-0.5 rounded-full bg-indigo-100 text-indigo-700 text-[10px] font-bold">0</span>
</button>
<button type="button" data-ucp-tab="voicemail" onclick="switchUcpTab('voicemail')"
class="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">
<span>📬 Messagerie Vocale</span>
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><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"/></svg>
<span>Messagerie Vocale</span>
<span id="ucp-badge-vm" class="px-1.5 py-0.5 rounded-full bg-slate-200 text-slate-700 text-[10px] font-bold">0</span>
</button>
<button type="button" data-ucp-tab="cdr" onclick="switchUcpTab('cdr')"
class="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">
<span>📞 Mes Appels</span>
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><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"/></svg>
<span>Mes Appels</span>
</button>
<button type="button" data-ucp-tab="phonebook" onclick="switchUcpTab('phonebook')"
class="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">
<span>📖 Annuaire &amp; Profil</span>
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"/></svg>
<span>Annuaire &amp; Profil</span>
</button>
<button type="button" data-ucp-tab="guide" onclick="switchUcpTab('guide')"
class="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">
<span>📋 Notice &amp; Codes</span>
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25z"/></svg>
<span>Notice &amp; Codes</span>
</button>
<button type="button" data-ucp-tab="account" onclick="switchUcpTab('account')"
class="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">
<span>🔐 Mon Compte</span>
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"/></svg>
<span>Mon Compte</span>
</button>
</nav>
</div>
@@ -451,7 +459,10 @@ $csrfToken = UcpAuth::getCsrfToken();
<!-- Pied de document -->
<div class="ucp-print-avoid-break pt-4 border-t border-slate-200 flex flex-col sm:flex-row sm:items-center justify-between gap-2 text-[11px] text-slate-500">
<div>💡 <strong>Astuce :</strong> composez ces codes directement sur votre combiné IP, ou pilotez DND / renvois / messagerie depuis le portail UCP.</div>
<div class="flex items-center gap-1.5">
<svg class="w-4 h-4 text-amber-500 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"/></svg>
<span><strong>Astuce :</strong> composez ces codes directement sur votre combiné IP, ou pilotez DND / renvois / messagerie depuis le portail UCP.</span>
</div>
<div class="font-semibold text-slate-600"><?= htmlspecialchars($companyName ?: 'NexusPBX') ?> — Document interne</div>
</div>
</div>
@@ -531,7 +542,8 @@ $csrfToken = UcpAuth::getCsrfToken();
<div id="ucp-profile-initials" class="w-14 h-14 rounded-full bg-indigo-100 text-indigo-700 font-bold flex items-center justify-center text-lg">?</div>
<div class="flex-1 min-w-0">
<label class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-white border border-slate-300 hover:bg-slate-100 text-xs font-semibold text-slate-700 cursor-pointer transition">
📷 Choisir une photo
<svg class="w-4 h-4 text-slate-500 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 015.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 00-1.134-.175 2.31 2.31 0 01-1.64-1.055l-.822-1.316a2.192 2.192 0 00-1.736-1.039 48.774 48.774 0 00-5.232 0 2.192 2.192 0 00-1.736 1.039l-.821 1.316z"/><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0z"/></svg>
<span>Choisir une photo</span>
<input type="file" id="ucp-avatar-input" accept="image/jpeg,image/png,image/webp" class="hidden" onchange="uploadSelectedAvatar()">
</label>
<p class="text-[11px] text-slate-400 mt-1">JPG, PNG ou WebP • 5 Mo max.</p>