feat(dashboard): availability-by-speed panel in Supply Squeeze (class + price trend + why per tier)
This commit is contained in:
parent
3cbfabbb38
commit
1ba598e7ae
@ -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 & 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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user