/** * EventFormModal - 通用事件表单弹窗组件 * 用于新建/编辑投资计划、复盘、日历事件等 * * 通过 props 配置差异化行为: * - 字段显示控制(日期选择器、类型、状态、重要度、标签等) * - API 端点配置(investment-plans 或 calendar/events) * - 主题颜色和标签文案 */ import React, { useState, useEffect } from 'react'; import { Button, Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton, VStack, HStack, Icon, Input, InputGroup, InputLeftElement, FormControl, FormLabel, Textarea, Select, Tag, TagLabel, TagLeftIcon, TagCloseButton, } from '@chakra-ui/react'; import { FiSave, FiCalendar, FiTrendingUp, FiHash, } from 'react-icons/fi'; import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; import { usePlanningData } from './PlanningContext'; import type { InvestmentEvent, EventType, EventStatus } from '@/types'; import { logger } from '@/utils/logger'; import { getApiBase } from '@/utils/apiConfig'; dayjs.locale('zh-cn'); /** * 表单数据接口 */ interface FormData { date: string; title: string; content: string; type: EventType; stocks: string[]; tags: string[]; status: EventStatus; importance: number; } /** * EventFormModal Props */ export interface EventFormModalProps { /** 弹窗是否打开 */ isOpen: boolean; /** 关闭弹窗回调 */ onClose: () => void; /** 模式:新建或编辑 */ mode: 'create' | 'edit'; /** 事件类型(新建时使用) */ eventType?: EventType; /** 初始日期(新建时使用,如从日历点击) */ initialDate?: string; /** 编辑时的原始事件数据 */ editingEvent?: InvestmentEvent | null; /** 保存成功回调 */ onSuccess: () => void; /** 主题颜色 */ colorScheme?: string; /** 显示标签(如 "计划"、"复盘"、"事件") */ label?: string; /** 是否显示日期选择器 */ showDatePicker?: boolean; /** 是否显示类型选择 */ showTypeSelect?: boolean; /** 是否显示状态选择 */ showStatusSelect?: boolean; /** 是否显示重要度选择 */ showImportance?: boolean; /** 是否显示标签输入 */ showTags?: boolean; /** 股票输入方式:'tag' 为标签形式,'text' 为逗号分隔文本 */ stockInputMode?: 'tag' | 'text'; /** API 端点 */ apiEndpoint?: 'investment-plans' | 'calendar/events'; } /** * EventFormModal 组件 */ export const EventFormModal: React.FC = ({ isOpen, onClose, mode, eventType = 'plan', initialDate, editingEvent, onSuccess, colorScheme = 'purple', label = '事件', showDatePicker = true, showTypeSelect = false, showStatusSelect = true, showImportance = false, showTags = true, stockInputMode = 'tag', apiEndpoint = 'investment-plans', }) => { const { toast, secondaryText } = usePlanningData(); // 表单数据 const [formData, setFormData] = useState({ date: initialDate || dayjs().format('YYYY-MM-DD'), title: '', content: '', type: eventType, stocks: [], tags: [], status: 'active', importance: 3, }); // 股票和标签输入框 const [stockInput, setStockInput] = useState(''); const [tagInput, setTagInput] = useState(''); // 保存中状态 const [saving, setSaving] = useState(false); // 初始化表单数据 useEffect(() => { if (mode === 'edit' && editingEvent) { setFormData({ date: dayjs(editingEvent.event_date || editingEvent.date).format('YYYY-MM-DD'), title: editingEvent.title, content: editingEvent.description || editingEvent.content || '', type: editingEvent.type || eventType, stocks: editingEvent.stocks || [], tags: editingEvent.tags || [], status: editingEvent.status || 'active', importance: editingEvent.importance || 3, }); // 如果是文本模式,将股票数组转为逗号分隔 if (stockInputMode === 'text' && editingEvent.stocks) { setStockInput(editingEvent.stocks.join(',')); } } else { // 新建模式,重置表单 setFormData({ date: initialDate || dayjs().format('YYYY-MM-DD'), title: '', content: '', type: eventType, stocks: [], tags: [], status: 'active', importance: 3, }); setStockInput(''); setTagInput(''); } }, [mode, editingEvent, eventType, initialDate, stockInputMode]); // 保存数据 const handleSave = async (): Promise => { try { setSaving(true); const base = getApiBase(); // 构建请求数据 let requestData: Record = { ...formData }; // 如果是文本模式,解析股票输入 if (stockInputMode === 'text' && stockInput) { requestData.stocks = stockInput.split(',').map(s => s.trim()).filter(s => s); } // 根据 API 端点调整字段名 if (apiEndpoint === 'calendar/events') { requestData = { title: formData.title, description: formData.content, type: formData.type, importance: formData.importance, stocks: requestData.stocks, event_date: formData.date, }; } const url = mode === 'edit' && editingEvent ? `${base}/api/account/${apiEndpoint}/${editingEvent.id}` : `${base}/api/account/${apiEndpoint}`; const method = mode === 'edit' ? 'PUT' : 'POST'; const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(requestData), }); if (response.ok) { logger.info('EventFormModal', `${mode === 'edit' ? '更新' : '创建'}${label}成功`, { itemId: editingEvent?.id, title: formData.title, }); toast({ title: mode === 'edit' ? '更新成功' : '创建成功', status: 'success', duration: 2000, }); onClose(); onSuccess(); } else { throw new Error('保存失败'); } } catch (error) { logger.error('EventFormModal', 'handleSave', error, { itemId: editingEvent?.id, title: formData?.title }); toast({ title: '保存失败', description: '无法保存数据', status: 'error', duration: 3000, }); } finally { setSaving(false); } }; // 添加股票(Tag 模式) const handleAddStock = (): void => { if (stockInput.trim() && !formData.stocks.includes(stockInput.trim())) { setFormData({ ...formData, stocks: [...formData.stocks, stockInput.trim()], }); setStockInput(''); } }; // 移除股票(Tag 模式) const handleRemoveStock = (index: number): void => { setFormData({ ...formData, stocks: formData.stocks.filter((_, i) => i !== index), }); }; // 添加标签 const handleAddTag = (): void => { if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) { setFormData({ ...formData, tags: [...formData.tags, tagInput.trim()], }); setTagInput(''); } }; // 移除标签 const handleRemoveTag = (index: number): void => { setFormData({ ...formData, tags: formData.tags.filter((_, i) => i !== index), }); }; // 获取标题 placeholder const getTitlePlaceholder = (): string => { switch (formData.type) { case 'plan': return '例如:布局新能源板块'; case 'review': return '例如:本周操作复盘'; case 'reminder': return '例如:关注半导体板块'; case 'analysis': return '例如:行业分析任务'; default: return '请输入标题'; } }; // 获取内容 placeholder const getContentPlaceholder = (): string => { switch (formData.type) { case 'plan': return '详细描述您的投资计划...'; case 'review': return '详细记录您的投资复盘...'; default: return '详细描述...'; } }; return ( {mode === 'edit' ? '编辑' : '新建'}投资{label} {/* 日期选择器 */} {showDatePicker && ( 日期 setFormData({ ...formData, date: e.target.value })} /> )} {/* 标题 */} 标题 setFormData({ ...formData, title: e.target.value })} placeholder={getTitlePlaceholder()} /> {/* 内容/描述 */} 内容