232 lines
7.5 KiB
JavaScript
232 lines
7.5 KiB
JavaScript
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';
|
|
import ActionButton from './ActionButton';
|
|
import tools from '../utils/tools';
|
|
import './MeetingInfoCard.css';
|
|
|
|
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',
|
|
},
|
|
};
|
|
|
|
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' }),
|
|
};
|
|
};
|
|
|
|
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();
|
|
};
|
|
|
|
const MeetingTimeline = ({
|
|
meetingsByDate,
|
|
currentUser,
|
|
onDeleteMeeting,
|
|
hasMore = false,
|
|
onLoadMore,
|
|
loadingMore = false,
|
|
filterType = 'all',
|
|
searchQuery = '',
|
|
selectedTags = [],
|
|
}) => {
|
|
const navigate = useNavigate();
|
|
|
|
const handleEditClick = (event, meetingId) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
navigate(`/meetings/edit/${meetingId}`);
|
|
};
|
|
|
|
const handleDeleteClick = (event, meeting) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
onDeleteMeeting(meeting.meeting_id);
|
|
};
|
|
|
|
const sortedDates = Object.keys(meetingsByDate).sort((a, b) => new Date(b) - new Date(a));
|
|
|
|
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>
|
|
),
|
|
dot: <span className="timeline-date-dot" />,
|
|
children: (
|
|
<div className="timeline-date-group">
|
|
{meetingsByDate[date].map((meeting) => {
|
|
const isCreator = String(meeting.creator_id) === String(currentUser.user_id);
|
|
const roleMeta = isCreator ? ROLE_META.creator : ROLE_META.attendee;
|
|
const statusMeta = getStatusMeta(meeting);
|
|
const summaryPreview = getSummaryPreview(meeting.summary);
|
|
const hasSummary = Boolean(summaryPreview);
|
|
|
|
return (
|
|
<Card
|
|
key={meeting.meeting_id}
|
|
hoverable
|
|
variant="borderless"
|
|
className="timeline-meeting-card console-surface shared-meeting-card"
|
|
onClick={() => navigate(`/meetings/${meeting.meeting_id}`, {
|
|
state: { filterContext: { filterType, searchQuery, selectedTags } },
|
|
})}
|
|
>
|
|
<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)}
|
|
/>
|
|
</Space>
|
|
) : 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>
|
|
</Space>
|
|
<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>
|
|
|
|
<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>
|
|
</Space>
|
|
<ActionButton tone="view" variant="textSm" icon={<ArrowRightOutlined />} className="shared-meeting-card-footer-link">
|
|
查看详情
|
|
</ActionButton>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
),
|
|
};
|
|
});
|
|
|
|
return (
|
|
<div className="modern-timeline">
|
|
<Timeline mode="left" items={timelineItems} />
|
|
<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>
|
|
);
|
|
};
|
|
|
|
export default MeetingTimeline;
|