From cb52cfad1a29f1e7d09a09f8e037a97eaf6e37b8 Mon Sep 17 00:00:00 2001 From: caty21 Date: Sat, 27 Jun 2026 23:09:02 +0200 Subject: [PATCH] fix: TvChart embed-widget-advanced-chart (candlestick, no symbol restriction), unique instance IDs, CHF/SNB trial, NZD candlestick in ReportTab --- .github/scripts/fetch-rate-data.mjs | 1 + components/ReportTab.tsx | 4 +- components/TvChart.tsx | 248 ++++++++++++++-------------- 3 files changed, 126 insertions(+), 127 deletions(-) diff --git a/.github/scripts/fetch-rate-data.mjs b/.github/scripts/fetch-rate-data.mjs index 4935514..b75c04f 100644 --- a/.github/scripts/fetch-rate-data.mjs +++ b/.github/scripts/fetch-rate-data.mjs @@ -10,6 +10,7 @@ const CB_KEYS = [ ["CAD", "boc"], ["AUD", "rba"], ["NZD", "rbnz"], + ["CHF", "snb"], // BNS β€” essai, null si l'endpoint n'existe pas sur rateprobability.com ]; const HEADERS = { diff --git a/components/ReportTab.tsx b/components/ReportTab.tsx index 1137952..5459e46 100644 --- a/components/ReportTab.tsx +++ b/components/ReportTab.tsx @@ -523,7 +523,7 @@ export default function ReportTab({ calEvents, drivers, cotHistory }: Props) {
- {/* 8 currency mini charts */} + {/* 8 currency charts β€” bougie hebdomadaire */}
{[ { sym: "TVC:DXY", label: "πŸ‡ΊπŸ‡Έ USD Β· DXY" }, @@ -535,7 +535,7 @@ export default function ReportTab({ calEvents, drivers, cotHistory }: Props) { { sym: "FX:AUDUSD", label: "πŸ‡¦πŸ‡Ί AUD/USD" }, { sym: "FX:NZDUSD", label: "πŸ‡³πŸ‡Ώ NZD/USD" }, ].map(({ sym, label }) => ( - + ))}
diff --git a/components/TvChart.tsx b/components/TvChart.tsx index 63fe312..0503957 100644 --- a/components/TvChart.tsx +++ b/components/TvChart.tsx @@ -2,71 +2,96 @@ import { useEffect, useRef } from "react"; -// ── TvMiniChart ─────────────────────────────────────────────────────────────── -// Utilise le script embed TradingView dΓ©diΓ© (embed-widget-mini-symbol-overview.js) -// et NON pas tv.js (qui n'expose pas MiniSymbolOverview). -// La structure DOM attendue par TradingView : -//
← wrapper -//
-// -//
+// ── Helpers ─────────────────────────────────────────────────────────────────── +// Chaque instance obtient un identifiant unique injectΓ© dans l'URL du script +// pour forcer le navigateur Γ  rΓ©-exΓ©cuter le script mΓͺme si l'URL est en cache. +// Sans Γ§a, les 4 charts simultanΓ©s du tab MarchΓ©s ne s'initialisent pas tous. -interface TvMiniChartProps { - symbol: string; // ex: "SP:SPX", "TVC:DXY", "FX:EURUSD" - label?: string; // titre affichΓ© au-dessus - interval?: "W" | "D" | "M"; - dateRange?: string; // "1D","5D","1M","3M","6M","12M","60M","ALL","YTD" - height?: number; - showInfo?: boolean; // afficher nom + prix sous le graphique +function mkInstanceId() { + return Math.random().toString(36).slice(2); } -export function TvMiniChart({ +// ── Structure DOM commune attendue par les widgets embed TradingView ────────── +//
← wrapperRef +//
+// +//
+ +function mountEmbedWidget( + wrapper: HTMLDivElement, + scriptSrc: string, + config: Record, + height: number +) { + wrapper.innerHTML = ""; + + const widgetDiv = document.createElement("div"); + widgetDiv.className = "tradingview-widget-container__widget"; + widgetDiv.style.width = "100%"; + widgetDiv.style.height = `${height}px`; + wrapper.appendChild(widgetDiv); + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.async = true; + script.src = scriptSrc; + script.text = JSON.stringify(config); + wrapper.appendChild(script); +} + +// ── TvAdvancedChart ─────────────────────────────────────────────────────────── +// Graphique avancΓ© (bougies) via embed-widget-advanced-chart.js. +// Utilise une iframe TradingView β†’ accΓ¨s aux mΓͺmes symboles que le site web +// (SP:SPX, TVC:VIX, TVC:DXY, TVC:GOLD, etc.) sans restriction "abonnement". + +interface TvAdvancedChartProps { + symbol: string; // ex: "SP:SPX", "TVC:DXY", "FX:EURUSD" + label?: string; + interval?: string; // "D", "W", "M", "60", etc. + height?: number; +} + +export function TvAdvancedChart({ symbol, label, - dateRange = "1M", - height = 180, - showInfo = true, -}: TvMiniChartProps) { + interval = "D", + height = 250, +}: TvAdvancedChartProps) { const wrapperRef = useRef(null); const initialized = useRef(false); + const instanceId = useRef(mkInstanceId()); useEffect(() => { if (initialized.current || !wrapperRef.current) return; initialized.current = true; - const wrapper = wrapperRef.current; - wrapper.innerHTML = ""; - - // Div cible du widget - const widgetDiv = document.createElement("div"); - widgetDiv.className = "tradingview-widget-container__widget"; - wrapper.appendChild(widgetDiv); - - // Script embed avec config inline (textContent lu par le script au chargement) - const script = document.createElement("script"); - script.type = "text/javascript"; - script.async = true; - script.src = "https://s3.tradingview.com/external-embedding/embed-widget-mini-symbol-overview.js"; - script.text = JSON.stringify({ - symbol, - width: "100%", - height, - locale: "fr", - dateRange, - colorTheme: "dark", - trendLineColor: "#38bdf8", - underLineColor: "rgba(56,189,248,0.08)", - underLineBottomColor: "rgba(56,189,248,0)", - isTransparent: true, - autosize: false, - largeChartUrl: "", - noTimeScale: false, - chartOnly: !showInfo, - }); - wrapper.appendChild(script); + mountEmbedWidget( + wrapperRef.current, + `https://s3.tradingview.com/external-embedding/embed-widget-advanced-chart.js?t=${instanceId.current}`, + { + autosize: false, + width: "100%", + height, + symbol, + interval, + timezone: "Europe/Paris", + theme: "dark", + style: "1", // bougies japonaises + locale: "fr", + backgroundColor: "rgba(8,12,20,0)", + gridColor: "rgba(30,45,61,0.5)", + hide_top_toolbar: true, + hide_legend: false, + allow_symbol_change: false, + calendar: false, + hide_volume: true, + isTransparent: true, + }, + height + ); return () => { - wrapper.innerHTML = ""; + if (wrapperRef.current) wrapperRef.current.innerHTML = ""; initialized.current = false; }; }, []); // eslint-disable-line @@ -87,85 +112,59 @@ export function TvMiniChart({ ); } -// ── TvAdvancedChart ─────────────────────────────────────────────────────────── -// Graphique avancΓ© plein format via tv.js (TradingView.widget). -// Note : certains symboles affichent une popup "disponible uniquement sur TradingView". +// ── TvMiniChart ─────────────────────────────────────────────────────────────── +// Graphique lΓ©ger (ligne/area) via embed-widget-mini-symbol-overview.js. +// IdΓ©al pour les aperΓ§us compacts (CurrencyCard, sidebar, etc.). -declare global { - interface Window { - TradingView?: { - widget: new (config: Record) => void; - }; - } +interface TvMiniChartProps { + symbol: string; + label?: string; + dateRange?: string; // "1D","5D","1M","3M","6M","12M","60M","ALL","YTD" + height?: number; + showInfo?: boolean; } -let tvScriptLoaded = false; -let tvScriptLoading = false; -const tvCallbacks: (() => void)[] = []; - -function loadTvScript(cb: () => void) { - if (tvScriptLoaded) { cb(); return; } - tvCallbacks.push(cb); - if (tvScriptLoading) return; - tvScriptLoading = true; - const s = document.createElement("script"); - s.src = "https://s3.tradingview.com/tv.js"; - s.async = true; - s.onload = () => { - tvScriptLoaded = true; - tvCallbacks.forEach(f => f()); - tvCallbacks.length = 0; - }; - document.head.appendChild(s); -} - -interface TvAdvancedChartProps { - symbol: string; - label?: string; - interval?: string; - height?: number; -} - -export function TvAdvancedChart({ +export function TvMiniChart({ symbol, label, - interval = "W", - height = 250, -}: TvAdvancedChartProps) { - const uid = useRef(`tv_adv_${Math.random().toString(36).slice(2)}`); - const ref = useRef(null); - const init = useRef(false); + dateRange = "1M", + height = 180, + showInfo = true, +}: TvMiniChartProps) { + const wrapperRef = useRef(null); + const initialized = useRef(false); + const instanceId = useRef(mkInstanceId()); useEffect(() => { - if (init.current) return; - init.current = true; + if (initialized.current || !wrapperRef.current) return; + initialized.current = true; - loadTvScript(() => { - if (!window.TradingView || !ref.current) return; - try { - new window.TradingView.widget({ - autosize: false, - width: "100%", - height, - symbol, - interval, - timezone: "Europe/Paris", - theme: "dark", - style: "1", - locale: "fr", - toolbar_bg: "#0f1623", - enable_publishing: false, - hide_top_toolbar: true, - hide_legend: false, - save_image: false, - container_id: uid.current, - backgroundColor: "rgba(8,12,20,0)", - gridColor: "rgba(30,45,61,0.5)", - hide_volume: false, - studies: [], - }); - } catch { /* TradingView indisponible */ } - }); + mountEmbedWidget( + wrapperRef.current, + `https://s3.tradingview.com/external-embedding/embed-widget-mini-symbol-overview.js?t=${instanceId.current}`, + { + symbol, + width: "100%", + height, + locale: "fr", + dateRange, + colorTheme: "dark", + trendLineColor: "#38bdf8", + underLineColor: "rgba(56,189,248,0.08)", + underLineBottomColor: "rgba(56,189,248,0)", + isTransparent: true, + autosize: false, + largeChartUrl: "", + noTimeScale: false, + chartOnly: !showInfo, + }, + height + ); + + return () => { + if (wrapperRef.current) wrapperRef.current.innerHTML = ""; + initialized.current = false; + }; }, []); // eslint-disable-line return ( @@ -176,9 +175,8 @@ export function TvAdvancedChart({

)}