169 lines
16 KiB
HTML
169 lines
16 KiB
HTML
<!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>
|