Add explain analysis workflow and UI
This commit is contained in:
157
frontend/src/components/explain/ExplainEventsSection.jsx
Normal file
157
frontend/src/components/explain/ExplainEventsSection.jsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import React from 'react';
|
||||
import { formatDateTime } from '../../utils/formatters';
|
||||
|
||||
export default function ExplainEventsSection({
|
||||
explainTimeline,
|
||||
isOpen,
|
||||
onToggle,
|
||||
availableEventDates,
|
||||
selectedEventDate,
|
||||
onSelectEventDate,
|
||||
eventCategoryCounts,
|
||||
activeEventCategory,
|
||||
onSelectEventCategory,
|
||||
eventCategoryMeta,
|
||||
visibleExplainEvents,
|
||||
}) {
|
||||
return (
|
||||
<div className="section">
|
||||
<div className="section-header">
|
||||
<h2 className="section-title">关键事件时间线</h2>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div style={{ fontSize: 11, color: '#666666' }}>
|
||||
图上点击事件点可切换对应日期
|
||||
</div>
|
||||
<button
|
||||
onClick={onToggle}
|
||||
style={{
|
||||
border: '1px solid #111111',
|
||||
background: isOpen ? '#111111' : '#ffffff',
|
||||
color: isOpen ? '#ffffff' : '#111111',
|
||||
padding: '7px 10px',
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 11,
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
{isOpen ? '收起关键事件' : `展开关键事件 ${explainTimeline.length}`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{explainTimeline.length === 0 ? (
|
||||
<div className="empty-state">当前还没有可以串起来看的关键事件。</div>
|
||||
) : !isOpen ? (
|
||||
<div className="empty-state">关键事件默认收起,需要时再展开查看和筛选。</div>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gap: 14 }}>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{availableEventDates.map((dateKey) => {
|
||||
const isActive = dateKey === selectedEventDate;
|
||||
return (
|
||||
<button
|
||||
key={dateKey}
|
||||
onClick={() => onSelectEventDate(dateKey)}
|
||||
style={{
|
||||
border: '1px solid #111111',
|
||||
background: isActive ? '#111111' : '#ffffff',
|
||||
color: isActive ? '#ffffff' : '#111111',
|
||||
padding: '7px 10px',
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 11,
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
{dateKey}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{Object.entries(eventCategoryMeta)
|
||||
.filter(([key]) => (eventCategoryCounts[key] || 0) > 0 || key === 'all')
|
||||
.map(([key, meta]) => {
|
||||
const isActive = key === activeEventCategory;
|
||||
return (
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => onSelectEventCategory(key)}
|
||||
style={{
|
||||
border: `1px solid ${meta.color}`,
|
||||
background: isActive ? meta.color : '#ffffff',
|
||||
color: isActive ? '#ffffff' : meta.color,
|
||||
padding: '8px 10px',
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 11,
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
{meta.label} {eventCategoryCounts[key] || 0}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{visibleExplainEvents.length === 0 ? (
|
||||
<div className="empty-state">当前日期下没有符合筛选条件的事件</div>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: 16 }}>
|
||||
{visibleExplainEvents.map((event) => {
|
||||
const accent = event.tone === 'positive' ? '#00C853' : event.tone === 'negative' ? '#FF1744' : '#000000';
|
||||
const categoryMeta = eventCategoryMeta[event.category] || eventCategoryMeta.other;
|
||||
return (
|
||||
<div
|
||||
key={event.id}
|
||||
style={{
|
||||
border: '1px solid #000000',
|
||||
background: '#ffffff',
|
||||
padding: 14,
|
||||
minHeight: 180
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, marginBottom: 8 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
||||
<span style={{
|
||||
display: 'inline-flex',
|
||||
padding: '2px 6px',
|
||||
border: `1px solid ${categoryMeta.color}`,
|
||||
color: categoryMeta.color,
|
||||
fontSize: 10,
|
||||
fontWeight: 700
|
||||
}}>
|
||||
{categoryMeta.label}
|
||||
</span>
|
||||
<strong style={{ fontSize: 13 }}>{event.title}</strong>
|
||||
</div>
|
||||
<span style={{ fontSize: 10, color: '#666666', whiteSpace: 'nowrap' }}>
|
||||
{formatDateTime(event.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
|
||||
<span style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: '50%',
|
||||
background: accent
|
||||
}} />
|
||||
<span style={{ fontSize: 10, color: '#666666', textTransform: 'uppercase', letterSpacing: 0.6 }}>
|
||||
{event.meta}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 12, lineHeight: 1.7, color: '#000000', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
||||
{event.body}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user