feat: Flexoptix section — speed formatting + Lagerbestand display

Speed display: fix raw Gbps decimals → formatted labels
- 1600.00G → 1.6T (≥1000 Gbps converted to T)
- 400G → 400G (clean integer, no trailing .00)
- Helper function fmtSpeed() added in dashboard JS

Lagerbestand: add stock availability per transceiver
- getFlexoptixSuggestions() extended with LEFT JOIN LATERAL on
  stock_observations (latest row per transceiver)
- Returns warehouse_de_qty, warehouse_global_qty, backorder_qty,
  backorder_estimated_date
- Dashboard renders color-coded badges per row:
    green  = DE-Lager quantity
    blue   = Global-Lager quantity
    yellow = Zulauf with estimated delivery date if available
- Badges hidden when all quantities are null/zero (graceful fallback)
This commit is contained in:
Rene Fichtmueller 2026-05-14 00:52:21 +02:00
parent de179c4c7c
commit 9b8b03e783
2 changed files with 40 additions and 4 deletions

View File

@ -352,9 +352,20 @@ export async function getFlexoptixSuggestions(switchId: string) {
CASE WHEN t.price_verified_eur IS NOT NULL THEN 'EUR'
ELSE (SELECT po.currency FROM price_observations po
WHERE po.transceiver_id = t.id ORDER BY po.time DESC LIMIT 1)
END AS latest_currency
END AS latest_currency,
so.warehouse_de_qty,
so.warehouse_global_qty,
so.backorder_qty,
so.backorder_estimated_date
FROM transceivers t
JOIN vendors v ON t.vendor_id = v.id
LEFT JOIN LATERAL (
SELECT warehouse_de_qty, warehouse_global_qty, backorder_qty, backorder_estimated_date
FROM stock_observations
WHERE transceiver_id = t.id
ORDER BY time DESC
LIMIT 1
) so ON true
WHERE LOWER(v.name) = 'flexoptix'
AND t.form_factor IN (
SELECT form_factor FROM switch_form_factors WHERE form_factor IS NOT NULL

View File

@ -4310,10 +4310,18 @@ async function openSwitchDetail(id) {
+ '</div>';
fch += '<div style="font-size:0.72rem;color:var(--text-dim);margin-bottom:0.6rem">Passend für diesen Switch — FlexBox-Codierung möglich</div>';
// Format speed_gbps → "1.6T", "400G", "100G" etc.
function fmtSpeed(gbps) {
if (!gbps) return '?';
var n = parseFloat(gbps);
if (n >= 1000) return (n / 1000) + 'T';
return Math.round(n) + 'G';
}
// Group by speed class
var foGroups = {};
foAll.forEach(function(t) {
var key = (t.speed_gbps ? t.speed_gbps + 'G' : (t.speed || '?')) + ' ' + (t.form_factor || '?');
var key = fmtSpeed(t.speed_gbps) + ' ' + (t.form_factor || '?');
if (!foGroups[key]) foGroups[key] = [];
foGroups[key].push(t);
});
@ -4342,10 +4350,27 @@ async function openSwitchDetail(id) {
var shopHref = t.product_page_url || ('https://www.flexoptix.net/en/search/ajax/suggest/?q=' + encodeURIComponent(t.part_number || t.standard_name || ''));
var reach = t.reach_label ? '<span style="color:var(--text-dim);font-size:0.68rem;margin-left:0.25rem">' + esc(t.reach_label) + '</span>' : '';
// Stock badges
var stockHtml = '';
var deQty = parseInt(t.warehouse_de_qty) || 0;
var glQty = parseInt(t.warehouse_global_qty) || 0;
var boQty = parseInt(t.backorder_qty) || 0;
if (deQty > 0 || glQty > 0 || boQty > 0) {
stockHtml += '<div style="display:flex;gap:0.3rem;flex-wrap:wrap;margin-top:0.2rem">';
if (deQty > 0) stockHtml += '<span style="font-size:0.62rem;background:rgba(16,185,129,0.12);color:#10b981;border:1px solid rgba(16,185,129,0.3);border-radius:3px;padding:1px 5px">DE ' + deQty + ' Stk</span>';
if (glQty > 0) stockHtml += '<span style="font-size:0.62rem;background:rgba(59,130,246,0.12);color:#60a5fa;border:1px solid rgba(59,130,246,0.3);border-radius:3px;padding:1px 5px">Global ' + glQty + ' Stk</span>';
if (boQty > 0) {
var boDate = t.backorder_estimated_date ? ' bis ' + new Date(t.backorder_estimated_date).toLocaleDateString('de-DE', {day:'2-digit',month:'2-digit'}) : '';
stockHtml += '<span style="font-size:0.62rem;background:rgba(245,158,11,0.12);color:#fbbf24;border:1px solid rgba(245,158,11,0.3);border-radius:3px;padding:1px 5px">Zulauf ' + boQty + boDate + '</span>';
}
stockHtml += '</div>';
}
fch += '<div style="display:flex;align-items:center;padding:0.35rem 0.5rem;background:rgba(255,102,0,0.05);border:1px solid rgba(255,102,0,0.2);border-radius:6px;gap:0.35rem;cursor:pointer" onclick="openTxDetail(\'' + esc(t.id) + '\')">'
+ '<div style="flex:1;min-width:0">'
+ '<span style="font-weight:600;font-size:0.8rem;color:var(--text-bright)">' + esc(t.part_number || t.standard_name || t.slug) + '</span>'
+ reach
+ '<div><span style="font-weight:600;font-size:0.8rem;color:var(--text-bright)">' + esc(t.part_number || t.standard_name || t.slug) + '</span>'
+ reach + '</div>'
+ stockHtml
+ '</div>'
+ (priceStr
? '<span style="font-weight:700;font-size:0.78rem;color:#ff6600;white-space:nowrap">' + priceStr + '</span>'