imetting/frontend/src/components/MeetingTimeline.jsx

174 lines
6.2 KiB
React
Raw Normal View History

2026-03-26 06:55:12 +00:00
import React from 'react';
import { useNavigate } from 'react-router-dom';
import {
App,
Avatar,
Button,
Card,
Divider,
Dropdown,
Space,
Tag,
Timeline,
Typography,
} from 'antd';
import {
ArrowRightOutlined,
CalendarOutlined,
ClockCircleOutlined,
DeleteOutlined,
EditOutlined,
FileTextOutlined,
MoreOutlined,
TeamOutlined,
UserOutlined,
} from '@ant-design/icons';
import MarkdownRenderer from './MarkdownRenderer';
import tools from '../utils/tools';
2026-03-26 06:55:12 +00:00
const { Title, Text, Paragraph } = Typography;
2026-03-26 06:55:12 +00:00
const formatDateMeta = (date) => {
const parsed = new Date(date);
if (Number.isNaN(parsed.getTime())) {
return { main: date, sub: '' };
}
return {
main: tools.formatDateLong(date),
sub: parsed.toLocaleDateString('zh-CN', { weekday: 'long' }),
};
2026-03-26 06:55:12 +00:00
};
const MeetingTimeline = ({
meetingsByDate,
currentUser,
onDeleteMeeting,
hasMore = false,
onLoadMore,
loadingMore = false,
filterType = 'all',
searchQuery = '',
selectedTags = [],
}) => {
const { modal } = App.useApp();
const navigate = useNavigate();
2026-03-26 06:55:12 +00:00
const handleEditClick = (event, meetingId) => {
event.preventDefault();
event.stopPropagation();
navigate(`/meetings/edit/${meetingId}`);
};
2026-03-26 06:55:12 +00:00
const handleDeleteClick = (event, meeting) => {
event.preventDefault();
event.stopPropagation();
modal.confirm({
title: '删除会议',
content: `确定要删除会议“${meeting.title}”吗?此操作无法撤销。`,
okText: '删除',
okType: 'danger',
onOk: () => onDeleteMeeting(meeting.meeting_id),
});
};
const sortedDates = Object.keys(meetingsByDate).sort((a, b) => new Date(b) - new Date(a));
2026-03-26 06:55:12 +00:00
const timelineItems = sortedDates.map((date) => {
const dateMeta = formatDateMeta(date);
return {
label: (
<div className="timeline-date-label">
<Text className="timeline-date-main">{dateMeta.main}</Text>
{dateMeta.sub ? <Text className="timeline-date-sub">{dateMeta.sub}</Text> : null}
</div>
),
children: (
<div className="timeline-date-group">
{meetingsByDate[date].map((meeting) => {
const isCreator = String(meeting.creator_id) === String(currentUser.user_id);
const menuItems = [
{ key: 'edit', label: '编辑', icon: <EditOutlined />, onClick: ({ domEvent }) => handleEditClick(domEvent, meeting.meeting_id) },
{ key: 'delete', label: '删除', icon: <DeleteOutlined />, danger: true, onClick: ({ domEvent }) => handleDeleteClick(domEvent, meeting) },
];
2026-03-26 06:55:12 +00:00
return (
<Card
key={meeting.meeting_id}
hoverable
className="timeline-meeting-card"
style={{ borderLeft: isCreator ? '4px solid #1677ff' : '4px solid #52c41a' }}
onClick={() => navigate(`/meetings/${meeting.meeting_id}`, {
state: { filterContext: { filterType, searchQuery, selectedTags } },
})}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 14, gap: 16 }}>
<Space direction="vertical" size={6} style={{ flex: 1 }}>
<Title level={4} style={{ margin: 0, fontSize: 20 }}>{meeting.title}</Title>
<Space size={12} split={<Divider type="vertical" />} wrap>
<Text type="secondary"><ClockCircleOutlined /> {tools.formatTime(meeting.meeting_time)}</Text>
<Text type="secondary"><TeamOutlined /> {meeting.attendees?.length || 0} </Text>
<Space size={[6, 6]} wrap>
{meeting.tags?.slice(0, 4).map((tag) => (
<Tag key={tag.id} color="blue" bordered={false} style={{ fontSize: 12, borderRadius: 999 }}>
{tag.name}
</Tag>
))}
</Space>
</Space>
</Space>
{isCreator ? (
<Dropdown menu={{ items: menuItems }} placement="bottomRight" arrow trigger={['click']}>
<Button type="text" icon={<MoreOutlined />} className="timeline-action-trigger" onClick={(event) => event.stopPropagation()} />
</Dropdown>
) : null}
</div>
2026-03-26 06:55:12 +00:00
{meeting.summary ? (
<div className="timeline-summary-box">
<Space size={8} style={{ marginBottom: 8, display: 'flex' }}>
<FileTextOutlined style={{ color: '#1677ff' }} />
<Text strong>会议摘要</Text>
</Space>
<div className="timeline-summary-content">
<Paragraph ellipsis={{ rows: 2 }} type="secondary" style={{ margin: 0, fontSize: 13 }}>
<MarkdownRenderer content={tools.truncateSummary(meeting.summary)} />
</Paragraph>
</div>
2026-03-26 06:55:12 +00:00
</div>
) : null}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
<Space>
<Avatar size="small" src={meeting.creator_avatar_url} icon={<UserOutlined />} />
<Text type="secondary" style={{ fontSize: 12 }}>{meeting.creator_username}</Text>
</Space>
<Button type="text" size="small" icon={<ArrowRightOutlined />} className="timeline-footer-link">
查看详情
</Button>
</div>
2026-03-26 06:55:12 +00:00
</Card>
);
})}
</div>
2026-03-26 06:55:12 +00:00
),
};
});
2026-03-26 06:55:12 +00:00
return (
<div className="modern-timeline">
<Timeline mode="left" items={timelineItems} />
<div style={{ textAlign: 'center', marginTop: 28 }}>
{hasMore ? (
2026-03-26 06:55:12 +00:00
<Button onClick={onLoadMore} loading={loadingMore} icon={<CalendarOutlined />}>
加载更多
</Button>
) : (
2026-03-26 06:55:12 +00:00
<Divider plain><Text type="secondary">已加载全部会议</Text></Divider>
)}
</div>
</div>
);
};
2026-03-26 06:55:12 +00:00
export default MeetingTimeline;