feat(dashboard): availability-by-speed panel in Supply Squeeze (class + price trend + why per tier)

This commit is contained in:
Rene Fichtmueller 2026-06-11 10:35:37 +00:00
parent 3cbfabbb38
commit 1ba598e7ae

View File

@ -8129,12 +8129,51 @@ async function loadSwitchCompat() {
} }
/* ── C: Supply Squeeze Detector ────────────────────────────────────────── */ /* ── C: Supply Squeeze Detector ────────────────────────────────────────── */
/* ── Verfügbarkeit pro Speed-Tier (mit Warum-Begründung) ────────────────── */
async function renderAvailabilityPanel() {
var host = el('proc-squeeze-summary');
if (!host) return;
try {
var d = await api('/api/procurement/availability');
var tiers = (d && d.tiers) || [];
if (!tiers.length) return;
var meta = {
scarce: { c:'#ef4444', bg:'rgba(239,68,68,0.10)', lbl:'KNAPP' },
constrained: { c:'#f97316', bg:'rgba(249,115,22,0.10)', lbl:'eng' },
moderate: { c:'#f59e0b', bg:'rgba(245,158,11,0.10)', lbl:'moderat' },
abundant: { c:'#22c55e', bg:'rgba(34,197,94,0.10)', lbl:'breit' }
};
function spd(g){ return g>=1000 ? (g/1000)+'T' : g+'G'; }
var html = '<div style="margin-bottom:1rem">'
+ '<div style="font-size:0.8rem;font-weight:700;color:var(--text-bright);margin-bottom:0.5rem">📦 Verfügbarkeit nach Geschwindigkeit <span style="font-weight:400;color:var(--text-dim);font-size:0.7rem">— aus Anbieter-Diversität, Stock-Coverage &amp; Preis-Momentum (echte Daten)</span></div>'
+ '<div style="display:flex;gap:0.5rem;flex-wrap:wrap">';
tiers.forEach(function(t) {
var m = meta[t.availability] || meta.moderate;
var pt = t.price_trend, pd = t.price_delta_pct;
var priceTag = pt==='rising' ? '<span style="color:#ef4444">▲ +'+pd+'%</span>' : pt==='falling' ? '<span style="color:#22c55e">▼ '+pd+'%</span>' : '<span style="color:var(--text-dim)">→ stabil</span>';
html += '<div title="' + esc(t.why || '') + '" style="flex:1;min-width:150px;border:1px solid '+m.c+'44;background:'+m.bg+';border-radius:9px;padding:0.6rem 0.7rem">'
+ '<div style="display:flex;align-items:baseline;justify-content:space-between">'
+ '<span style="font-size:1.05rem;font-weight:800;color:var(--text-bright)">'+spd(t.speed_gbps)+'</span>'
+ '<span style="font-size:0.62rem;font-weight:800;color:'+m.c+';text-transform:uppercase;letter-spacing:0.03em">'+m.lbl+'</span>'
+ '</div>'
+ '<div style="font-size:0.68rem;color:var(--text-dim);margin-top:2px">'+t.suppliers+' Anbieter · '+(t.in_stock_pct!=null?t.in_stock_pct+'% stock':'k.A.')+' · '+priceTag+'</div>'
+ '<div style="font-size:0.66rem;color:var(--text-dim);margin-top:5px;line-height:1.35">'+esc((t.why||'').charAt(0).toUpperCase()+(t.why||'').slice(1))+'</div>'
+ '</div>';
});
html += '</div></div>';
// prepend to summary (before the squeeze severity badges, which get set after)
host.insertAdjacentHTML('afterbegin', html);
} catch(e) { /* silent */ }
}
async function loadSupplySqueeze() { async function loadSupplySqueeze() {
var token = (window.loadToken ? window.loadToken() : '') || ''; var token = (window.loadToken ? window.loadToken() : '') || '';
var summEl = el('proc-squeeze-summary'); var summEl = el('proc-squeeze-summary');
var listEl = el('proc-squeeze-list'); var listEl = el('proc-squeeze-list');
if (listEl) listEl.innerHTML = '<div style="color:var(--text-dim)">Analysiere Preis- & Nachfragesignale…</div>'; if (listEl) listEl.innerHTML = '<div style="color:var(--text-dim)">Analysiere Preis- & Nachfragesignale…</div>';
try { try {
renderAvailabilityPanel();
var r = await fetch('/api/procurement/supply-squeeze', { headers: { 'Authorization': 'Bearer ' + token } }); var r = await fetch('/api/procurement/supply-squeeze', { headers: { 'Authorization': 'Bearer ' + token } });
var d = await r.json(); var d = await r.json();
if (!d.success) throw new Error(d.error); if (!d.success) throw new Error(d.error);