imetting/frontend/src/components/MeetingTimeline.jsx

232 lines
7.5 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 {
Avatar,
Button,
Card,
Divider,
Space,
Tag,
Timeline,
Typography,
} from 'antd';
import {
ArrowRightOutlined,
CalendarOutlined,
ClockCircleOutlined,
DeleteOutlined,
EditOutlined,
TeamOutlined,
UserOutlined,
} from '@ant-design/icons';
2026-04-03 16:25:53 +00:00
import ActionButton from './ActionButton';
import tools from '../utils/tools';
2026-04-03 16:25:53 +00:00
import './MeetingInfoCard.css';
2026-04-03 16:25:53 +00:00
const { Text, Paragraph } = Typography;
const ROLE_META = {
creator: {
label: '我发起',
tagClass: 'console-tag-soft-blue',
},
attendee: {
label: '我参与',
tagClass: 'console-tag-soft-green',
},
};
const STATUS_META = {
completed: {
label: '已完成',
tagClass: 'console-tag-soft-green',
},
failed: {
label: '失败',
tagClass: 'console-tag-soft-red',
},
transcribing: {
label: '转写中',
tagClass: 'console-tag-soft-blue',
},
summarizing: {
label: '总结中',
tagClass: 'console-tag-soft-orange',
},
pending: {
label: '待处理',
tagClass: 'console-tag-soft-default',
},
};
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
};
2026-04-03 16:25:53 +00:00
const getStatusMeta = (meeting) => STATUS_META[meeting?.overall_status || 'pending'] || STATUS_META.pending;
const getSummaryPreview = (summary) => {
if (!summary) return '';
return tools.stripMarkdown(summary).replace(/\s+/g, ' ').trim();
};
2026-03-26 06:55:12 +00:00
const MeetingTimeline = ({
meetingsByDate,
currentUser,
onDeleteMeeting,
hasMore = false,
onLoadMore,
loadingMore = false,
filterType = 'all',
searchQuery = '',
selectedTags = [],
}) => {
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();
2026-04-03 16:25:53 +00:00
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>
),
2026-04-03 16:25:53 +00:00
dot: <span className="timeline-date-dot" />,
2026-03-26 06:55:12 +00:00
children: (
<div className="timeline-date-group">
{meetingsByDate[date].map((meeting) => {
const isCreator = String(meeting.creator_id) === String(currentUser.user_id);
2026-04-03 16:25:53 +00:00
const roleMeta = isCreator ? ROLE_META.creator : ROLE_META.attendee;
const statusMeta = getStatusMeta(meeting);
const summaryPreview = getSummaryPreview(meeting.summary);
const hasSummary = Boolean(summaryPreview);
2026-03-26 06:55:12 +00:00
return (
<Card
key={meeting.meeting_id}
hoverable
2026-04-03 16:25:53 +00:00
variant="borderless"
className="timeline-meeting-card console-surface shared-meeting-card"
2026-03-26 06:55:12 +00:00
onClick={() => navigate(`/meetings/${meeting.meeting_id}`, {
state: { filterContext: { filterType, searchQuery, selectedTags } },
})}
>
2026-04-03 16:25:53 +00:00
<div className="shared-meeting-card-shell">
<div className="shared-meeting-card-top">
<Space size={10} wrap>
<Tag bordered={false} className={`${roleMeta.tagClass} console-tag-large`}>
{roleMeta.label}
</Tag>
<Tag bordered={false} className={`${statusMeta.tagClass} console-tag-large`}>
{statusMeta.label}
</Tag>
</Space>
{isCreator ? (
<Space size={8}>
<ActionButton
tone="edit"
variant="iconSm"
tooltip="编辑"
icon={<EditOutlined />}
onClick={(event) => handleEditClick(event, meeting.meeting_id)}
/>
<ActionButton
tone="delete"
variant="iconSm"
tooltip="删除"
icon={<DeleteOutlined />}
onClick={(event) => handleDeleteClick(event, meeting)}
/>
2026-03-26 06:55:12 +00:00
</Space>
2026-04-03 16:25:53 +00:00
) : null}
</div>
<div className="shared-meeting-card-title-block">
<Paragraph
className="shared-meeting-card-title"
ellipsis={{ rows: 2, tooltip: meeting.title }}
>
{meeting.title}
</Paragraph>
</div>
<div className={`shared-meeting-card-summary ${hasSummary ? '' : 'is-empty'}`}>
<Paragraph
className="shared-meeting-card-summary-content"
ellipsis={hasSummary ? { rows: 2, tooltip: summaryPreview } : false}
>
{hasSummary ? summaryPreview : '暂无摘要'}
</Paragraph>
</div>
<div className="shared-meeting-card-meta">
<Space size={10}>
<ClockCircleOutlined className="shared-meeting-card-meta-icon" />
<Text className="shared-meeting-card-meta-text">
{tools.formatTime(meeting.meeting_time || meeting.created_at)}
</Text>
2026-03-26 06:55:12 +00:00
</Space>
2026-04-03 16:25:53 +00:00
<Divider type="vertical" />
<Space size={10}>
<TeamOutlined className="shared-meeting-card-meta-icon" />
<Text className="shared-meeting-card-meta-text">{meeting.attendees?.length || 0} </Text>
</Space>
</div>
2026-04-03 16:25:53 +00:00
<div className="shared-meeting-card-footer">
<Space>
<Avatar size="small" src={meeting.creator_avatar_url} icon={<UserOutlined />} />
<Text type="secondary" className="shared-meeting-card-footer-user">{meeting.creator_username}</Text>
2026-03-26 06:55:12 +00:00
</Space>
2026-04-03 16:25:53 +00:00
<ActionButton tone="view" variant="textSm" icon={<ArrowRightOutlined />} className="shared-meeting-card-footer-link">
查看详情
</ActionButton>
2026-03-26 06:55:12 +00:00
</div>
</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} />
2026-04-03 16:25:53 +00:00
<div style={{ textAlign: 'center', marginTop: 28 }}>
{hasMore ? (
<Button className="btn-pill-secondary" onClick={onLoadMore} loading={loadingMore} icon={<CalendarOutlined />}>
加载更多
</Button>
) : (
<Divider plain><Text type="secondary">已加载全部会议</Text></Divider>
)}
</div>
</div>
);
};
2026-03-26 06:55:12 +00:00
export default MeetingTimeline;