/** * Impact tab — shows strategic-product impact data for the destination * country. Renders top 5 products by value with chokepoint exposure, * lane-specific value for the selected HS2, and dependency flags. * * Clicking a strategic-product row fires onDrillSideways with that HS2, * allowing the explorer to re-query with the clicked product. */ import type { GetRouteImpactResponse, StrategicProduct, } from '@/generated/server/worldmonitor/supply_chain/v1/service_server'; import { formatScoredResilienceOverallLabel, formatResilienceConfidence, formatResilienceScoreInterval, hasScoredResilienceOverall, } from '@/components/resilience-widget-utils'; import type { ResilienceScoreResponse } from '@/services/resilience'; import { escapeHtml } from './route-utils'; import { setTrustedHtml, trustedHtml } from '@/utils/dom-utils'; export interface CountryImpactTabOptions { onDrillSideways?: (hs2: string) => void; } function hs4ToHs2(hs4: string): string { return String(Number.parseInt(hs4.slice(0, 2), 10)); } function formatUsd(value: number): string { if (value >= 1e9) return `$${(value / 1e9).toFixed(1)}B`; if (value >= 1e6) return `$${(value / 1e6).toFixed(1)}M`; if (value >= 1e3) return `$${(value / 1e3).toFixed(0)}K`; return `$${value.toFixed(0)}`; } const FLAG_LABELS: Record = { DEPENDENCY_FLAG_SINGLE_SOURCE_CRITICAL: 'Single Source Critical', DEPENDENCY_FLAG_SINGLE_CORRIDOR_CRITICAL: 'Single Corridor Critical', DEPENDENCY_FLAG_COMPOUND_RISK: 'Compound Risk', DEPENDENCY_FLAG_DIVERSIFIABLE: 'Diversifiable', }; export class CountryImpactTab { public readonly element: HTMLDivElement; private opts: CountryImpactTabOptions; private data: GetRouteImpactResponse | null = null; private resilience: ResilienceScoreResponse | null = null; constructor(opts: CountryImpactTabOptions = {}) { this.opts = opts; this.element = document.createElement('div'); this.element.className = 're-tab re-tab--impact'; this.element.setAttribute('role', 'tabpanel'); this.renderPlaceholder(); } public update(data: GetRouteImpactResponse | null): void { this.data = data; if (!data) this.resilience = null; if (!data) { this.renderPlaceholder(); return; } if (data.comtradeSource === 'missing') { this.renderMissing(); return; } if (data.comtradeSource === 'empty') { this.renderEmpty(); return; } if (data.comtradeSource === 'lazy') { this.renderLazy(); return; } this.renderData(data); } public updateResilience(resilience: ResilienceScoreResponse | null): void { this.resilience = resilience; if (this.data && this.data.comtradeSource !== 'missing' && this.data.comtradeSource !== 'empty' && this.data.comtradeSource !== 'lazy') { this.updateResilienceSlot(this.data); } } private renderPlaceholder(): void { setTrustedHtml(this.element, trustedHtml('
Pick a country pair and product to see the impact analysis.
', "legacy direct innerHTML migration")); } private renderMissing(): void { setTrustedHtml(this.element, trustedHtml('
' + '

No trade data available

' + '

WorldMonitor does not have bilateral trade data for this destination country yet.

' + '
', "legacy direct innerHTML migration")); } private renderEmpty(): void { setTrustedHtml(this.element, trustedHtml('
' + '

No strategic products found

' + '

The bilateral trade store returned empty data for this destination.

' + '
', "legacy direct innerHTML migration")); } private renderLazy(): void { setTrustedHtml(this.element, trustedHtml('
' + '

Loading trade data

' + '

WorldMonitor is fetching trade data for this destination for the first time. ' + 'Try again in a few seconds.

' + '
', "legacy direct innerHTML migration")); } private renderData(data: GetRouteImpactResponse): void { const bannerHtml = !data.hs2InSeededUniverse ? '
Lane value for this HS code is not in WorldMonitor\'s strategic-products dataset. Top strategic products shown below.
' : ''; const laneHtml = data.hs2InSeededUniverse ? `
${formatUsd(data.laneValueUsd)}
Lane value at risk
${data.primaryExporterIso2 ? `
Top exporter: ${escapeHtml(data.primaryExporterIso2)} (${Math.round(data.primaryExporterShare * 100)}%)
` : ''}
` : ''; const flagsHtml = data.dependencyFlags.length > 0 ? `
${data.dependencyFlags.map((f) => `${escapeHtml(FLAG_LABELS[f] ?? f)}`).join('')}
` : ''; const productsHtml = this.renderProducts(data.topStrategicProducts); setTrustedHtml(this.element, trustedHtml(`${bannerHtml}${laneHtml}${flagsHtml}
${this.renderResilience(data)}

Top strategic products

${productsHtml}`, "legacy direct innerHTML migration")); this.attachDrillListeners(); } private updateResilienceSlot(data: GetRouteImpactResponse): void { const slot = this.element.querySelector('.re-impact__resilience-slot'); if (!slot) return; setTrustedHtml(slot, trustedHtml(this.renderResilience(data), "legacy direct innerHTML migration")); } private renderResilience(data: GetRouteImpactResponse): string { const resilience = this.resilience; if (resilience && hasScoredResilienceOverall(resilience)) { const scoreLabel = formatScoredResilienceOverallLabel(resilience.overallScore); const confidence = formatResilienceConfidence(resilience); const interval = formatResilienceScoreInterval(resilience.scoreInterval); return [ '
', ` Resilience: ${escapeHtml(scoreLabel)}/100`, ...(interval ? [` ${escapeHtml(interval.label)}`] : []), ` ${escapeHtml(confidence)}`, '
', ].join(''); } if (resilience) { return '
Resilience: \u2014No scored resilience data
'; } const fallbackScore = Number.isFinite(data.resilienceScore) ? Math.round(data.resilienceScore) : 0; if (fallbackScore > 0) { return `
Resilience: ${fallbackScore}/100Confidence unavailable
`; } return ''; } private renderProducts(products: StrategicProduct[]): string { if (products.length === 0) return '
No products available.
'; const rows = products.map((p) => `` + `HS ${escapeHtml(p.hs4)}` + `${escapeHtml(p.label)}` + `${formatUsd(p.totalValueUsd)}` + `${escapeHtml(p.topExporterIso2)} (${Math.round(p.topExporterShare * 100)}%)` + `${escapeHtml(p.primaryChokepointId)}` + ``, ); return [ '', '', `${rows.join('')}`, '
HS4ProductValueTop ExporterChokepoint
', ].join(''); } private attachDrillListeners(): void { if (!this.opts.onDrillSideways) return; const rows = this.element.querySelectorAll('.re-impact__product-row'); rows.forEach((row) => { const hs2 = row.dataset.hs2; if (!hs2) return; const drill = () => this.opts.onDrillSideways?.(hs2); row.addEventListener('click', drill); row.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); drill(); } }); }); } }