@@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback, useMemo, useRef } from "react"
22import { Card , CardContent , CardHeader , CardTitle } from "@/components/ui/card" ;
33import { Tabs , TabsContent , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
44import { LineChart , Line , XAxis , YAxis , CartesianGrid , Tooltip , ResponsiveContainer , ReferenceLine } from "recharts" ;
5- import { X , Loader2 , AlertCircle , Settings } from "lucide-react" ;
5+ import { X , Loader2 , AlertCircle , Settings , Eye } from "lucide-react" ;
66import { Button } from "@/components/ui/button" ;
77import { Badge } from "@/components/ui/badge" ;
88import { Alert , AlertDescription } from "@/components/ui/alert" ;
@@ -11,9 +11,11 @@ import { useAuth, isSessionValid, hasAlpacaCredentials } from "@/lib/auth";
1111import { useToast } from "@/hooks/use-toast" ;
1212import { fetchPortfolioDataForPeriod , fetchStockDataForPeriod , type PortfolioData , type StockData , type PortfolioDataPoint } from "@/lib/portfolio-data" ;
1313import { useNavigate } from "react-router-dom" ;
14+ import StockTickerAutocomplete from "@/components/StockTickerAutocomplete" ;
1415
1516interface PerformanceChartProps {
1617 selectedStock ?: string ;
18+ selectedStockDescription ?: string ;
1719 onClearSelection ?: ( ) => void ;
1820}
1921
@@ -57,7 +59,7 @@ const formatValue = (value: number | undefined, isMobile: boolean = false): stri
5759
5860// Remove the old hardcoded function - we'll create a dynamic one in the component
5961
60- const PerformanceChart = React . memo ( ( { selectedStock, onClearSelection } : PerformanceChartProps ) => {
62+ const PerformanceChart = React . memo ( ( { selectedStock : propSelectedStock , selectedStockDescription , onClearSelection } : PerformanceChartProps ) => {
6163 const navigate = useNavigate ( ) ;
6264 const [ selectedPeriod , setSelectedPeriod ] = useState < TimePeriod > ( "1D" ) ;
6365 const [ loading , setLoading ] = useState ( false ) ;
@@ -71,12 +73,38 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
7173 const hasConfiguredAlpaca = useMemo ( ( ) => hasAlpacaCredentials ( apiSettings ) , [ apiSettings ] ) ;
7274 const [ hasAlpacaConfig , setHasAlpacaConfig ] = useState ( hasConfiguredAlpaca ) ;
7375 const { toast } = useToast ( ) ;
76+
77+ // Internal state for selected stock (can be from prop or from search)
78+ const [ internalSelectedStock , setInternalSelectedStock ] = useState < string | undefined > ( propSelectedStock ) ;
79+ const [ tickerInput , setTickerInput ] = useState < string > ( "" ) ;
80+ const [ stockDescription , setStockDescription ] = useState < string > ( selectedStockDescription || "" ) ;
81+
82+ // Use prop or internal state
83+ const selectedStock = propSelectedStock || internalSelectedStock ;
7484
7585 // Track if we've already fetched for current apiSettings and selectedStock
7686 const fetchedRef = useRef < string > ( '' ) ;
7787 const lastFetchTimeRef = useRef < number > ( 0 ) ;
7888 const metricsLoaded = useRef ( false ) ;
7989
90+ // Handle viewing a stock from the search bar
91+ const handleViewStock = useCallback ( ( ) => {
92+ if ( tickerInput . trim ( ) ) {
93+ setInternalSelectedStock ( tickerInput . trim ( ) . toUpperCase ( ) ) ;
94+ setTickerInput ( "" ) ;
95+ }
96+ } , [ tickerInput ] ) ;
97+
98+ // Handle clearing the selection
99+ const handleClearSelection = useCallback ( ( ) => {
100+ if ( onClearSelection ) {
101+ onClearSelection ( ) ;
102+ }
103+ setInternalSelectedStock ( undefined ) ;
104+ setTickerInput ( "" ) ;
105+ setStockDescription ( "" ) ;
106+ } , [ onClearSelection ] ) ;
107+
80108 const fetchData = useCallback ( async ( period : string ) => {
81109 // Debounce fetches - don't fetch if we just fetched less than 2 seconds ago
82110 const now = Date . now ( ) ;
@@ -110,6 +138,22 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
110138 }
111139 } ) ) ;
112140 }
141+
142+ // Fetch stock description if not already fetched
143+ if ( ! stockDescription && hasConfiguredAlpaca ) {
144+ try {
145+ const assetInfo = await alpacaAPI . getAsset ( selectedStock ) . catch ( err => {
146+ console . warn ( `Could not fetch asset info for ${ selectedStock } :` , err ) ;
147+ return null ;
148+ } ) ;
149+
150+ if ( assetInfo ?. name ) {
151+ setStockDescription ( assetInfo . name ) ;
152+ }
153+ } catch ( err ) {
154+ console . warn ( `Could not fetch description for ${ selectedStock } :` , err ) ;
155+ }
156+ }
113157 } else {
114158 // Fetch portfolio data for this period if not cached
115159 if ( ! portfolioData [ period ] ) {
@@ -250,6 +294,15 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
250294 setHasAlpacaConfig ( hasConfiguredAlpaca ) ;
251295 } , [ hasConfiguredAlpaca ] ) ;
252296
297+ // Sync internal state with prop changes
298+ useEffect ( ( ) => {
299+ if ( propSelectedStock !== internalSelectedStock ) {
300+ setInternalSelectedStock ( propSelectedStock ) ;
301+ // Update description if provided, otherwise clear it
302+ setStockDescription ( selectedStockDescription || "" ) ;
303+ }
304+ } , [ propSelectedStock , selectedStockDescription ] ) ;
305+
253306 // Get real stock metrics from positions
254307 const getStockMetrics = useCallback ( ( symbol : string ) => {
255308 const position = positions . find ( ( p : any ) => p . symbol === symbol ) ;
@@ -434,58 +487,89 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
434487 < Card >
435488 < CardHeader className = "pb-4" >
436489 < div className = "space-y-2" >
437- < div className = "flex items-center justify-between" >
438- < div className = "flex items-center gap-2" >
439- < CardTitle > Performance</ CardTitle >
440- { selectedStock && (
441- < div className = "flex items-center gap-2" >
442- < Badge variant = "default" > { selectedStock } </ Badge >
443- < Button
444- variant = "ghost"
445- size = "icon"
446- className = "h-6 w-6"
447- onClick = { onClearSelection }
448- >
449- < X className = "h-3 w-3" />
450- </ Button >
451- </ div >
452- ) }
453- </ div >
454- </ div >
455- < div className = "text-xs text-muted-foreground" >
456- Data may be incomplete or delayed.{ ' ' }
457- < a
458- href = { selectedStock
459- ? `https://app.alpaca.markets/trade/${ selectedStock } `
460- : 'https://app.alpaca.markets/dashboard/overview'
461- }
462- target = "_blank"
463- rel = "noopener noreferrer"
464- className = "text-primary hover:underline"
465- >
466- View on Alpaca →
467- </ a >
468- </ div >
469- </ div >
470- </ CardHeader >
471- < CardContent >
472- { ! hasAlpacaConfig && (
473- < Alert className = "mb-4" >
474- < AlertCircle className = "h-4 w-4" />
475- < AlertDescription className = "flex items-center justify-between" >
476- < span > Connect your Alpaca account to view live performance data</ span >
490+ { ! hasAlpacaConfig ? (
491+ < div className = "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 rounded-lg border p-4 bg-background" >
492+ < div className = "flex items-center gap-2" >
493+ < AlertCircle className = "h-4 w-4 flex-shrink-0" />
494+ < span className = "text-sm" > Connect your Alpaca account to view live performance data</ span >
495+ </ div >
477496 < Button
478497 variant = "outline"
479498 size = "sm"
480- onClick = { ( ) => navigate ( '/settings?tab=trading ' ) }
481- className = "ml-4 "
499+ onClick = { ( ) => navigate ( '/settings' ) }
500+ className = "w-full sm:w-auto "
482501 >
483502 < Settings className = "h-4 w-4 mr-2" />
484503 Configure API
485504 </ Button >
486- </ AlertDescription >
487- </ Alert >
488- ) }
505+ </ div >
506+ ) : (
507+ < >
508+ { selectedStock ? (
509+ < Badge variant = "default" className = "flex items-center justify-between w-full pr-1.5 py-2 px-4" >
510+ < div className = "flex-1" /> { /* Spacer */ }
511+ < span className = "text-base" >
512+ < span className = "font-bold" > { selectedStock } </ span >
513+ { stockDescription && (
514+ < span className = "font-medium text-muted-foreground ml-2" >
515+ { stockDescription }
516+ </ span >
517+ ) }
518+ </ span >
519+ < div className = "flex-1 flex justify-end" > { /* Right-aligned container */ }
520+ < button
521+ className = "ml-4 rounded-full hover:bg-primary/30 p-0.5 transition-colors"
522+ onClick = { handleClearSelection }
523+ type = "button"
524+ aria-label = "Clear stock selection"
525+ >
526+ < X className = "h-4 w-4" />
527+ </ button >
528+ </ div >
529+ </ Badge >
530+ ) : (
531+ < div className = "flex items-center gap-2 w-full" >
532+ < StockTickerAutocomplete
533+ value = { tickerInput }
534+ onChange = { setTickerInput }
535+ onEnterPress = { handleViewStock }
536+ onSelect = { ( suggestion ) => {
537+ setInternalSelectedStock ( suggestion . symbol ) ;
538+ setStockDescription ( suggestion . description || "" ) ;
539+ setTickerInput ( "" ) ;
540+ } }
541+ placeholder = "Search stock..."
542+ className = "flex-1"
543+ />
544+ < Button
545+ size = "sm"
546+ onClick = { handleViewStock }
547+ disabled = { ! tickerInput . trim ( ) }
548+ >
549+ < Eye className = "h-4 w-4 mr-1" />
550+ View
551+ </ Button >
552+ </ div >
553+ ) }
554+ < div className = "text-xs text-muted-foreground" >
555+ Data may be incomplete or delayed.{ ' ' }
556+ < a
557+ href = { selectedStock
558+ ? `https://app.alpaca.markets/trade/${ selectedStock } `
559+ : 'https://app.alpaca.markets/dashboard/overview'
560+ }
561+ target = "_blank"
562+ rel = "noopener noreferrer"
563+ className = "text-primary hover:underline"
564+ >
565+ View on Alpaca →
566+ </ a >
567+ </ div >
568+ </ >
569+ ) }
570+ </ div >
571+ </ CardHeader >
572+ < CardContent >
489573 < Tabs value = { selectedPeriod } onValueChange = { ( value ) => setSelectedPeriod ( value as TimePeriod ) } className = "space-y-4" >
490574 < TabsList className = "grid w-full grid-cols-8 max-w-5xl mx-auto" >
491575 { periods . map ( ( period ) => (
0 commit comments