import { useState, useEffect } from 'react';
import { fetchEvents } from '../api';
const TYPE_STYLE = {
geopolitical: { color: '#ef4444', bg: 'rgba(239,68,68,.12)', label: '地缘' },
supply: { color: '#10b981', bg: 'rgba(16,185,129,.12)', label: '供给' },
demand: { color: '#f59e0b', bg: 'rgba(245,158,11,.12)', label: '需求' },
macro: { color: '#3b82f6', bg: 'rgba(59,130,246,.12)', label: '宏观' },
policy: { color: '#8b5cf6', bg: 'rgba(139,92,246,.12)', label: '政策' },
};
const IMPACT_COLOR = { bullish: '#10b981', bearish: '#ef4444', neutral: '#94a3b8' };
const IMPACT_LABEL = { bullish: '利多', bearish: '利空', neutral: '中性' };
const FACTOR_ZH = {
vix_lag1: 'VIX', usd_index: 'USD指数', supply_saudi: '沙特产量',
Brent_spot: 'Brent', mom1m_lag1: '动量', natgas_spot_henry: '天然气',
pmi_us_mfg: 'PMI制造', ipi_us: '工业产出', rig_count_us_new: '钻井数',
news_oil_sentiment: '新闻情绪', news_geo_tone: '地缘情绪',
};
export default function EventTimeline({ limit = 5 }) {
const [events, setEvents] = useState([]);
const [expanded, setExpanded] = useState(null);
const [filter, setFilter] = useState('all');
useEffect(() => {
fetchEvents().then(d => {
if (Array.isArray(d)) {
d.sort((a, b) => b.date.localeCompare(a.date));
setEvents(d);
}
}).catch(() => {});
}, []);
const filtered = filter === 'all' ? events : events.filter(e => e.type === filter);
const shown = filtered.slice(0, expanded !== null ? 50 : limit);
if (!events.length) return null;
return (
🔗 因果叙事链
事件 → 因子异动 → 风险信号 → 对冲建议
{[['all', '全部'], ...Object.entries(TYPE_STYLE).map(([k, v]) => [k, v.label])].map(([k, label]) => (
))}
{/* Timeline */}
{/* Vertical line */}
{shown.map((ev, i) => {
const ts = TYPE_STYLE[ev.type] || TYPE_STYLE.macro;
const isOpen = expanded === i;
return (
{/* Dot on timeline */}
{/* Event card */}
setExpanded(isOpen ? null : i)}
style={{
background: isOpen ? ts.bg : 'rgba(255,255,255,.02)',
border: `1px solid ${isOpen ? ts.color + '33' : 'rgba(255,255,255,.06)'}`,
borderRadius: 10, padding: '10px 14px', cursor: 'pointer',
transition: 'all .2s ease',
}}>
{/* Header row */}
{ev.icon}
{ev.title}
{IMPACT_LABEL[ev.impact]}
{ev.date}
{/* Expanded content: causal chain */}
{isOpen && (
{/* Narrative */}
{ev.narrative}
{/* Causal chain visualization */}
{/* Step 1: Event */}
① 事件触发
{ev.icon}
{ev.title.slice(0, 12)}
{/* Step 2: Factor changes */}
② 因子异动
{ev.factor_changes && Object.entries(ev.factor_changes).slice(0, 3).map(([k, v]) => (
{FACTOR_ZH[k] || k}{' '}
0 ? '#10b981' : '#ef4444', fontWeight: 700 }}>
{v > 0 ? '↑' : '↓'}{Math.abs(v).toFixed(1)}
))}
{/* Step 3: Risk signal */}
{/* Step 4: Hedging action */}
④ 对冲建议
{ev.hedging_action}
{/* Arrow chain */}
事件 →
因子 →
信号 →
对冲
)}
);
})}
{/* Show more */}
{filtered.length > limit && expanded === null && (
)}
);
}