Files
html/dsh-vm-alerts-widget.js
2026-04-18 16:00:03 +02:00

152 lines
8.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* DG Command Center Widget · Visual Management enrichment · 18avr2026
* Self-contained · zero external deps · safe include anywhere.
* Fetches live /api/wevia-v69-dg-command-center.php and renders:
* - 7 business alerts DG (critical/high/medium)
* - 5 structured risks (likelihood × impact)
* - Conversion funnel + pipeline value
* Pattern: glass-card premium, follows dsh-predict-widget.js
* Yacine Mahboub · WEVAL Consulting
*/
(function(){
if (window.__DSH_VM_ALERTS_LOADED__) return;
window.__DSH_VM_ALERTS_LOADED__ = true;
const API_DG = '/api/wevia-v69-dg-command-center.php';
const REFRESH_MS = 60000; // 1 min
// ── Styles (scoped .dsh-vm prefix) ──
const css = `
.dsh-vm-wrap{position:relative;margin:24px 0;font-family:-apple-system,'Segoe UI',Roboto,sans-serif;color:#e6e9f0}
.dsh-vm-card{
background:linear-gradient(135deg,rgba(20,25,40,.78),rgba(30,15,55,.6));
border:1px solid rgba(120,140,200,.25);
border-radius:14px;padding:22px;backdrop-filter:blur(12px);
box-shadow:0 10px 40px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.05);
}
.dsh-vm-head{display:flex;align-items:center;gap:12px;margin-bottom:16px;padding-bottom:14px;border-bottom:1px dashed rgba(120,140,200,.2)}
.dsh-vm-head h3{margin:0;font-size:17px;font-weight:600;color:#c4d0ff;letter-spacing:.3px}
.dsh-vm-head .badge{padding:4px 10px;border-radius:99px;font-size:10.5px;font-weight:600;letter-spacing:.4px;text-transform:uppercase}
.dsh-vm-head .badge.live{background:rgba(16,185,129,.2);color:#6ee7b7;border:1px solid rgba(16,185,129,.4)}
.dsh-vm-head .meta{margin-left:auto;font-size:11px;color:#8b95b8;font-family:'SF Mono',Monaco,monospace}
.dsh-vm-stats{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:10px;margin-bottom:18px}
.dsh-vm-stat{background:rgba(255,255,255,.03);border:1px solid rgba(120,140,200,.15);border-radius:9px;padding:11px 13px}
.dsh-vm-stat .k{font-size:10px;color:#8b95b8;text-transform:uppercase;letter-spacing:.4px;margin-bottom:4px}
.dsh-vm-stat .v{font-size:20px;font-weight:700;color:#e6e9f0;font-family:'SF Mono',Monaco,monospace}
.dsh-vm-stat .v.crit{color:#fca5a5}
.dsh-vm-stat .v.high{color:#fbbf24}
.dsh-vm-stat .v.good{color:#6ee7b7}
.dsh-vm-alerts{display:flex;flex-direction:column;gap:9px;margin-bottom:16px}
.dsh-vm-alert{display:flex;gap:14px;align-items:flex-start;padding:13px 15px;border-radius:9px;background:rgba(255,255,255,.02);border-left:4px solid #6ba3ff;transition:all .2s}
.dsh-vm-alert:hover{background:rgba(255,255,255,.05);transform:translateX(2px)}
.dsh-vm-alert.critical{border-left-color:#ef4444;background:linear-gradient(90deg,rgba(239,68,68,.08),rgba(255,255,255,.02))}
.dsh-vm-alert.high{border-left-color:#f59e0b;background:linear-gradient(90deg,rgba(245,158,11,.06),rgba(255,255,255,.02))}
.dsh-vm-alert.medium{border-left-color:#6ba3ff}
.dsh-vm-alert .icon{font-size:22px;line-height:1;flex-shrink:0;margin-top:2px}
.dsh-vm-alert .body{flex:1}
.dsh-vm-alert .title{font-size:13.5px;font-weight:600;color:#e6e9f0;margin-bottom:4px}
.dsh-vm-alert .detail{font-size:12px;color:#a8b2d4;line-height:1.5}
.dsh-vm-alert .meta-row{display:flex;gap:12px;margin-top:6px;font-size:10.5px;color:#8b95b8}
.dsh-vm-alert .deadline{font-family:'SF Mono',Monaco,monospace;color:#fbbf24}
.dsh-vm-alert a{color:#93c5fd;text-decoration:none;border-bottom:1px dashed rgba(147,197,253,.3)}
.dsh-vm-alert a:hover{color:#dbeafe;border-bottom-color:#dbeafe}
.dsh-vm-risks{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px}
.dsh-vm-risk{background:rgba(255,255,255,.03);border:1px solid rgba(120,140,200,.18);border-radius:9px;padding:11px 13px;position:relative}
.dsh-vm-risk.critical{border-color:rgba(239,68,68,.4)}
.dsh-vm-risk.high{border-color:rgba(245,158,11,.4)}
.dsh-vm-risk .id{font-size:10px;color:#8b95b8;font-family:'SF Mono',Monaco,monospace;letter-spacing:.5px}
.dsh-vm-risk .t{font-size:13px;font-weight:600;color:#e6e9f0;margin:3px 0 5px}
.dsh-vm-risk .m{font-size:11px;color:#a8b2d4;line-height:1.5}
.dsh-vm-loading{text-align:center;padding:28px;color:#8b95b8;font-size:12px}
.dsh-vm-error{padding:15px;background:rgba(239,68,68,.08);border:1px solid rgba(239,68,68,.3);border-radius:9px;color:#fca5a5;font-size:12px}
`;
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
// ── HTML skeleton ──
const wrap = document.createElement('div');
wrap.className = 'dsh-vm-wrap';
wrap.innerHTML = `
<div class="dsh-vm-card">
<div class="dsh-vm-head">
<h3>🎯 DG Command Center · Alertes business live</h3>
<span class="badge live">● LIVE</span>
<span class="meta" id="dsh-vm-ts">—</span>
</div>
<div id="dsh-vm-body" class="dsh-vm-loading">Chargement alertes DG…</div>
</div>`;
// Inject at target or body end
const target = document.getElementById('dsh-vm-alerts-mount') || document.body;
target.appendChild(wrap);
// ── Fetch + render ──
async function refresh(){
try {
const r = await fetch(API_DG, { cache: 'no-store' });
const d = await r.json();
const body = document.getElementById('dsh-vm-body');
const ts = document.getElementById('dsh-vm-ts');
const s = d.summary || {};
ts.textContent = (s.timestamp || '').replace('T',' ').split('.')[0] + ' UTC';
// Stats row
const statsHtml = `
<div class="dsh-vm-stats">
<div class="dsh-vm-stat"><div class="k">Bottleneck TOC</div><div class="v high">${s.toc_bottleneck_label||'—'}</div></div>
<div class="dsh-vm-stat"><div class="k">Conversion</div><div class="v">${((s.conversion_overall_pct||0)*100).toFixed(2)}%</div></div>
<div class="dsh-vm-stat"><div class="k">Pipeline k€</div><div class="v">${s.pipeline_value_keur||0}</div></div>
<div class="dsh-vm-stat"><div class="k">Clients actifs</div><div class="v">${s.active_clients||0}</div></div>
<div class="dsh-vm-stat"><div class="k">Alertes</div><div class="v crit">${s.alerts_dg_count||0}</div></div>
<div class="dsh-vm-stat"><div class="k">Risques critiques</div><div class="v crit">${s.risks_critical||0}</div></div>
<div class="dsh-vm-stat"><div class="k">Funnel leaks</div><div class="v high">${s.funnel_leaks_count||0}</div></div>
<div class="dsh-vm-stat"><div class="k">Data pipeline</div><div class="v good">${s.data_pipeline_health_pct||0}%</div></div>
</div>`;
const alertsArr = d.alerts_dg || [];
const alertsHtml = alertsArr.length ? `
<h4 style="margin:6px 0 10px;color:#c4d0ff;font-size:13px;font-weight:600">🚨 Alertes DG (${alertsArr.length})</h4>
<div class="dsh-vm-alerts">
${alertsArr.map(a => `
<div class="dsh-vm-alert ${a.level||'medium'}">
<div class="icon">${a.icon||'•'}</div>
<div class="body">
<div class="title">${escapeHtml(a.title||'')}</div>
<div class="detail">${escapeHtml(a.detail||'')}</div>
<div class="meta-row">
${a.owner?`<span>👤 ${escapeHtml(a.owner)}</span>`:''}
${a.deadline?`<span class="deadline">⏰ ${escapeHtml(a.deadline)}</span>`:''}
${a.action_link && a.action_link!=='#'?`<a href="${escapeAttr(a.action_link)}" target="_blank">→ Action</a>`:''}
</div>
</div>
</div>`).join('')}
</div>` : '';
const risksArr = d.risks || [];
const risksHtml = risksArr.length ? `
<h4 style="margin:16px 0 10px;color:#c4d0ff;font-size:13px;font-weight:600">⚠️ Risques (${risksArr.length})</h4>
<div class="dsh-vm-risks">
${risksArr.slice(0,6).map(r => `
<div class="dsh-vm-risk ${r.priority||''}">
<div class="id">${escapeHtml(r.id||'')} · ${escapeHtml(r.category||'')}</div>
<div class="t">${escapeHtml(r.title||'')}</div>
<div class="m">${escapeHtml(r.mitigation||'')}</div>
</div>`).join('')}
</div>` : '';
body.innerHTML = statsHtml + alertsHtml + risksHtml;
body.classList.remove('dsh-vm-loading');
} catch(e) {
document.getElementById('dsh-vm-body').innerHTML =
`<div class="dsh-vm-error">⚠ Erreur fetch DG : ${escapeHtml(e.message)}</div>`;
}
}
function escapeHtml(s){return String(s||'').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}
function escapeAttr(s){return escapeHtml(s);}
// First render + periodic refresh
refresh();
setInterval(refresh, REFRESH_MS);
})();