import { useState } from 'react' import { useGeoNews, usePatternRelevance, useGeoRiskScore } from '../hooks/useApi' import clsx from 'clsx' import type { GeoNews } from '../types' import { Globe, ExternalLink, Search } from 'lucide-react' const CATEGORY_COLORS: Record = { military: 'badge-red', sanctions: 'badge-orange', elections: 'badge-purple', natural_disaster: 'badge-yellow', health_crisis: 'badge-orange', resource_scarcity: 'badge-yellow', trade_war: 'badge-blue', energy: 'badge-orange', political_speech: 'badge-blue', financial_crisis: 'badge-red', general: 'badge-blue', } const CATEGORY_LABELS: Record = { military: '⚔️ Militaire', sanctions: '🚫 Sanctions', elections: '🗳️ Élections', natural_disaster: '🌪️ Catastrophe', health_crisis: '🏥 Santé', resource_scarcity: '⚠️ Ressources', trade_war: '🤝 Commerce', energy: '⚡ Énergie', political_speech: '🎙️ Discours', financial_crisis: '💸 Finance', general: '📰 Général', } const ASSET_LABELS: Record = { energy: '⛽ Énergie', metals: '🥇 Métaux', agriculture: '🌾 Agri', equities: '📈 Actions', indices: '📊 Indices', forex: '💱 Forex', } function ImpactBar({ value, label }: { value: number; label: string }) { const abs = Math.abs(value) const positive = value > 0 return (
{label}
{positive ? ( <>
) : ( <>
)}
{positive ? '+' : ''}{(value * 100).toFixed(0)}
) } function NewsCard({ news }: { news: GeoNews }) { const [expanded, setExpanded] = useState(false) return (
setExpanded(!expanded)}>
{news.title}
{CATEGORY_LABELS[news.category] ?? news.category} {news.source}
{Math.round(news.impact_score * 100)}
impact
{expanded && ( <>

{news.summary}

{Object.keys(news.asset_impacts).length > 0 && (
Impact par classe d'actif
{Object.entries(news.asset_impacts).map(([cls, val]) => ( ))}
)} {news.tags.length > 0 && (
{news.tags.map(tag => ( #{tag} ))}
)} {news.url && ( e.stopPropagation()}> Lire l'article )} )}
{news.date?.slice(0, 16)} {expanded ? '▲ Réduire' : '▼ Détail'}
) } function RelevanceBar({ pct }: { pct: number }) { const color = pct >= 60 ? 'bg-emerald-500' : pct >= 30 ? 'bg-yellow-500' : 'bg-slate-600' return (
= 60 ? 'text-emerald-400' : pct >= 30 ? 'text-yellow-400' : 'text-slate-500')}> {pct}%
) } function PatternRelevanceCard({ p }: { p: any }) { const [expanded, setExpanded] = useState(false) const hasNews = p.matching_news?.length > 0 return (
= 60, 'border-yellow-700/30': p.relevance >= 30 && p.relevance < 60, 'border-slate-700/20 opacity-60': p.relevance === 0, })}>
{p.name}
{p.asset_class} {p.keyword_hits}/{p.keyword_total} mots-clés {hasNews && ( {p.matching_news.length} news )}
{/* Matched keywords */} {p.matched_keywords?.length > 0 && (
{p.matched_keywords.map((kw: string) => ( #{kw} ))}
)} {/* Toggle matching news */} {hasNews && ( <> {expanded && (
{p.matching_news.map((n: any, i: number) => (
{n.title}
{n.source} · {n.date}
{Math.round(n.impact * 100)}
{n.matched_keywords.map((kw: string) => ( #{kw} ))}
{n.url && ( e.stopPropagation()}> Lire )}
))}
)} )} {!hasNews && p.relevance === 0 && (
Aucune news correspondante sur la période
)}
) } export default function GeoRadar() { const { data: news, isLoading } = useGeoNews() const { data: riskScore } = useGeoRiskScore() const [filterCat, setFilterCat] = useState('all') const [tab, setTab] = useState<'news' | 'patterns'>('news') const [days, setDays] = useState(2) const { data: patternRelevance, isLoading: relLoading } = usePatternRelevance(days) const categories = ['all', ...Array.from(new Set(news?.map(n => n.category) ?? []))] const filtered = filterCat === 'all' ? news : news?.filter(n => n.category === filterCat) const patternsWithNews = (patternRelevance ?? []).filter((p: any) => p.matching_news?.length > 0).length const totalPatterns = patternRelevance?.length ?? 0 return (

Radar Géopolitique

Flux d'actualités · Correspondance avec les patterns de trading

{riskScore && (
{riskScore.score}/100
{riskScore.level}
)}
{/* Tabs */}
{(['news', 'patterns'] as const).map(t => ( ))}
{/* Day selector — only shown on patterns tab */} {tab === 'patterns' && (
Fenêtre :
{[1, 2, 7, 30].map(d => ( ))}
derniers jours
)}
{/* News tab */} {tab === 'news' && ( <>
{categories.slice(0, 8).map(cat => ( ))}
{isLoading ? (
{[1,2,3,4].map(i =>
)}
) : (
{filtered?.map((n, i) => )} {(!filtered || filtered.length === 0) && (
Démarrer le backend pour charger les actualités
)}
)} )} {/* Patterns relevance tab */} {tab === 'patterns' && ( <>
Correspondance entre les mots-clés de chaque pattern et les news des {days} derniers jours. Ce signal sert à enrichir le contexte donné à l'IA lors du scoring. {patternsWithNews > 0 && ( {patternsWithNews} patterns ont des news correspondantes. )}
{relLoading ? (
{[1,2,3].map(i =>
)}
) : (
{(patternRelevance ?? []).map((p: any) => ( ))} {(!patternRelevance || patternRelevance.length === 0) && (
Démarrer le backend pour calculer la correspondance news-patterns
)}
)} )}
) }