Files
html/lean6sigma-dashboard.html
opus e30ddf5007
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
auto-sync via WEVIA git_sync_all intent 2026-04-20T13:11:38+02:00
2026-04-20 13:11:38 +02:00

169 lines
16 KiB
HTML
Raw 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.
<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"><title>Lean 6σ Dashboard — WEVIA EM</title><style>
*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,sans-serif;background:#0a0e1a;color:#e2e8f0;padding:20px}
.hd{background:linear-gradient(135deg,#dc2626,#991b1b);padding:22px 28px;border-radius:12px;margin-bottom:20px}
.hd h1{color:white;font-size:26px}.hd .sub{color:rgba(255,255,255,.85);margin-top:6px;font-size:13px}
.maturity{display:grid;grid-template-columns:1fr auto;gap:20px;align-items:center;background:linear-gradient(135deg,#7c2d12,#431407);padding:20px;border-radius:12px;margin-bottom:20px}
.maturity .label{font-size:13px;color:#fcd34d}
.maturity .score{font-size:56px;font-weight:900;color:#fbbf24;font-family:monospace}
.maturity .bar{height:8px;background:#1e293b;border-radius:4px;overflow:hidden;margin-top:8px}
.maturity .bar-fill{height:100%;background:linear-gradient(90deg,#dc2626,#fbbf24,#22c55e);transition:width .5s}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;margin-bottom:20px}
.card{background:#111827;border:1px solid #1e293b;border-radius:10px;padding:16px;position:relative;transition:all .2s}
.card:hover{border-color:#dc2626;transform:translateY(-2px)}
.card h3{font-size:13px;color:#fbbf24;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px}
.card .n{font-size:32px;font-weight:800;color:#e2e8f0;font-family:monospace}
.card .unit{font-size:14px;color:#64748b;margin-left:4px}
.card .l{font-size:11px;color:#94a3b8;margin-top:6px}
.card .icon{position:absolute;top:16px;right:16px;font-size:22px;opacity:.7}
.section{margin-top:28px}
.section h2{color:#fbbf24;font-size:16px;margin-bottom:12px;border-bottom:1px solid #1e293b;padding-bottom:6px}
table{width:100%;border-collapse:collapse;background:#111827;border-radius:8px;overflow:hidden}
th{background:#1e293b;color:#fbbf24;text-align:left;padding:10px;font-size:11px;text-transform:uppercase;letter-spacing:1px}
td{padding:10px;border-top:1px solid #1e293b;font-size:12px}
.badge{padding:2px 8px;border-radius:4px;font-size:10px;font-weight:600}
.sev-5{background:rgba(220,38,38,.2);color:#f87171}
.sev-4{background:rgba(234,88,12,.2);color:#fb923c}
.sev-3{background:rgba(234,179,8,.2);color:#facc15}
.sev-2{background:rgba(34,197,94,.2);color:#4ade80}
.sev-1{background:rgba(59,130,246,.2);color:#60a5fa}
.status-completed{background:rgba(34,197,94,.2);color:#22c55e}
.status-in-progress{background:rgba(234,179,8,.2);color:#facc15}
.status-identified{background:rgba(59,130,246,.2);color:#60a5fa}
.status-analyzed{background:rgba(168,85,247,.2);color:#c084fc}
.status-fixed{background:rgba(34,197,94,.2);color:#22c55e}
.status-fixing{background:rgba(234,179,8,.2);color:#facc15}
.status-planned{background:rgba(107,114,128,.2);color:#9ca3af}
.andon-red{color:#f87171}.andon-yellow{color:#facc15}.andon-green{color:#4ade80}
.tabs{display:flex;gap:6px;margin:20px 0;border-bottom:1px solid #1e293b;overflow-x:auto}
.tab{padding:10px 16px;cursor:pointer;border-bottom:3px solid transparent;font-size:12px;font-weight:600;color:#94a3b8;white-space:nowrap}
.tab.active{border-color:#fbbf24;color:#fbbf24}
.panel{display:none}.panel.active{display:block}
.tenant-sel{display:flex;gap:10px;align-items:center;margin-bottom:16px}
.tenant-sel select{padding:8px;background:#111827;border:1px solid #1e293b;color:#e2e8f0;border-radius:6px}
</style></head><body>
<div class="hd"><h1>🎯 Lean 6σ Dashboard</h1><div class="sub">Muda · Poka-Yoke · Kaizen · Gemba · PDCA · Andon · 5S · A3 — pilotage opérationnel WEVIA EM</div></div>
<div class="tenant-sel"><label>Tenant:</label><select id="tenant" onchange="reload()"></select></div>
<div class="maturity">
<div>
<div class="label">MATURITY SCORE (Lean 6σ global)</div>
<div class="bar"><div class="bar-fill" id="mat-bar" style="width:0%"></div></div>
<div style="font-size:11px;color:#fcd34d;margin-top:6px">Composé de: 5S (30%) + Poka-Yoke efficiency (30%) + Kaizen volume (20%) + Gemba frequency (20%)</div>
</div>
<div class="score" id="mat-score">-</div>
</div>
<div class="grid" id="stats">Chargement...</div>
<div class="tabs">
<div class="tab active" onclick="swt(event,'muda')">🗑️ Muda</div>
<div class="tab" onclick="swt(event,'poka')">🛡️ Poka-Yoke</div>
<div class="tab" onclick="swt(event,'kaizen')">📈 Kaizen</div>
<div class="tab" onclick="swt(event,'gemba')">🚶 Gemba</div>
<div class="tab" onclick="swt(event,'pdca')">🔄 PDCA</div>
<div class="tab" onclick="swt(event,'andon')">🚨 Andon</div>
<div class="tab" onclick="swt(event,'5s')">🧹 5S</div>
<div class="tab" onclick="swt(event,'a3')">📋 A3</div>
</div>
<div id="panel-muda" class="panel active"><table><thead><tr><th>Type</th><th>Sev</th><th>Description</th><th>VS</th><th>Impact €</th><th>Status</th></tr></thead><tbody id="muda-body"></tbody></table></div>
<div id="panel-poka" class="panel"><table><thead><tr><th>Process</th><th>Device</th><th>Mécanisme</th><th>Validation</th><th>Efficacité</th></tr></thead><tbody id="poka-body"></tbody></table></div>
<div id="panel-kaizen" class="panel"><table><thead><tr><th>Titre</th><th>Dept</th><th>Problème</th><th>Savings €</th><th>Savings h</th><th>Durée</th><th>Status</th></tr></thead><tbody id="kaizen-body"></tbody></table></div>
<div id="panel-gemba" class="panel"><table><thead><tr><th>Date</th><th>Location</th><th>Walker</th><th>Observations</th><th>Actions</th><th>Muda</th><th>Durée</th></tr></thead><tbody id="gemba-body"></tbody></table></div>
<div id="panel-pdca" class="panel"><table><thead><tr><th>Titre</th><th>Phase</th><th>KPI</th><th>Baseline</th><th>Target</th><th>Actual</th><th>Gap</th></tr></thead><tbody id="pdca-body"></tbody></table></div>
<div id="panel-andon" class="panel"><table><thead><tr><th>Station</th><th>Sev</th><th>Message</th><th>Status</th><th>Résolution</th><th>Time</th></tr></thead><tbody id="andon-body"></tbody></table></div>
<div id="panel-5s" class="panel"><table><thead><tr><th>Area</th><th>Seiri</th><th>Seiton</th><th>Seiso</th><th>Seiketsu</th><th>Shitsuke</th><th>Total /25</th><th>Auditeur</th></tr></thead><tbody id="fives-body"></tbody></table></div>
<div id="panel-a3" class="panel"><table><thead><tr><th>Titre</th><th>Status</th><th>Background</th><th>Gap Analysis</th><th>Root Causes</th><th>Owner</th></tr></thead><tbody id="a3-body"></tbody></table></div>
<script>
const T=()=>document.getElementById('tenant').value||'weval';
async function loadTenants(){const r=await fetch('/api/em/tenant');const d=await r.json();const s=document.getElementById('tenant');s.innerHTML=(d.tenants||[]).map(t=>`<option value="${t.tenant_id}">${t.tenant_id}${t.name}</option>`).join('')}
function swt(e,p){document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));e.target.classList.add('active');document.querySelectorAll('.panel').forEach(x=>x.classList.remove('active'));document.getElementById('panel-'+p).classList.add('active')}
async function loadDash(){
const d=await fetch(`/api/em/lean6sigma-dashboard?tenant=${T()}`).then(r=>r.json());
const m=d.maturity_score||0;
document.getElementById('mat-score').textContent=m;
document.getElementById('mat-bar').style.width=m+'%';
document.getElementById('stats').innerHTML=`
<div class="card"><div class="icon">🗑️</div><h3>Muda</h3><div class="n">${d.muda?.count||0}</div><div class="l">${(d.muda?.impact_euro||0).toLocaleString()}€ impact · ${d.muda?.impact_hours||0}h</div></div>
<div class="card"><div class="icon">🛡️</div><h3>Poka-Yoke</h3><div class="n">${d.poka_yoke?.count||0}</div><div class="l">${d.poka_yoke?.avg_efficiency_pct||0}% efficacité moyenne</div></div>
<div class="card"><div class="icon">📈</div><h3>Kaizen</h3><div class="n">${d.kaizen?.count||0}</div><div class="l">${(d.kaizen?.total_savings_euro||0).toLocaleString()}€ économisés</div></div>
<div class="card"><div class="icon">🚶</div><h3>Gemba</h3><div class="n">${d.gemba?.walks||0}</div><div class="l">${d.gemba?.muda_spotted||0} muda spotted</div></div>
<div class="card"><div class="icon">🔄</div><h3>PDCA Active</h3><div class="n">${d.pdca_active||0}</div><div class="l">Cycles Plan/Do/Check</div></div>
<div class="card"><div class="icon">🚨</div><h3>Andon Open</h3><div class="n">${d.andon_open||0}</div><div class="l">Alertes actives</div></div>
<div class="card"><div class="icon">🧹</div><h3>5S Avg</h3><div class="n">${d.five_s_avg_score||0}<span class="unit">/25</span></div><div class="l">Maturité 5S</div></div>
<div class="card"><div class="icon">📋</div><h3>A3 Open</h3><div class="n">${d.a3_open||0}</div><div class="l">Problem-solving actifs</div></div>`;
}
async function loadMuda(){const d=await fetch(`/api/em/muda?tenant=${T()}`).then(r=>r.json());document.getElementById('muda-body').innerHTML=(d.entries||[]).map(e=>`<tr><td>${e.muda_type}</td><td><span class="badge sev-${e.severity}">sev${e.severity}</span></td><td>${e.description}</td><td>${e.vs_id||'-'}</td><td>${Number(e.impact_euro||0).toLocaleString()}€</td><td><span class="badge status-${e.status}">${e.status}</span></td></tr>`).join('')}
async function loadPoka(){const d=await fetch(`/api/em/poka-yoke?tenant=${T()}`).then(r=>r.json());document.getElementById('poka-body').innerHTML=(d.devices||[]).map(p=>`<tr><td>${p.process}</td><td><span class="badge">${p.device_type}</span></td><td>${p.mechanism}</td><td>${p.validation}</td><td>${p.efficiency_pct}%</td></tr>`).join('')}
async function loadKaizen(){const d=await fetch(`/api/em/kaizen?tenant=${T()}`).then(r=>r.json());document.getElementById('kaizen-body').innerHTML=(d.events||[]).map(k=>`<tr><td>${k.title}</td><td>${k.dept}</td><td>${k.problem}</td><td>${Number(k.savings_euro||0).toLocaleString()}€</td><td>${k.savings_hours||0}h</td><td>${k.duration_days||0}j</td><td><span class="badge status-${k.status}">${k.status}</span></td></tr>`).join('')}
async function loadGemba(){const d=await fetch(`/api/em/gemba?tenant=${T()}`).then(r=>r.json());document.getElementById('gemba-body').innerHTML=(d.walks||[]).map(w=>`<tr><td>${new Date(w.created_at).toLocaleDateString()}</td><td>${w.location}</td><td>${w.walker}</td><td>${(w.observations||[]).length} obs</td><td>${(w.actions||[]).length} actions</td><td>${w.muda_spotted}</td><td>${w.walk_duration_min}min</td></tr>`).join('')}
async function loadPdca(){const d=await fetch(`/api/em/pdca?tenant=${T()}`).then(r=>r.json());document.getElementById('pdca-body').innerHTML=(d.cycles||[]).map(p=>{const gap=p.actual&&p.target?((p.actual-p.target)/Math.max(1,p.target)*100).toFixed(1)+'%':'-';return `<tr><td>${p.title}</td><td><span class="badge">${p.phase}</span></td><td>${p.kpi_name}</td><td>${p.baseline}</td><td>${p.target}</td><td>${p.actual||'-'}</td><td>${gap}</td></tr>`}).join('')}
async function loadAndon(){const d=await fetch(`/api/em/andon?tenant=${T()}`).then(r=>r.json());document.getElementById('andon-body').innerHTML=(d.alerts||[]).map(a=>`<tr><td>${a.station}</td><td class="andon-${a.severity}">●${a.severity}</td><td>${a.message}</td><td><span class="badge status-${a.status==='open'?'in-progress':'completed'}">${a.status}</span></td><td>${a.resolved_by||'-'}</td><td>${a.resolution_time_min||'-'}min</td></tr>`).join('')}
async function loadFiveS(){const d=await fetch(`/api/em/five-s?tenant=${T()}`).then(r=>r.json());document.getElementById('fives-body').innerHTML=(d.audits||[]).map(a=>`<tr><td>${a.area}</td><td>${a.seiri}</td><td>${a.seiton}</td><td>${a.seiso}</td><td>${a.seiketsu}</td><td>${a.shitsuke}</td><td><strong>${a.total_score}/25</strong></td><td>${a.auditor}</td></tr>`).join('')}
async function loadA3(){const d=await fetch(`/api/em/a3?tenant=${T()}`).then(r=>r.json());document.getElementById('a3-body').innerHTML=(d.reports||[]).map(r=>`<tr><td>${r.title}</td><td><span class="badge status-${r.status}">${r.status}</span></td><td>${(r.background||'').substring(0,100)}...</td><td>${(r.gap_analysis||'').substring(0,80)}...</td><td>${(r.root_causes||[]).length} causes</td><td>${r.owner}</td></tr>`).join('')}
async function reload(){await loadDash();await loadMuda();await loadPoka();await loadKaizen();await loadGemba();await loadPdca();await loadAndon();await loadFiveS();await loadA3()}
(async()=>{await loadTenants();await reload()})();
</script>
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
<script>
(function(){
if (window.__opusUniversalDrill) return; window.__opusUniversalDrill = true;
var d = document;
var m = d.createElement('div');
m.id = 'opus-udrill';
m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:99995;padding:20px;cursor:pointer';
var inner = d.createElement('div');
inner.id = 'opus-udrill-in';
inner.style.cssText = 'max-width:900px;width:100%;max-height:90vh;overflow:auto;background:#0b0d15;border:1px solid rgba(99,102,241,0.35);border-radius:14px;padding:28px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6);color:#e2e8f0;font:14px/1.55 Inter,system-ui,sans-serif';
inner.addEventListener('click', function(e){ e.stopPropagation(); });
m.appendChild(inner);
m.addEventListener('click', function(){ m.style.display='none'; });
d.addEventListener('keydown', function(e){ if(e.key==='Escape') m.style.display='none'; });
(d.body || d.documentElement).appendChild(m);
function openCard(card) {
// Clone card content + show close btn + increase font-size
var html = '<div style="display:flex;justify-content:flex-end;margin-bottom:14px"><button id="opus-udrill-close" style="padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.25);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button></div>';
html += '<div style="transform-origin:top left;font-size:1.05em">' + card.outerHTML + '</div>';
inner.innerHTML = html;
d.getElementById('opus-udrill-close').onclick = function(){ m.style.display='none'; };
m.style.display = 'flex';
}
function wire(root) {
var sels = '.card,[class*="card"],.kpi,[class*="kpi"],.stat,[class*="stat"],.tile,[class*="tile"],.metric,[class*="metric"],.widget,[class*="widget"]';
var cards = root.querySelectorAll(sels);
for (var i = 0; i < cards.length; i++) {
var c = cards[i];
if (c.__opusWired) continue;
if (c.closest('button, a, input, select, textarea, #opus-udrill')) continue;
var r = c.getBoundingClientRect();
if (r.width < 60 || r.height < 40) continue;
c.__opusWired = true;
c.style.cursor = 'pointer';
c.setAttribute('role','button');
c.setAttribute('tabindex','0');
c.addEventListener('click', function(ev){
// If a more-specific drill is already active (e.g. pp-card custom), let it handle
if (ev.target.closest('[data-pp-id]') && window.__opusDrillInit) return;
if (ev.target.closest('a,button,input,select')) return;
ev.preventDefault(); ev.stopPropagation();
openCard(this);
});
c.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();openCard(this);} });
}
}
// Initial + mutation observer
var initRun = function(){ wire(d.body || d.documentElement); };
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', initRun);
else initRun();
var mo = new MutationObserver(function(muts){
var newCard = false;
for (var i=0;i<muts.length;i++) if (muts[i].addedNodes.length) { newCard = true; break; }
if (newCard) initRun();
});
mo.observe(d.body || d.documentElement, {childList:true, subtree:true});
})();
</script>
<!-- === OPUS UNIVERSAL DRILL-DOWN END === -->
<script src="/api/archi-meta-badge.js" defer></script>
</body></html>