diff --git a/packages/api/src/db/queries.ts b/packages/api/src/db/queries.ts index 9f5fe89..4135625 100644 --- a/packages/api/src/db/queries.ts +++ b/packages/api/src/db/queries.ts @@ -283,7 +283,7 @@ export async function getTransceiverDocuments(transceiverI: string) { export async function getCompatibleTransceivers(switchId: string) { const result = await pool.query( - `SELECT t.*, c.status, c.verified_by, c.notes as compat_notes, + `SELECT t.*, c.status, c.verified_by, c.verification_method, c.notes as compat_notes, v.name AS vendor_name, v.type AS vendor_type, v.website AS vendor_website, -- Latest verified price COALESCE(t.price_verified_eur, @@ -298,6 +298,9 @@ export async function getCompatibleTransceivers(switchId: string) { WHERE c.switch_id = $1 AND c.status = 'compatible' ORDER BY CASE WHEN LOWER(v.name) = 'flexoptix' THEN 0 ELSE 1 END, + CASE WHEN c.verification_method = 'vendor_compat' THEN 0 + WHEN c.verification_method = 'vendor_matrix' THEN 1 + ELSE 2 END, t.speed_gbps DESC`, [switchId] ); diff --git a/packages/dashboard/index.html b/packages/dashboard/index.html index ba50c14..af701b0 100644 --- a/packages/dashboard/index.html +++ b/packages/dashboard/index.html @@ -4070,9 +4070,19 @@ async function openSwitchDetail(id) { ch += '
Flexoptix Recommended ' + foList.length + '
'; ch += '
Directly available from Flexoptix — FlexBox coding supported
'; - // Group Flexoptix by speed class + // Split Flexoptix by verification method + var foVendorVerified = foList.filter(function(t) { return t.verification_method === 'vendor_compat' || t.verification_method === 'vendor_matrix'; }); + var foSpecMatch = foList.filter(function(t) { return t.verification_method !== 'vendor_compat' && t.verification_method !== 'vendor_matrix'; }); + + // Show explicitly verified first + var foToShow = foVendorVerified.length > 0 ? foVendorVerified : foList; + if (foVendorVerified.length > 0 && foSpecMatch.length > 0) { + ch += '
✓ Vendor-tested compatibility (' + foVendorVerified.length + ')
'; + } + + // Group by speed class var foGroups = {}; - foList.forEach(function(t) { + foToShow.forEach(function(t) { var key = (t.speed || '?') + ' ' + (t.form_factor || '?'); if (!foGroups[key]) foGroups[key] = []; foGroups[key].push(t); @@ -4107,28 +4117,74 @@ async function openSwitchDetail(id) { if (items.length > 8) ch += '
+' + (items.length - 8) + ' more Flexoptix options
'; ch += ''; }); + + // Form-factor matches (spec_match) — collapsed summary + if (foSpecMatch.length > 0) { + ch += '
' + + '+ ' + foSpecMatch.length + ' more by form factor match' + + '
' + + foSpecMatch.slice(0, 20).map(function(t) { + return '' + + esc(t.standard_name || t.part_number) + ''; + }).join('') + + (foSpecMatch.length > 20 ? '+' + (foSpecMatch.length - 20) + ' more' : '') + + '
'; + } } // ── ALL COMPATIBLE (other vendors) ──────────────────────────────────── - ch += '
Compatible Transceivers ' + txList.length + '
'; - var groups = {}; - otherList.forEach(function(t) { - var key = (t.form_factor || '?') + ' ' + (t.speed || '?'); - if (!groups[key]) groups[key] = []; - groups[key].push(t); - }); - Object.keys(groups).sort().forEach(function(key) { - var items = groups[key]; - ch += '
' + esc(key) + ' (' + items.length + ')
'; - ch += '
'; - items.slice(0, 12).forEach(function(t) { - var fullyBadge = (t.fully_verified === true) ? '★ ' : ''; - ch += '' - + fullyBadge + esc(t.standard_name || t.slug || t.part_number) + ''; - }); - if (items.length > 12) ch += '+' + (items.length - 12) + ' more'; - ch += '
'; - }); + if (otherList.length > 0) { + var verifiedOthers = otherList.filter(function(t) { return t.verification_method === 'vendor_matrix'; }); + var specOthers = otherList.filter(function(t) { return t.verification_method !== 'vendor_matrix'; }); + + ch += '
Competitor Transceivers ' + otherList.length + '' + + (verifiedOthers.length > 0 ? '(' + verifiedOthers.length + ' vendor-tested)' : '') + '
'; + + // Show vendor-tested ones with price info + if (verifiedOthers.length > 0) { + var groups = {}; + verifiedOthers.forEach(function(t) { + var key = (t.form_factor || '?') + ' ' + (t.speed || '?'); + if (!groups[key]) groups[key] = []; + groups[key].push(t); + }); + Object.keys(groups).sort().forEach(function(key) { + var items = groups[key]; + ch += '
' + esc(key) + ' (' + items.length + ')
'; + ch += '
'; + items.slice(0, 6).forEach(function(t) { + var priceStr = ''; + if (t.latest_price) { + var _pAmt = parseFloat(t.latest_price); + var _pCur = (t.latest_currency || 'USD').toUpperCase(); + var _pUSD = toUSD(_pAmt, _pCur); + priceStr = _pUSD !== null ? fmtUSD(_pUSD) : _pCur + ' ' + _pAmt.toFixed(2); + } + ch += '
' + + '' + esc(t.part_number || t.standard_name) + '' + + '' + esc(t.vendor_name || '') + '' + + (priceStr ? '' + priceStr + '' : '') + + '
'; + }); + if (items.length > 6) ch += '
+' + (items.length - 6) + ' more
'; + ch += '
'; + }); + } + + // Show spec-match ones as compact chips + if (specOthers.length > 0) { + ch += '
'; + ch += '
Form factor compatible
'; + ch += '
'; + specOthers.slice(0, 20).forEach(function(t) { + var fullyBadge = (t.fully_verified === true) ? '★ ' : ''; + ch += '' + + fullyBadge + esc(t.standard_name || t.slug || t.part_number) + ''; + }); + if (specOthers.length > 20) ch += '+' + (specOthers.length - 20) + ' more'; + ch += '
'; + } + } el('panel-content').insertAdjacentHTML('beforeend', ch); }).catch(function() {});