/** * EventPanel - 通用事件面板组件 (Redux 版本) * 用于显示、编辑和管理投资计划或复盘 * * 通过 props 配置差异化行为: * - type: 'plan' | 'review' * - colorScheme: 主题色 * - label: 显示文案 */ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Box, Grid, VStack, Text, Spinner, Center, Icon, useToast, } from '@chakra-ui/react'; import { FiFileText } from 'react-icons/fi'; import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { fetchAllEvents, removeEvent, selectPlans, selectReviews, selectPlanningLoading, } from '@/store/slices/planningSlice'; import { getApiBase } from '@/utils/apiConfig'; import { EventFormModal } from './EventFormModal'; import { FUIEventCard } from './FUIEventCard'; import type { InvestmentEvent } from '@/types'; import { logger } from '@/utils/logger'; /** * EventPanel Props */ export interface EventPanelProps { /** 事件类型 */ type: 'plan' | 'review'; /** 主题颜色 */ colorScheme: string; /** 显示标签(如 "计划" 或 "复盘") */ label: string; /** 外部触发打开模态框的计数器 */ openModalTrigger?: number; } /** * EventPanel 组件 * 通用事件列表面板,显示投资计划或复盘 */ export const EventPanel: React.FC = ({ type, colorScheme, label, openModalTrigger, }) => { const dispatch = useAppDispatch(); const toast = useToast(); // Redux 状态 const plans = useAppSelector(selectPlans); const reviews = useAppSelector(selectReviews); const loading = useAppSelector(selectPlanningLoading); // 根据类型选择事件列表 const events = type === 'plan' ? plans : reviews; // 弹窗状态 const [isModalOpen, setIsModalOpen] = useState(false); const [editingItem, setEditingItem] = useState(null); const [modalMode, setModalMode] = useState<'create' | 'edit'>('create'); // 使用 ref 记录上一次的 trigger 值,避免组件挂载时误触发 const prevTriggerRef = useRef(openModalTrigger || 0); // 监听外部触发打开新建模态框(修复 bug:只在值变化时触发) useEffect(() => { if (openModalTrigger !== undefined && openModalTrigger > prevTriggerRef.current) { // 只有当 trigger 值增加时才打开弹窗 handleOpenModal(null); } prevTriggerRef.current = openModalTrigger || 0; }, [openModalTrigger]); // 打开编辑/新建模态框 const handleOpenModal = (item: InvestmentEvent | null = null): void => { if (item) { setEditingItem(item); setModalMode('edit'); } else { setEditingItem(null); setModalMode('create'); } setIsModalOpen(true); }; // 关闭弹窗 const handleCloseModal = (): void => { setIsModalOpen(false); setEditingItem(null); }; // 删除数据 - 乐观更新模式 const handleDelete = async (id: number): Promise => { if (!window.confirm('确定要删除吗?')) return; // ① 立即从 UI 移除 dispatch(removeEvent(id)); // ② 后台发送 API 请求 try { const base = getApiBase(); const response = await fetch(`${base}/api/account/investment-plans/${id}`, { method: 'DELETE', credentials: 'include', }); if (response.ok) { logger.info('EventPanel', `删除${label}成功`, { itemId: id }); toast({ title: '删除成功', status: 'success', duration: 2000, }); } else { throw new Error('删除失败'); } } catch (error) { // ③ 失败回滚 - 重新加载数据 dispatch(fetchAllEvents()); logger.error('EventPanel', 'handleDelete rollback', error, { itemId: id }); toast({ title: '删除失败,请重试', status: 'error', duration: 3000, }); } }; // 刷新数据 const handleRefresh = useCallback(() => { dispatch(fetchAllEvents()); }, [dispatch]); // 使用 useCallback 优化回调函数 const handleEdit = useCallback((item: InvestmentEvent) => { handleOpenModal(item); }, []); // 颜色主题 const secondaryText = 'rgba(255, 255, 255, 0.6)'; return ( {loading ? (
) : events.length === 0 ? (
暂无投资{label}
) : ( {events.map(event => ( ))} )}
{/* 使用通用弹窗组件 */}
); }; export default EventPanel;