// src/views/Dashboard/components/InvestmentPlansAndReviews.js import React, { useState, useEffect, useCallback } from 'react'; import { Box, Card, CardHeader, CardBody, Heading, VStack, HStack, Text, Button, Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton, useDisclosure, Badge, IconButton, Flex, useColorModeValue, Divider, Icon, Input, FormControl, FormLabel, Textarea, Select, useToast, Spinner, Center, Tag, TagLabel, TagLeftIcon, TagCloseButton, Grid, Tabs, TabList, TabPanels, Tab, TabPanel, InputGroup, InputLeftElement, } from '@chakra-ui/react'; import { FiCalendar, FiClock, FiEdit2, FiTrash2, FiSave, FiPlus, FiFileText, FiTarget, FiTrendingUp, FiHash, FiCheckCircle, FiXCircle, FiAlertCircle, } from 'react-icons/fi'; import moment from 'moment'; import 'moment/locale/zh-cn'; import { logger } from '../../../utils/logger'; moment.locale('zh-cn'); export default function InvestmentPlansAndReviews({ type = 'both' }) { const { isOpen, onOpen, onClose } = useDisclosure(); const toast = useToast(); // 颜色主题 const bgColor = useColorModeValue('white', 'gray.800'); const borderColor = useColorModeValue('gray.200', 'gray.600'); const textColor = useColorModeValue('gray.700', 'white'); const secondaryText = useColorModeValue('gray.600', 'gray.400'); const cardBg = useColorModeValue('gray.50', 'gray.700'); const [plans, setPlans] = useState([]); const [reviews, setReviews] = useState([]); const [loading, setLoading] = useState(false); const [editingItem, setEditingItem] = useState(null); const [formData, setFormData] = useState({ date: moment().format('YYYY-MM-DD'), title: '', content: '', type: 'plan', stocks: [], tags: [], status: 'active', }); const [stockInput, setStockInput] = useState(''); const [tagInput, setTagInput] = useState(''); // 加载数据 const loadData = useCallback(async () => { try { setLoading(true); const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001'); const response = await fetch(base + '/api/account/investment-plans', { credentials: 'include' }); if (response.ok) { const data = await response.json(); if (data.success) { const allItems = data.data || []; setPlans(allItems.filter(item => item.type === 'plan')); setReviews(allItems.filter(item => item.type === 'review')); logger.debug('InvestmentPlansAndReviews', '数据加载成功', { plansCount: allItems.filter(item => item.type === 'plan').length, reviewsCount: allItems.filter(item => item.type === 'review').length }); } } } catch (error) { logger.error('InvestmentPlansAndReviews', 'loadData', error); // ❌ 移除数据加载失败 toast(非关键操作) } finally { setLoading(false); } }, []); // ✅ 移除 toast 依赖 useEffect(() => { loadData(); }, [loadData]); // 打开编辑/新建模态框 const handleOpenModal = (item = null, itemType = 'plan') => { if (item) { setEditingItem(item); setFormData({ ...item, date: moment(item.date).format('YYYY-MM-DD'), }); } else { setEditingItem(null); setFormData({ date: moment().format('YYYY-MM-DD'), title: '', content: '', type: itemType, stocks: [], tags: [], status: 'active', }); } onOpen(); }; // 保存数据 const handleSave = async () => { try { const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001'); const url = editingItem ? base + `/api/account/investment-plans/${editingItem.id}` : base + '/api/account/investment-plans'; const method = editingItem ? 'PUT' : 'POST'; const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(formData), }); if (response.ok) { logger.info('InvestmentPlansAndReviews', `${editingItem ? '更新' : '创建'}成功`, { itemId: editingItem?.id, title: formData.title, type: formData.type }); toast({ title: editingItem ? '更新成功' : '创建成功', status: 'success', duration: 2000, }); onClose(); loadData(); } else { throw new Error('保存失败'); } } catch (error) { logger.error('InvestmentPlansAndReviews', 'handleSave', error, { itemId: editingItem?.id, title: formData?.title }); toast({ title: '保存失败', description: '无法保存数据', status: 'error', duration: 3000, }); } }; // 删除数据 const handleDelete = async (id) => { if (!window.confirm('确定要删除吗?')) return; try { const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001'); const response = await fetch(base + `/api/account/investment-plans/${id}`, { method: 'DELETE', credentials: 'include', }); if (response.ok) { logger.info('InvestmentPlansAndReviews', '删除成功', { itemId: id }); toast({ title: '删除成功', status: 'success', duration: 2000, }); loadData(); } } catch (error) { logger.error('InvestmentPlansAndReviews', 'handleDelete', error, { itemId: id }); toast({ title: '删除失败', status: 'error', duration: 3000, }); } }; // 添加股票 const handleAddStock = () => { if (stockInput.trim() && !formData.stocks.includes(stockInput.trim())) { setFormData({ ...formData, stocks: [...formData.stocks, stockInput.trim()], }); setStockInput(''); } }; // 添加标签 const handleAddTag = () => { if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) { setFormData({ ...formData, tags: [...formData.tags, tagInput.trim()], }); setTagInput(''); } }; // 获取状态图标和颜色 const getStatusInfo = (status) => { switch (status) { case 'completed': return { icon: FiCheckCircle, color: 'green' }; case 'cancelled': return { icon: FiXCircle, color: 'red' }; default: return { icon: FiAlertCircle, color: 'blue' }; } }; // 渲染单个卡片 const renderCard = (item) => { const statusInfo = getStatusInfo(item.status); return ( {item.title} {moment(item.date).format('YYYY年MM月DD日')} } > {item.status === 'active' ? '进行中' : item.status === 'completed' ? '已完成' : '已取消'} } size="sm" variant="ghost" onClick={() => handleOpenModal(item)} /> } size="sm" variant="ghost" colorScheme="red" onClick={() => handleDelete(item.id)} /> {item.content && ( {item.content} )} {item.stocks && item.stocks.length > 0 && ( <> {item.stocks.map((stock, idx) => ( {stock} ))} )} {item.tags && item.tags.length > 0 && ( <> {item.tags.map((tag, idx) => ( {tag} ))} )} ); }; return ( 我的计划 ({plans.length}) 我的复盘 ({reviews.length}) {/* 计划面板 */} {loading ? (
) : plans.length === 0 ? (
暂无投资计划
) : ( {plans.map(renderCard)} )}
{/* 复盘面板 */} {loading ? (
) : reviews.length === 0 ? (
暂无复盘记录
) : ( {reviews.map(renderCard)} )}
{/* 编辑/新建模态框 - 条件渲染 */} {isOpen && ( {editingItem ? '编辑' : '新建'} {formData.type === 'plan' ? '投资计划' : '复盘记录'} 日期 setFormData({ ...formData, date: e.target.value })} /> 标题 setFormData({ ...formData, title: e.target.value })} placeholder={formData.type === 'plan' ? '例如:布局新能源板块' : '例如:本周交易复盘'} /> 内容