import { useState, useRef, useEffect } from 'react'; import { Plus, Trash, Activity } from 'lucide-react'; import Sparkline from './Sparkline'; import Logo from './Logo'; interface WatchlistSidebarProps { watchlist: string[]; watchlistQuotes: Record; activeTicker: string; onSelectTicker: (ticker: string) => void; onAddTicker: (ticker: string) => Promise; onRemoveTicker: (ticker: string) => Promise; } const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'; // Typo mapping to automatically resolve company names to correct tickers const TYPO_MAP: Record = { "GOOGLE": "GOOGL", "ALPHABET": "GOOGL", "FACEBOOK": "META", "META PLATFORMS": "META", "APPLE": "AAPL", "AMAZON": "AMZN", "MICROSOFT": "MSFT", "NETFLIX": "NFLX", "TESLA": "TSLA", "RELIANCE": "RELIANCE.NS", "TCS": "TCS.NS", "TATA CONSULTANCY": "TCS.NS", "INFOSYS": "INFY", "NVIDIA": "NVDA", "AMD": "AMD", "INTEL": "INTC", "ADOBE": "ADBE", "SALESFORCE": "CRM", "COCA COLA": "KO", "PEPSI": "PEP", "ADANI": "ADANIENT.NS", "ADANI ENTERPRISES": "ADANIENT.NS", "ADANIENT": "ADANIENT.NS", "ADANIPORTS": "ADANIPORTS.NS", "ADANIPOWER": "ADANIPOWER.NS" }; // Popular default suggestions to show when search is empty const DEFAULT_SUGGESTIONS = [ { symbol: "AAPL", name: "Apple Inc." }, { symbol: "TSLA", name: "Tesla Inc." }, { symbol: "META", name: "Meta Platforms" }, { symbol: "GOOGL", name: "Alphabet (Google)" }, { symbol: "MSFT", name: "Microsoft Corp." }, { symbol: "ADANIENT.NS", name: "Adani Enterprises" }, { symbol: "RELIANCE.NS", name: "Reliance Industries" } ]; export default function WatchlistSidebar({ watchlist, watchlistQuotes, activeTicker, onSelectTicker, onAddTicker, onRemoveTicker, }: WatchlistSidebarProps) { const [newTicker, setNewTicker] = useState(''); const [suggestions, setSuggestions] = useState<{ symbol: string; name: string }[]>([]); const [showDropdown, setShowDropdown] = useState(false); const [loading, setLoading] = useState(false); const [tickerToDelete, setTickerToDelete] = useState(null); const dropdownRef = useRef(null); // Close dropdown on click outside useEffect(() => { function handleClickOutside(event: MouseEvent) { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setShowDropdown(false); } } document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // Debounced dynamic search from Yahoo Finance proxy API useEffect(() => { const query = newTicker.trim(); if (query.length < 2) { setSuggestions([]); return; } const delayDebounce = setTimeout(async () => { setLoading(true); try { const response = await fetch(`${API_URL}/api/v1/stocks/search?q=${encodeURIComponent(query)}`); if (response.ok) { const data = await response.json(); if (Array.isArray(data)) { setSuggestions(data); } else { setSuggestions([]); } } else { setSuggestions([]); } } catch (err) { console.error('Error fetching stock suggestions:', err); } finally { setLoading(false); } }, 300); return () => clearTimeout(delayDebounce); }, [newTicker]); // Determine what suggestions to display const displaySuggestions = (newTicker.trim().length >= 2 && Array.isArray(suggestions)) ? suggestions : DEFAULT_SUGGESTIONS; const handleAdd = async (ticker: string) => { let cleanTicker = ticker.trim().toUpperCase(); if (!cleanTicker) return; // Apply auto-correct map if matched if (TYPO_MAP[cleanTicker]) { cleanTicker = TYPO_MAP[cleanTicker]; } try { await onAddTicker(cleanTicker); setNewTicker(''); setShowDropdown(false); } catch (err) { console.error('Error adding ticker:', err); } }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); handleAdd(newTicker); }; const handleRemove = async (ticker: string, e: React.MouseEvent) => { e.stopPropagation(); // Avoid selecting as active ticker setTickerToDelete(ticker); }; return (
Watchlist
{ setNewTicker(e.target.value); setShowDropdown(true); }} onFocus={() => setShowDropdown(true)} />
{showDropdown && displaySuggestions.length > 0 && (
{loading && (
Searching global markets...
)} {displaySuggestions.map((item) => (
{ e.preventDefault(); // Keep input focused & prevent unmount before state updates setNewTicker(item.symbol); setShowDropdown(false); }} > {item.symbol} {item.name}
))}
)}
{/* Subtle background Logo watermark */}
{watchlist.map((ticker) => { const quote = watchlistQuotes ? watchlistQuotes[ticker] : null; const isPositive = quote ? quote.changePercent >= 0 : true; return (
onSelectTicker(ticker)} >
{ticker} {quote && ( ${quote.price.toFixed(2)} ({isPositive ? '+' : ''}{quote.changePercent.toFixed(2)}%) )}
{quote && (
)}
); })}
{tickerToDelete && (

Confirm Deletion

Are you sure you want to remove {tickerToDelete} from your watchlist?

)}
); }