@@ -9,7 +9,7 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
99import { alpacaAPI } from "@/lib/alpaca" ;
1010import { useAuth , isSessionValid } from "@/lib/auth" ;
1111import { useToast } from "@/hooks/use-toast" ;
12- import { fetchPortfolioData , fetchStockData , type PortfolioData , type StockData } from "@/lib/portfolio-data" ;
12+ import { fetchPortfolioDataForPeriod , fetchStockDataForPeriod , type PortfolioData , type StockData , type PortfolioDataPoint } from "@/lib/portfolio-data" ;
1313import { useNavigate } from "react-router-dom" ;
1414
1515interface PerformanceChartProps {
@@ -41,8 +41,8 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
4141 const [ positionsLoading , setPositionsLoading ] = useState ( true ) ; // Track positions loading separately
4242 const [ error , setError ] = useState < string | null > ( null ) ;
4343 const [ metrics , setMetrics ] = useState < any > ( null ) ;
44- const [ portfolioData , setPortfolioData ] = useState < PortfolioData | null > ( null ) ;
45- const [ stockData , setStockData ] = useState < StockData > ( { } ) ;
44+ const [ portfolioData , setPortfolioData ] = useState < { [ period : string ] : PortfolioDataPoint [ ] } > ( { } ) ;
45+ const [ stockData , setStockData ] = useState < { [ ticker : string ] : { [ period : string ] : PortfolioDataPoint [ ] } } > ( { } ) ;
4646 const [ positions , setPositions ] = useState < any [ ] > ( [ ] ) ;
4747 const [ hasAlpacaConfig , setHasAlpacaConfig ] = useState ( true ) ; // Assume configured initially
4848 const { apiSettings, isAuthenticated } = useAuth ( ) ;
@@ -51,8 +51,9 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
5151 // Track if we've already fetched for current apiSettings and selectedStock
5252 const fetchedRef = useRef < string > ( '' ) ;
5353 const lastFetchTimeRef = useRef < number > ( 0 ) ;
54+ const metricsLoaded = useRef ( false ) ;
5455
55- const fetchData = useCallback ( async ( ) => {
56+ const fetchData = useCallback ( async ( period : string ) => {
5657 // Debounce fetches - don't fetch if we just fetched less than 2 seconds ago
5758 const now = Date . now ( ) ;
5859 if ( now - lastFetchTimeRef . current < 2000 ) {
@@ -64,13 +65,33 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
6465 setError ( null ) ;
6566
6667 try {
67- // First try to fetch portfolio data (doesn't require Alpaca API)
68- const portfolioHistoryData = await fetchPortfolioData ( ) ;
69- setPortfolioData ( portfolioHistoryData ) ;
68+ // Fetch data for the specific period
69+ if ( selectedStock ) {
70+ // Check if we already have this data
71+ if ( ! stockData [ selectedStock ] ?. [ period ] ) {
72+ const stockHistoryData = await fetchStockDataForPeriod ( selectedStock , period ) ;
73+ setStockData ( prev => ( {
74+ ...prev ,
75+ [ selectedStock ] : {
76+ ...prev [ selectedStock ] ,
77+ [ period ] : stockHistoryData
78+ }
79+ } ) ) ;
80+ }
81+ } else {
82+ // Fetch portfolio data for this period if not cached
83+ if ( ! portfolioData [ period ] ) {
84+ const portfolioHistoryData = await fetchPortfolioDataForPeriod ( period ) ;
85+ setPortfolioData ( prev => ( {
86+ ...prev ,
87+ [ period ] : portfolioHistoryData
88+ } ) ) ;
89+ }
90+ }
7091
71- // Try to fetch metrics (which now uses batch internally )
72- // The edge functions will handle checking if Alpaca is configured
73- const metricsData = await alpacaAPI . calculateMetrics ( ) . catch ( err => {
92+ // Try to fetch metrics only once (not period-specific )
93+ if ( ! metricsLoaded . current ) {
94+ const metricsData = await alpacaAPI . calculateMetrics ( ) . catch ( err => {
7495 console . warn ( "Failed to calculate metrics:" , err ) ;
7596 // Check if it's a configuration error
7697 if ( err . message ?. includes ( 'API settings not found' ) ||
@@ -107,56 +128,33 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
107128 return null ;
108129 } ) ;
109130
110- // Positions are now included in metrics data
111- const positionsData = metricsData ?. positions || [ ] ;
131+ // Positions are now included in metrics data
132+ const positionsData = metricsData ?. positions || [ ] ;
112133
113- setMetrics ( metricsData ) ;
114- setPositions ( positionsData || [ ] ) ;
115- setPositionsLoading ( false ) ; // Mark positions as loaded
134+ setMetrics ( metricsData ) ;
135+ setPositions ( positionsData || [ ] ) ;
136+ setPositionsLoading ( false ) ; // Mark positions as loaded
137+ metricsLoaded . current = true ;
138+ }
116139
117- // If a stock is selected, fetch its data (uses Alpaca API)
118- if ( selectedStock ) {
140+ // Fetch daily change for selected stock if needed
141+ if ( selectedStock && period === '1D' ) {
119142 try {
120- // console.log(`Fetching stock data for ${selectedStock}...`);
121- const stockHistoryData = await fetchStockData ( selectedStock ) ;
122- // Debug logging removed to prevent console spam
123- setStockData ( prev => {
124- const newState = { ...prev , [ selectedStock ] : stockHistoryData } ;
125- // console.log(`Updated stockData state for ${selectedStock}`);
126- return newState ;
143+ const batchData = await alpacaAPI . getBatchData ( [ selectedStock ] , {
144+ includeQuotes : true ,
145+ includeBars : true
127146 } ) ;
128147
129- // Fetch daily change using batch method
130- try {
131- const batchData = await alpacaAPI . getBatchData ( [ selectedStock ] , {
132- includeQuotes : true ,
133- includeBars : true
134- } ) ;
135-
136- const data = batchData [ selectedStock ] ;
137- if ( data ?. quote && data ?. previousBar ) {
138- const currentPrice = data . quote . ap || data . quote . bp || 0 ;
139- const previousClose = data . previousBar . c ;
140- const dayChange = currentPrice - previousClose ;
141- const dayChangePercent = previousClose > 0 ? ( dayChange / previousClose ) * 100 : 0 ;
142-
143- // console.log(`Daily change for ${selectedStock}: $${dayChange.toFixed(2)} (${dayChangePercent.toFixed(2)}%`);
144- }
145- } catch ( err ) {
146- console . warn ( `Could not fetch daily change for ${ selectedStock } :` , err ) ;
148+ const data = batchData [ selectedStock ] ;
149+ if ( data ?. quote && data ?. previousBar ) {
150+ const currentPrice = data . quote . ap || data . quote . bp || 0 ;
151+ const previousClose = data . previousBar . c ;
152+ const dayChange = currentPrice - previousClose ;
153+ // const dayChangePercent = previousClose > 0 ? (dayChange / previousClose) * 100 : 0;
154+ console . log ( `Daily change for ${ selectedStock } : $${ dayChange . toFixed ( 2 ) } ` ) ;
147155 }
148156 } catch ( err ) {
149- console . error ( `Error fetching data for ${ selectedStock } :` , err ) ;
150- // Check if it's an API configuration error for stock data
151- if ( err instanceof Error &&
152- ( err . message . includes ( 'API settings not found' ) ||
153- err . message . includes ( 'not configured' ) ||
154- err . message . includes ( 'Edge Function returned a non-2xx status code' ) ) ) {
155- setHasAlpacaConfig ( false ) ;
156- setError ( null ) ; // Don't show error for missing API config
157- } else {
158- setError ( `Failed to fetch data for ${ selectedStock } : ${ err instanceof Error ? err . message : 'Unknown error' } ` ) ;
159- }
157+ console . warn ( `Could not fetch daily change for ${ selectedStock } :` , err ) ;
160158 }
161159 }
162160 } catch ( err ) {
@@ -181,17 +179,17 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
181179 } finally {
182180 setLoading ( false ) ;
183181 }
184- } , [ apiSettings , selectedStock , toast ] ) ;
182+ } , [ selectedStock , portfolioData , stockData , toast ] ) ;
185183
186- // Fetch data on component mount and when selectedStock changes
184+ // Fetch data when period or stock changes
187185 useEffect ( ( ) => {
188186 // Don't fetch if not authenticated or session is invalid
189187 if ( ! isAuthenticated || ! isSessionValid ( ) ) {
190188 console . log ( 'PerformanceChart: Skipping fetch - session invalid or not authenticated' ) ;
191189 return ;
192190 }
193191
194- const fetchKey = `${ apiSettings ?. apiKey || 'none ' } -${ selectedStock || 'portfolio' } ` ;
192+ const fetchKey = `${ selectedStock || 'portfolio ' } -${ selectedPeriod } ` ;
195193
196194 // Avoid duplicate fetches for the same configuration
197195 if ( fetchedRef . current === fetchKey ) {
@@ -202,11 +200,11 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
202200
203201 // Add a small delay on initial mount to ensure session is settled
204202 const timeoutId = setTimeout ( ( ) => {
205- fetchData ( ) ;
203+ fetchData ( selectedPeriod ) ;
206204 } , 500 ) ;
207205
208206 return ( ) => clearTimeout ( timeoutId ) ;
209- } , [ selectedStock , fetchData , isAuthenticated ] ) ; // Include fetchData and isAuthenticated in dependencies
207+ } , [ selectedStock , selectedPeriod , fetchData , isAuthenticated ] ) ; // Include fetchData and isAuthenticated in dependencies
210208
211209 // Get real stock metrics from positions
212210 const getStockMetrics = useCallback ( ( symbol : string ) => {
@@ -271,40 +269,42 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
271269
272270 // Get appropriate data based on selection
273271 const getCurrentData = useCallback ( ( ) => {
274- // Debug logging removed to prevent console spam
275-
276- // Check for real stock data first (even if no portfolio data)
277- if ( selectedStock && stockData [ selectedStock ] ) {
272+ // Check for real stock data first
273+ if ( selectedStock && stockData [ selectedStock ] ?. [ selectedPeriod ] ) {
278274 const periodData = stockData [ selectedStock ] [ selectedPeriod ] ;
279- // Debug logging removed to prevent console spam
280-
281275 if ( periodData && Array . isArray ( periodData ) && periodData . length > 0 ) {
282- // console.log(`Returning ${periodData.length} real data points for ${selectedStock} ${selectedPeriod}`);
283276 return periodData ;
284- } else {
285- // console.warn(`No valid data for ${selectedStock} ${selectedPeriod} - periodData:`, periodData);
286277 }
287278 }
288279
289280 // Check for portfolio data
290- if ( ! selectedStock && portfolioData && portfolioData [ selectedPeriod ] ) {
281+ if ( ! selectedStock && portfolioData [ selectedPeriod ] ) {
291282 const data = portfolioData [ selectedPeriod ] ;
292- // console.log(`Returning ${data.length} portfolio data points for ${selectedPeriod}`);
293283 return data ;
294284 }
295285
296286 // No data available
297- // Debug logging removed to prevent console spam
298287 return [ ] ;
299288 } , [ selectedStock , stockData , selectedPeriod , portfolioData ] ) ;
300289
301290 const currentData = useMemo ( ( ) => getCurrentData ( ) , [ getCurrentData ] ) ;
302291
292+ // Custom tick formatter for X-axis based on period
293+ const formatXAxisTick = useCallback ( ( value : string ) => {
294+ // For 1M period, show abbreviated format
295+ if ( selectedPeriod === '1M' || selectedPeriod === '3M' || selectedPeriod === 'YTD' || selectedPeriod === '1Y' ) {
296+ // If the value already looks like "Sep 12", keep it
297+ // Otherwise try to format it consistently
298+ return value ;
299+ }
300+ return value ;
301+ } , [ selectedPeriod ] ) ;
302+
303303 const latestValue = currentData [ currentData . length - 1 ] || { value : 0 , pnl : 0 } ;
304304 const firstValue = currentData [ 0 ] || { value : 0 , pnl : 0 } ;
305305 const totalReturn = latestValue . pnl || ( latestValue . value - firstValue . value ) ;
306- const totalReturnPercent = latestValue . pnlPercent ?
307- parseFloat ( latestValue . pnlPercent ) . toFixed ( 2 ) :
306+ const totalReturnPercent = 'pnlPercent' in latestValue && latestValue . pnlPercent ?
307+ parseFloat ( String ( latestValue . pnlPercent ) ) . toFixed ( 2 ) :
308308 ( firstValue . value > 0 ? ( ( totalReturn / firstValue . value ) * 100 ) . toFixed ( 2 ) : '0.00' ) ;
309309 const isPositive = totalReturn >= 0 ;
310310
@@ -394,6 +394,20 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
394394 </ div >
395395 ) }
396396 </ div >
397+ < div className = "text-xs text-muted-foreground" >
398+ Data may be incomplete or delayed.{ ' ' }
399+ < a
400+ href = { selectedStock
401+ ? `https://app.alpaca.markets/trade/${ selectedStock } `
402+ : 'https://app.alpaca.markets/dashboard/overview'
403+ }
404+ target = "_blank"
405+ rel = "noopener noreferrer"
406+ className = "text-primary hover:underline"
407+ >
408+ View on Alpaca →
409+ </ a >
410+ </ div >
397411 </ div >
398412 </ CardHeader >
399413 < CardContent >
@@ -439,6 +453,9 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
439453 tick = { { fontSize : 11 } }
440454 tickLine = { false }
441455 axisLine = { false }
456+ interval = "preserveStartEnd"
457+ minTickGap = { 50 }
458+ tickFormatter = { formatXAxisTick }
442459 />
443460 < YAxis
444461 domain = { yAxisDomain }
0 commit comments