imetting/frontend/src/components/MeetingTimeline.jsx

215 lines
8.5 KiB
React
Raw Normal View History

import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Clock, Users, FileText, User, Edit, Calendar , Trash2, MoreVertical } from 'lucide-react';
import TagDisplay from './TagDisplay';
import ConfirmDialog from './ConfirmDialog';
import Dropdown from './Dropdown';
import MarkdownRenderer from './MarkdownRenderer';
import tools from '../utils/tools';
import './MeetingTimeline.css';
const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore = false, onLoadMore, loadingMore = false, filterType = 'all', searchQuery = '', selectedTags = [] }) => {
const [deleteConfirmInfo, setDeleteConfirmInfo] = useState(null);
const navigate = useNavigate();
const shouldShowMoreButton = (summary, maxLines = 3, maxLength = 100) => {
if (!summary) return false;
const lines = summary.split('\n');
return lines.length > maxLines || summary.length > maxLength;
};
const handleEditClick = (meetingId) => {
navigate(`/meetings/edit/${meetingId}`);
};
const handleDeleteClick = (meeting) => {
setDeleteConfirmInfo({
id: meeting.meeting_id,
title: meeting.title
});
};
const handleConfirmDelete = async () => {
if (onDeleteMeeting && deleteConfirmInfo) {
await onDeleteMeeting(deleteConfirmInfo.id);
}
setDeleteConfirmInfo(null);
};
const sortedDates = Object.keys(meetingsByDate).sort((a, b) => new Date(b) - new Date(a));
if (sortedDates.length === 0) {
return (
<div className="timeline-empty">
<Calendar size={48} />
<h3>暂无会议记录</h3>
<p>您还没有参与任何会议</p>
</div>
);
}
return (
<div className="timeline-container">
<div className="timeline-line"></div>
{sortedDates.map(date => (
<div key={date} className="timeline-date-section">
<div className="timeline-date-node">
<span className="date-text">{tools.formatDateLong(date)}</span>
</div>
<div className="meetings-for-date">
{meetingsByDate[date].map(meeting => {
const isCreator = String(meeting.creator_id) === String(currentUser.user_id);
const cardClass = isCreator ? 'created-by-me' : 'attended-by-me';
return (
<div className="meeting-card-wrapper" key={meeting.meeting_id}>
<Link
to={`/meetings/${meeting.meeting_id}`}
state={{
filterContext: {
filterType,
searchQuery,
selectedTags
}
}}
>
<div className={`meeting-card ${cardClass} meeting-card-link`}>
<div className="meeting-content">
<div className="meeting-header">
<div className="meeting-title-section">
<div className="title-and-tags">
<h3 className="meeting-title">
{meeting.title}
{meeting.tags && meeting.tags.length > 0 && (
<TagDisplay
tags={meeting.tags.map(tag => tag.name)}
size="small"
maxDisplay={3}
showIcon={true}
className="inline-tags"
/>
)}
</h3>
</div>
{isCreator && (
<Dropdown
trigger={
<button className="dropdown-trigger">
<MoreVertical size={18} />
</button>
}
items={[
{
icon: <Edit size={16} />,
label: '编辑',
onClick: () => handleEditClick(meeting.meeting_id)
},
{
icon: <Trash2 size={16} />,
label: '删除',
onClick: () => handleDeleteClick(meeting),
danger: true
}
]}
align="right"
/>
)}
</div>
<div className="meeting-meta">
<div className="meta-item">
<Clock size={16} />
<span>{tools.formatTime(meeting.meeting_time)}</span>
</div>
<div className="meta-item">
<Users size={16} />
<span>{meeting.attendees.length} 人参会</span>
</div>
</div>
</div>
<div className="meeting-body">
{meeting.attendees && meeting.attendees.length > 0 && (
<div className="attendees-section">
<span className="attendees-label">参会人</span>
<div className="attendees-list">
{meeting.attendees.map((attendee, idx) => (
<span key={idx} className="attendee-tag">
{typeof attendee === 'string' ? attendee : attendee.caption}
</span>
))}
</div>
</div>
)}
{meeting.summary && (
<div className="summary-section">
<div className="summary-header">
<FileText size={16} />
<span>会议摘要</span>
</div>
<div className="summary-content">
<MarkdownRenderer
content={tools.truncateSummary(meeting.summary)}
className="markdown-content"
/>
{shouldShowMoreButton(meeting.summary) && (
<div className="summary-more-hint">
<span className="more-text">点击查看完整摘要</span>
</div>
)}
</div>
</div>
)}
</div>
<div className="meeting-footer">
<div className="creator-info">
<User size={14} />
<span>创建人: {meeting.creator_username}</span>
</div>
</div>
</div>
</div>
</Link>
</div>
);
})}
</div>
</div>
))}
{/* 加载更多/加载完毕 UI */}
<div className="timeline-footer">
{hasMore ? (
<button
className="load-more-btn"
onClick={onLoadMore}
disabled={loadingMore}
>
<span>{loadingMore ? '加载中...' : '加载更多'}</span>
</button>
) : (
sortedDates.length > 0 && (
<div className="all-loaded">
<span>已加载全部会议</span>
</div>
)
)}
</div>
{/* 删除会议确认对话框 */}
<ConfirmDialog
isOpen={!!deleteConfirmInfo}
onClose={() => setDeleteConfirmInfo(null)}
onConfirm={handleConfirmDelete}
title="删除会议"
message={`确定要删除会议"${deleteConfirmInfo?.title}"吗?此操作无法撤销。`}
confirmText="删除"
cancelText="取消"
type="danger"
/>
</div>
);
};
export default MeetingTimeline;