import { useState, useEffect, useCallback } from 'react' import { useSearchParams } from 'react-router-dom' import { ChevronLeft, ChevronRight, Sparkles, Calendar, Clock, Layers, RefreshCw, List, Cpu } from 'lucide-react' import axios from 'axios' import clsx from 'clsx' import TimelineVertical from '../components/TimelineVertical' import EventManager from '../components/EventManager' const api = axios.create({ baseURL: '/api' }) interface MarketEvent { id: number name: string start_date: string end_date: string | null level: 'long' | 'medium' | 'short' category: string description: string market_impact: string affected_assets: string impact_score: number } interface DayContext { ref_date: string events: { long: MarketEvent | null medium: MarketEvent | null short: MarketEvent | null } long_commentary: string medium_commentary: string short_commentary: string evidence_headlines: string[] generated_at: string | null has_commentary: boolean } const LEVEL_CONFIG = { long: { label: 'Long Terme', sublabel: 'Régime structurel', color: 'text-violet-400', border: 'border-violet-700/50', bg: 'bg-violet-950/30', dot: 'bg-violet-500', badge: 'bg-violet-900/50 text-violet-300 border-violet-700/50', }, medium: { label: 'Moyen Terme', sublabel: 'Régime courant', color: 'text-blue-400', border: 'border-blue-700/50', bg: 'bg-blue-950/30', dot: 'bg-blue-500', badge: 'bg-blue-900/50 text-blue-300 border-blue-700/50', }, short: { label: 'Court Terme', sublabel: 'Catalyseur récent', color: 'text-emerald-400', border: 'border-emerald-700/50', bg: 'bg-emerald-950/30', dot: 'bg-emerald-500', badge: 'bg-emerald-900/50 text-emerald-300 border-emerald-700/50', }, } function daysSince(refDate: string, startDate: string): number { const d0 = new Date(startDate) const d1 = new Date(refDate) return Math.floor((d1.getTime() - d0.getTime()) / 86_400_000) } function fmtDate(d: string): string { return new Date(d).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short', year: 'numeric' }) } function offsetDate(d: string, days: number): string { const dt = new Date(d) dt.setDate(dt.getDate() + days) return dt.toISOString().split('T')[0] } function todayStr(): string { return new Date().toISOString().split('T')[0] } function ContextPanel({ level, event, commentary, refDate, }: { level: 'long' | 'medium' | 'short' event: MarketEvent | null commentary: string refDate: string }) { const cfg = LEVEL_CONFIG[level] return (
{/* Header */}
{cfg.label}
{cfg.sublabel}
{event ? ( <> {/* Event card */}
{event.name}
{event.category}
J+{daysSince(refDate, event.start_date)} ({fmtDate(event.start_date)}) {event.end_date && ( → {fmtDate(event.end_date)} )} {!event.end_date && ( en cours )}

{event.description}

{event.market_impact && (

{event.market_impact}

)} {/* Affected assets */} {event.affected_assets && (() => { try { const assets = JSON.parse(event.affected_assets) if (assets.length > 0) return (
{assets.map((a: string) => ( {a} ))}
) } catch { /* ignore */ } return null })()}
{/* AI Commentary */} {commentary ? (
Analyse IA
{commentary}
) : (
Commentaire IA non généré — cliquez "Générer IA"
)} ) : (
Aucun événement {cfg.label.toLowerCase()} actif à cette date
)}
) } export default function Timeline() { const [searchParams] = useSearchParams() const [refDate, setRefDate] = useState(() => { const p = searchParams.get('date') return p && p >= '2020-01-01' && p <= todayStr() ? p : todayStr() }) const [ctx, setCtx] = useState(null) const [allEvents, setAllEvents] = useState([]) const [loading, setLoading] = useState(false) const [generating, setGenerating] = useState(false) const [bootstrapped, setBootstrapped] = useState(false) const [showManager, setShowManager] = useState(false) const [bootstrappingMA, setBootstrappingMA] = useState(false) const [maResult, setMaResult] = useState<{ detected: number; saved: number } | null>(null) const fetchDay = useCallback(async (d: string) => { setLoading(true) try { const res = await api.get(`/timeline/day/${d}`) setCtx(res.data) } catch { /* ignore */ } finally { setLoading(false) } }, []) const fetchEvents = useCallback(async () => { try { const res = await api.get('/timeline/events') setAllEvents(res.data) } catch { /* ignore */ } }, []) const bootstrap = useCallback(async () => { try { await api.post('/timeline/bootstrap') setBootstrapped(true) await fetchEvents() await fetchDay(refDate) } catch { /* ignore */ } }, [refDate, fetchDay, fetchEvents]) const generateAI = useCallback(async () => { setGenerating(true) try { const res = await api.post(`/timeline/generate/${refDate}`) const data = res.data setCtx(prev => prev ? { ...prev, long_commentary: data.long_commentary, medium_commentary: data.medium_commentary, short_commentary: data.short_commentary, has_commentary: true, } : prev) } catch { /* ignore */ } finally { setGenerating(false) } }, [refDate]) useEffect(() => { fetchEvents() }, [fetchEvents]) useEffect(() => { fetchDay(refDate) }, [refDate, fetchDay]) // Auto-bootstrap on first load if no events useEffect(() => { if (!bootstrapped && allEvents.length === 0) { bootstrap() } }, [allEvents.length, bootstrapped, bootstrap]) const bootstrapMA = useCallback(async () => { setBootstrappingMA(true) setMaResult(null) try { const res = await api.post('/timeline/bootstrap-ma') setMaResult({ detected: res.data.detected, saved: res.data.saved }) await fetchEvents() } catch { /* ignore */ } finally { setBootstrappingMA(false) } }, [fetchEvents]) const navigate = (days: number) => setRefDate(prev => offsetDate(prev, days)) return (
{/* Header */}

Timeline Navigator

Contexte historique 3 temporalités — COVID (2020) → aujourd'hui

{maResult && ( {maResult.saved} périodes ajoutées )}
{/* Date Navigator */}
{/* Prev buttons */}
{/* Date input */}
setRefDate(e.target.value)} className="bg-dark-700 border border-slate-700/40 rounded-lg px-3 py-1.5 text-sm text-white focus:outline-none focus:border-violet-500/50" /> {fmtDate(refDate)}
{/* Next buttons */}
{/* Generate AI */}
{/* Vertical timeline */} {allEvents.length > 0 && ( )} {/* 3 context panels */} {loading ? (
Chargement du contexte...
) : ctx ? (
) : (
Aucun contexte disponible
)} {/* Event Manager */} {showManager && ( )}
) }