import React, { useState, useRef, useEffect, useCallback } from 'react' import { Search, TrendingUp, Loader2, LogOut, Star, BarChart2, User, X, Gem, ScanLine, Droplets } from 'lucide-react' import { searchStocks, SearchResult } from '../api/stockApi' export type Market = 'tw' | 'us' | 'fund' const MARKET_TABS: { key: Market; label: string; flag: string }[] = [ { key: 'tw', label: '台肑', flag: 'πŸ‡ΉπŸ‡Ό' }, { key: 'us', label: 'ηΎŽθ‚‘', flag: 'πŸ‡ΊπŸ‡Έ' }, { key: 'fund', label: 'εŸΊι‡‘', flag: 'πŸ“Š' }, ] const MARKET_PLACEHOLDER: Record = { tw: 'ε°θ‚‘δ»£θ™Ÿ (e.g. 7856)', us: 'ηΎŽθ‚‘δ»£θ™Ÿ (e.g. AAPL)', fund: 'εŸΊι‡‘δ»£θ™Ÿ (e.g. PIM91)', } interface HeaderProps { input: string setInput: (val: string) => void months: number setMonths: (val: number) => void loading: boolean onSubmit: (e: React.FormEvent) => void onLogout: () => void currentUser: string userPhotoURL?: string view: 'main' | 'favorites' | 'metals' | 'oil' | 'recommend' onViewChange: (v: 'main' | 'favorites' | 'metals' | 'oil' | 'recommend') => void favoritesCount: number market: Market onMarketChange: (m: Market) => void onSelect: (stockNo: string) => void } export const Header: React.FC = ({ input, setInput, months, setMonths, loading, onSubmit, onLogout, currentUser, userPhotoURL, view, onViewChange, favoritesCount, market, onMarketChange, onSelect, }) => { const [suggestions, setSuggestions] = useState([]) const [showDrop, setShowDrop] = useState(false) const [searching, setSearching] = useState(false) const debounceRef = useRef | null>(null) const wrapperRef = useRef(null) // Close dropdown when clicking outside useEffect(() => { const handler = (e: MouseEvent) => { if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { setShowDrop(false) } } document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, []) const fetchSuggestions = useCallback(async (q: string, mkt: Market) => { if (!q.trim()) { setSuggestions([]); setShowDrop(false); return } // For TW: need at least 1 char; for US/fund: 1 char if (q.length < 1) return setSearching(true) try { const results = await searchStocks(q, mkt) setSuggestions(results) setShowDrop(results.length > 0) } catch { setSuggestions([]) setShowDrop(false) } finally { setSearching(false) } }, []) const handleInputChange = (val: string) => { setInput(val) if (debounceRef.current) clearTimeout(debounceRef.current) debounceRef.current = setTimeout(() => fetchSuggestions(val, market), 350) } const handleSelect = (stockNo: string) => { setInput(stockNo) setSuggestions([]) setShowDrop(false) onSelect(stockNo) } const handleMarketChange = (m: Market) => { onMarketChange(m) setSuggestions([]) setShowDrop(false) setInput('') } const handleFormSubmit = (e: React.FormEvent) => { setShowDrop(false) onSubmit(e) } const exchangeColor: Record = { TWSE: 'text-blue-400', TPEX: 'text-purple-400', εŸΊι‡‘: 'text-yellow-400', MoneyDJ: 'text-yellow-400', NYSE: 'text-green-400', NASDAQ: 'text-green-400', } return (
{/* Brand */}

ML θ‚‘η₯¨ι ζΈ¬

{/* View nav (εˆ†ζž / ζœ€ζ„›) */} {/* Market tabs + search (only on main view) */} {view === 'main' && (
{/* Market tabs */}
{MARKET_TABS.map(tab => ( ))}
{/* Search with autocomplete */}
handleInputChange(e.target.value)} onFocus={() => suggestions.length > 0 && setShowDrop(true)} placeholder={MARKET_PLACEHOLDER[market]} className="bg-slate-800 border border-slate-600 rounded-lg pl-8 pr-7 py-2 text-sm w-full sm:w-52 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 text-white placeholder-slate-500" /> {input && ( )} {/* Autocomplete dropdown */} {showDrop && (
{searching && (
ζœε°‹δΈ­...
)} {suggestions.map((s) => ( ))}
)}
)} {/* User avatar + logout */}
{userPhotoURL ? ( {currentUser} ) : (
)} {currentUser}
) }