import { useState, useEffect, useRef } from 'react' import { EquityChart } from './EquityChart' import { AdvancedChart } from './AdvancedChart' import { useLanguage } from '../contexts/LanguageContext' import { t } from '../i18n/translations' import { BarChart3, CandlestickChart, ChevronDown, Search } from 'lucide-react' import { motion, AnimatePresence } from 'framer-motion' interface ChartTabsProps { traderId: string selectedSymbol?: string // 从外部选择的币种 updateKey?: number // 强制更新的 key exchangeId?: string // 交易所ID } type ChartTab = 'equity' | 'kline' type Interval = '1m' | '5m' | '15m' | '30m' | '1h' | '4h' | '1d' type MarketType = 'hyperliquid' | 'crypto' | 'stocks' | 'forex' | 'metals' interface SymbolInfo { symbol: string name: string category: string } // 市场类型配置 const MARKET_CONFIG = { hyperliquid: { exchange: 'hyperliquid', defaultSymbol: 'BTC', icon: '🔷', label: { zh: 'HL', en: 'HL' }, color: 'cyan', hasDropdown: true }, crypto: { exchange: 'binance', defaultSymbol: 'BTCUSDT', icon: '₿', label: { zh: '加密', en: 'Crypto' }, color: 'yellow', hasDropdown: false }, stocks: { exchange: 'alpaca', defaultSymbol: 'AAPL', icon: '📈', label: { zh: '美股', en: 'Stocks' }, color: 'green', hasDropdown: false }, forex: { exchange: 'forex', defaultSymbol: 'EUR/USD', icon: '💱', label: { zh: '外汇', en: 'Forex' }, color: 'blue', hasDropdown: false }, metals: { exchange: 'metals', defaultSymbol: 'XAU/USD', icon: '🥇', label: { zh: '金属', en: 'Metals' }, color: 'amber', hasDropdown: false }, } const INTERVALS: { value: Interval; label: string }[] = [ { value: '1m', label: '1m' }, { value: '5m', label: '5m' }, { value: '15m', label: '15m' }, { value: '30m', label: '30m' }, { value: '1h', label: '1h' }, { value: '4h', label: '4h' }, { value: '1d', label: '1d' }, ] // 根据交易所ID推断市场类型 function getMarketTypeFromExchange(exchangeId: string | undefined): MarketType { if (!exchangeId) return 'hyperliquid' const lower = exchangeId.toLowerCase() if (lower.includes('hyperliquid')) return 'hyperliquid' // 其他交易所默认使用 crypto 类型 return 'crypto' } export function ChartTabs({ traderId, selectedSymbol, updateKey, exchangeId }: ChartTabsProps) { const { language } = useLanguage() const [activeTab, setActiveTab] = useState('equity') const [chartSymbol, setChartSymbol] = useState('BTC') const [interval, setInterval] = useState('5m') const [symbolInput, setSymbolInput] = useState('') const [marketType, setMarketType] = useState(() => getMarketTypeFromExchange(exchangeId)) const [availableSymbols, setAvailableSymbols] = useState([]) const [showDropdown, setShowDropdown] = useState(false) const [searchFilter, setSearchFilter] = useState('') const dropdownRef = useRef(null) // 当交易所ID变化时,自动切换市场类型 useEffect(() => { const newMarketType = getMarketTypeFromExchange(exchangeId) setMarketType(newMarketType) }, [exchangeId]) // 根据市场类型确定交易所 const marketConfig = MARKET_CONFIG[marketType] // 优先使用传入的 exchangeId(非 hyperliquid 时) const currentExchange = marketType === 'hyperliquid' ? 'hyperliquid' : (exchangeId || marketConfig.exchange) // 获取可用币种列表 useEffect(() => { if (marketConfig.hasDropdown) { fetch(`/api/symbols?exchange=${marketConfig.exchange}`) .then(res => res.json()) .then(data => { if (data.symbols) { // 按类别排序: crypto > stock > forex > commodity > index const categoryOrder: Record = { crypto: 0, stock: 1, forex: 2, commodity: 3, index: 4 } const sorted = [...data.symbols].sort((a: SymbolInfo, b: SymbolInfo) => { const orderA = categoryOrder[a.category] ?? 5 const orderB = categoryOrder[b.category] ?? 5 if (orderA !== orderB) return orderA - orderB return a.symbol.localeCompare(b.symbol) }) setAvailableSymbols(sorted) } }) .catch(err => console.error('Failed to fetch symbols:', err)) } }, [marketType, marketConfig.exchange, marketConfig.hasDropdown]) // 点击外部关闭下拉 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setShowDropdown(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, []) // 切换市场类型时更新默认符号 const handleMarketTypeChange = (type: MarketType) => { setMarketType(type) setChartSymbol(MARKET_CONFIG[type].defaultSymbol) setShowDropdown(false) } // 过滤后的币种列表 const filteredSymbols = availableSymbols.filter(s => s.symbol.toLowerCase().includes(searchFilter.toLowerCase()) ) // 当从外部选择币种时,自动切换到K线图 useEffect(() => { if (selectedSymbol) { console.log('[ChartTabs] 收到币种选择:', selectedSymbol, 'updateKey:', updateKey) setChartSymbol(selectedSymbol) setActiveTab('kline') } }, [selectedSymbol, updateKey]) // 处理手动输入符号 const handleSymbolSubmit = (e: React.FormEvent) => { e.preventDefault() if (symbolInput.trim()) { let symbol = symbolInput.trim().toUpperCase() // 加密货币自动加 USDT 后缀 if (marketType === 'crypto' && !symbol.endsWith('USDT')) { symbol = symbol + 'USDT' } setChartSymbol(symbol) setSymbolInput('') } } console.log('[ChartTabs] rendering, activeTab:', activeTab) return (
{/* Premium Professional Toolbar Mobile: Single row, horizontal scroll with gradient mask Desktop: Standard flex-wrap/nowrap */}
{/* Left: Tab Switcher */}
{/* Market Type Pills - Only when kline active, HIDDEN on mobile to save space */} {activeTab === 'kline' && (
{(Object.keys(MARKET_CONFIG) as MarketType[]).map((type) => { const config = MARKET_CONFIG[type] const isActive = marketType === type return ( ) })}
)}
{/* Right: Symbol + Interval */} {activeTab === 'kline' && (
{/* Symbol Dropdown */}
{marketConfig.hasDropdown ? ( <> {showDropdown && (
setSearchFilter(e.target.value)} placeholder="Search symbol..." className="flex-1 bg-transparent text-[11px] text-white placeholder-gray-600 focus:outline-none font-mono" autoFocus />
{['crypto', 'stock', 'forex', 'commodity', 'index'].map(category => { const categorySymbols = filteredSymbols.filter(s => s.category === category) if (categorySymbols.length === 0) return null const labels: Record = { crypto: 'Crypto', stock: 'Stocks', forex: 'Forex', commodity: 'Commodities', index: 'Index' } return (
{labels[category]}
{categorySymbols.map(s => ( ))}
) })}
)} ) : ( {chartSymbol} )}
{/* Interval Selector - Allow scrolling if needed */}
{INTERVALS.map((int) => ( ))}
{/* Quick Input - Hidden on mobile, dropdown search is enough */}
setSymbolInput(e.target.value)} placeholder="Sym" className="w-16 px-2 py-1 bg-black/40 border border-white/10 rounded-l text-[10px] text-white placeholder-gray-600 focus:outline-none focus:border-nofx-gold/50 font-mono transition-colors" />
)}
{/* Tab Content - Chart autosizes to this container */}
{activeTab === 'equity' ? ( ) : ( )}
) }