feat(resilience): energy domain tooltip from energy profile data (#2762)

* feat(resilience): energy domain tooltip from energy profile data

When CountryDeepDivePanel receives energy profile data, it now passes it
to ResilienceWidget via setEnergyMix(). The energy domain row in the
widget gains a native title-attribute tooltip showing:

  Import dep: X% | Gas: X% | Coal: X% | Renew: X%

...and if EU gas storage is available: | EU storage: X%

No new fetch — reuses the CountryEnergyProfileData already fetched by
the energy section (PR C). Zero JS/CSS overhead: native browser tooltip.

* fix(resilience): hide energy tooltip in locked preview mode
This commit is contained in:
Elie Habib
2026-04-06 12:28:21 +04:00
committed by GitHub
parent 0895bc8d9e
commit 0a07dc34f2
3 changed files with 28 additions and 3 deletions

View File

@@ -485,6 +485,7 @@ export class CountryDeepDivePanel implements CountryBriefPanel {
public updateEnergyProfile(data: CountryEnergyProfileData): void {
if (!this.energyBody) return;
this.renderEnergyProfile(data);
this.resilienceWidget?.setEnergyMix(data);
}
private renderEnergyProfile(data: CountryEnergyProfileData): void {

View File

@@ -14,6 +14,7 @@ import {
getResilienceTrendArrow,
getResilienceVisualLevel,
} from './resilience-widget-utils';
import type { CountryEnergyProfileData } from './CountryBriefPanel';
const LOCKED_PREVIEW: ResilienceScoreResponse = {
countryCode: 'US',
@@ -51,6 +52,7 @@ export class ResilienceWidget {
private loading = false;
private errorMessage: string | null = null;
private requestVersion = 0;
private energyMixData: CountryEnergyProfileData | null = null;
constructor(countryCode?: string | null) {
this.element = document.createElement('section');
@@ -78,6 +80,7 @@ export class ResilienceWidget {
this.currentCountryCode = normalized;
this.currentData = null;
this.energyMixData = null;
this.errorMessage = null;
this.loading = false;
this.requestVersion += 1;
@@ -122,6 +125,11 @@ export class ResilienceWidget {
}
}
public setEnergyMix(data: CountryEnergyProfileData | null): void {
this.energyMixData = data;
this.render();
}
public destroy(): void {
this.requestVersion += 1;
this.unsubscribeAuth?.();
@@ -257,7 +265,7 @@ export class ResilienceWidget {
h(
'div',
{ className: 'resilience-widget__domains' },
...data.domains.map((domain) => this.renderDomainRow(domain)),
...data.domains.map((domain) => this.renderDomainRow(domain, preview)),
),
h(
'div',
@@ -275,13 +283,27 @@ export class ResilienceWidget {
);
}
private renderDomainRow(domain: ResilienceDomain): HTMLElement {
private renderDomainRow(domain: ResilienceDomain, preview = false): HTMLElement {
const score = clampScore(domain.score);
const levelColor = RESILIENCE_VISUAL_LEVEL_COLORS[getResilienceVisualLevel(score)];
const attrs: Record<string, string> = { className: 'resilience-widget__domain-row' };
if (!preview && domain.id === 'energy' && this.energyMixData?.mixAvailable) {
const d = this.energyMixData;
const parts = [
`Import dep: ${d.importShare.toFixed(1)}%`,
`Gas: ${d.gasShare.toFixed(1)}%`,
`Coal: ${d.coalShare.toFixed(1)}%`,
`Renew: ${d.renewShare.toFixed(1)}%`,
];
if (d.gasStorageAvailable) parts.push(`EU storage: ${d.gasStorageFillPct.toFixed(1)}%`);
attrs['title'] = parts.join(' | ');
}
return h(
'div',
{ className: 'resilience-widget__domain-row' },
attrs,
h('span', { className: 'resilience-widget__domain-label' }, getResilienceDomainLabel(domain.id)),
this.renderBarBlock(score, levelColor),
h('span', { className: 'resilience-widget__domain-score' }, String(Math.round(score))),

View File

@@ -160,6 +160,8 @@ describe('Product ID guard', () => {
const result = execSync(
`grep -rn 'pdt_' --include='*.ts' --include='*.tsx' --include='*.mjs' --include='*.js' . ` +
`| grep -v node_modules ` +
`| grep -v '.claude/worktrees/' ` +
`| grep -v 'convex/_generated/' ` +
`| grep -v 'convex/config/productCatalog' ` +
`| grep -v 'api/product-catalog' ` +
`| grep -v 'api/_product-fallback-prices' ` +