feat: dashboard v8 — COT, FX Weekly, Report, TvChart, Sentiment, News

Ajout des onglets COT, Weekly Report, TvChart. Refonte Sentiment, News,
Calendar, Drivers. Nouvelles API cot-history et fx-weekly. Intégration FinancialJuice.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Capucine Gest
2026-06-08 14:33:23 +02:00
co-authored by Claude Sonnet 4.6
parent e11d61eb4d
commit 1a2ea02b22
15 changed files with 2284 additions and 389 deletions
+37 -5
View File
@@ -12,8 +12,11 @@ import CalendarTab from "@/components/CalendarTab";
import SentimentPairsTab from "@/components/SentimentPairsTab";
import YieldsTab from "@/components/YieldsTab";
import NewsTab from "@/components/NewsTab";
import CotTab from "@/components/CotTab";
import ReportTab from "@/components/ReportTab";
import type { CalendarEvent } from "@/app/api/calendar/route";
import type { NewsItem } from "@/app/api/news/route";
import type { CotHistory } from "@/app/api/cot-history/route";
const REFRESH_MS = parseInt(process.env.NEXT_PUBLIC_REFRESH_INTERVAL_MS ?? "3600000");
@@ -25,10 +28,12 @@ export default function Dashboard() {
const [cot, setCot] = useState<Record<string, CotEntry> | null>(null);
const [calEvents, setCalEvents] = useState<CalendarEvent[]>([]);
const [nextWeekAvail, setNextWeekAvail] = useState(false);
const [activeTab, setActiveTab] = useState<"dashboard" | "calendar" | "pairs" | "yields" | "news">("dashboard");
const [activeTab, setActiveTab] = useState<"dashboard" | "calendar" | "pairs" | "yields" | "news" | "cot" | "report">("dashboard");
const [newsItems, setNewsItems] = useState<NewsItem[]>([]);
const [newsLoading, setNewsLoading] = useState(false);
const [rawSymbols, setRawSymbols] = useState<Array<{ name: string; longPercentage: number; shortPercentage: number; totalPositions: number }> | null>(null);
const [cotHistory, setCotHistory] = useState<CotHistory | null>(null);
const [cotLoading, setCotLoading] = useState(false);
const [rawSymbols, setRawSymbols] = useState<Array<{ name: string; longPercentage: number; shortPercentage: number; longVolume: number; shortVolume: number; longPositions: number; shortPositions: number; totalPositions: number; avgLongPrice?: number; avgShortPrice?: number }> | null>(null);
const [rateProbabilities, setRateProbabilities] = useState<RateProbData | null>(null);
const [lastRefresh, setLastRefresh] = useState<Date>(new Date());
const [loading, setLoading] = useState(true);
@@ -174,7 +179,7 @@ export default function Dashboard() {
// ── Sentiment Myfxbook ────────────────────────────────────────────────
if (sentimentRes.status === "fulfilled" && !sentimentRes.value?.error && sentimentRes.value?.symbols) {
const syms = sentimentRes.value.symbols as Array<{ name: string; longPercentage: number; shortPercentage: number; totalPositions: number }>;
const syms = sentimentRes.value.symbols as Array<{ name: string; longPercentage: number; shortPercentage: number; longVolume: number; shortVolume: number; longPositions: number; shortPositions: number; totalPositions: number; avgLongPrice?: number; avgShortPrice?: number }>;
setRawSymbols(syms);
const mapped = parseSentimentSymbols(syms);
setSentiment(mapped);
@@ -233,6 +238,23 @@ export default function Dashboard() {
if (activeTab === "news" && newsItems.length === 0) refreshNews();
}, [activeTab, newsItems.length, refreshNews]);
const refreshCotHistory = useCallback(async () => {
setCotLoading(true);
try {
const res = await fetch("/api/cot-history");
if (res.ok) {
const json = await res.json();
if (!json.error) setCotHistory(json as CotHistory);
}
} finally {
setCotLoading(false);
}
}, []);
useEffect(() => {
if (activeTab === "cot" && !cotHistory) refreshCotHistory();
}, [activeTab, cotHistory, refreshCotHistory]);
const handleDivergenceUpdate = useCallback((currency: Currency, score: number) => {
setActiveDivergences((prev) => {
const filtered = prev.filter((d) => d.currency !== currency);
@@ -292,7 +314,7 @@ export default function Dashboard() {
{/* Tab navigation */}
<div className="flex gap-0 border-b border-slate-800 mb-4">
{(["dashboard", "calendar", "pairs", "yields", "news"] as const).map((tab) => (
{(["dashboard", "calendar", "pairs", "yields", "news", "cot", "report"] as const).map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
@@ -306,7 +328,9 @@ export default function Dashboard() {
: tab === "calendar" ? "📅 Calendrier"
: tab === "pairs" ? "↕ Paires"
: tab === "yields" ? "📈 Yields 10Y"
: "📰 Actualités"}
: tab === "news" ? "📰 Actualités"
: tab === "cot" ? "📊 COT"
: "📋 Rapport"}
</button>
))}
</div>
@@ -371,6 +395,14 @@ export default function Dashboard() {
<NewsTab items={newsItems} loading={newsLoading} onRefresh={refreshNews} />
)}
{activeTab === "cot" && (
<CotTab history={cotHistory} loading={cotLoading} />
)}
{activeTab === "report" && (
<ReportTab calEvents={calEvents} drivers={drivers} cotHistory={cotHistory} />
)}
{/* Legend */}
<div className="mt-4 pt-3 border-t border-slate-800 flex items-center gap-5 flex-wrap text-[10px] text-slate-600">
<div className="flex items-center gap-1.5"><span className="w-2 h-2 rounded-full bg-emerald-500 shrink-0" /> Haussier / Sous-évalué</div>