1- import React , { useState } from "react" ;
1+ import React , { useState , useEffect } from "react" ;
22import {
33 Dialog ,
44 DialogContent ,
@@ -34,7 +34,9 @@ import {
3434 X ,
3535 PieChart ,
3636 RefreshCw ,
37- PlayCircle
37+ PlayCircle ,
38+ ChevronUp ,
39+ ChevronDown
3840} from "lucide-react" ;
3941import { Button } from "@/components/ui/button" ;
4042import { Progress } from "@/components/ui/progress" ;
@@ -63,6 +65,7 @@ interface AnalysisDetailModalProps {
6365 isOpen : boolean ;
6466 onClose : ( ) => void ;
6567 analysisDate ?: string ; // Optional: for viewing historical analyses
68+ initialTab ?: string ; // Optional: which tab to open initially
6669}
6770
6871// Import extracted components
@@ -75,7 +78,7 @@ import { useOrderActions } from "./analysis-detail/hooks/useOrderActions";
7578import { getStatusIcon , getDecisionIcon , getDecisionVariant } from "./analysis-detail/utils/statusHelpers" ;
7679
7780
78- export default function AnalysisDetailModal ( { ticker, analysisId, isOpen, onClose, analysisDate } : AnalysisDetailModalProps ) {
81+ export default function AnalysisDetailModal ( { ticker, analysisId, isOpen, onClose, analysisDate, initialTab } : AnalysisDetailModalProps ) {
7982 // Use extracted custom hooks
8083 const { analysisData, loading, error, isLiveAnalysis, updateAnalysisData, setError } = useAnalysisData ( {
8184 ticker,
@@ -91,13 +94,58 @@ export default function AnalysisDetailModal({ ticker, analysisId, isOpen, onClos
9194
9295 const { toast } = useToast ( ) ;
9396 const [ isRetrying , setIsRetrying ] = useState ( false ) ;
97+ const [ activeTab , setActiveTab ] = useState < string > ( "" ) ;
98+ const [ collapsedCards , setCollapsedCards ] = useState < Set < string > > ( new Set ( ) ) ;
99+
100+ // Set initial tab value
101+ useEffect ( ( ) => {
102+ if ( ! activeTab && isOpen ) {
103+ // Use initialTab if provided, otherwise default based on live status
104+ setActiveTab ( initialTab || ( isLiveAnalysis ? "actions" : "insights" ) ) ;
105+ }
106+ } , [ isLiveAnalysis , isOpen , activeTab , initialTab ] ) ;
107+
108+ // Handle navigation to insight tab for a specific agent
109+ const handleNavigateToInsight = ( agentKey : string ) => {
110+ setActiveTab ( "insights" ) ;
111+ // Optionally, we could scroll to the specific agent's insight
112+ // This would require adding an id to each insight card and using scrollIntoView
113+ setTimeout ( ( ) => {
114+ let elementId = `insight-${ agentKey } ` ;
115+
116+ // Special handling for Bull/Bear Researcher - navigate to Research Debate if it exists
117+ if ( ( agentKey === 'bullResearcher' || agentKey === 'bearResearcher' ) &&
118+ analysisData ?. agent_insights ?. researchDebate ) {
119+ // Navigate to Research Debate instead (first round has the bull researcher ID)
120+ elementId = agentKey === 'bullResearcher' ? 'insight-bullResearcher' : 'insight-researchDebate' ;
121+ }
122+
123+ const element = document . getElementById ( elementId ) ;
124+ if ( element ) {
125+ element . scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
126+ }
127+ } , 100 ) ;
128+ } ;
94129
95130 // Check if analysis is stale (no update in 5+ minutes)
96131 const isAnalysisStale = ( ) => {
97- if ( ! analysisData ?. updated_at ) return false ;
132+ if ( ! analysisData ?. updated_at ) {
133+ console . log ( 'No updated_at field in analysisData:' , analysisData ) ;
134+ return false ;
135+ }
98136 const lastUpdate = new Date ( analysisData . updated_at ) ;
99137 const timeSinceUpdate = Date . now ( ) - lastUpdate . getTime ( ) ;
100- return timeSinceUpdate > 5 * 60 * 1000 ; // 5 minutes
138+ const isStale = timeSinceUpdate > 5 * 60 * 1000 ; // 5 minutes
139+
140+ console . log ( 'Staleness check:' , {
141+ updated_at : analysisData . updated_at ,
142+ lastUpdate : lastUpdate . toISOString ( ) ,
143+ timeSinceUpdate : Math . round ( timeSinceUpdate / 1000 ) + 's' ,
144+ isStale,
145+ status : analysisData . status
146+ } ) ;
147+
148+ return isStale ;
101149 } ;
102150
103151 // Handle retry for error status
@@ -299,23 +347,34 @@ export default function AnalysisDetailModal({ ticker, analysisId, isOpen, onClos
299347 </ Button >
300348 ) }
301349
302- { ( analysisData . status === ANALYSIS_STATUS . RUNNING || analysisData . status === ANALYSIS_STATUS . PENDING ) && isAnalysisStale ( ) && (
303- < Button
304- variant = "outline"
305- size = "sm"
306- onClick = { handleReactivate }
307- disabled = { isRetrying }
308- className = "flex items-center gap-2"
309- title = { `Last updated ${ formatDistanceToNow ( new Date ( analysisData . updated_at ) ) } ago` }
310- >
311- { isRetrying ? (
312- < Loader2 className = "w-4 h-4 animate-spin" />
313- ) : (
314- < PlayCircle className = "w-4 h-4" />
315- ) }
316- Reactivate
317- </ Button >
318- ) }
350+ { ( ( ) => {
351+ const shouldShowReactivate = ( analysisData . status === ANALYSIS_STATUS . RUNNING || analysisData . status === ANALYSIS_STATUS . PENDING ) && isAnalysisStale ( ) ;
352+ console . log ( 'Reactivate button check:' , {
353+ status : analysisData . status ,
354+ isRunningOrPending : analysisData . status === ANALYSIS_STATUS . RUNNING || analysisData . status === ANALYSIS_STATUS . PENDING ,
355+ isStale : isAnalysisStale ( ) ,
356+ shouldShow : shouldShowReactivate ,
357+ ANALYSIS_STATUS
358+ } ) ;
359+
360+ return shouldShowReactivate && (
361+ < Button
362+ variant = "outline"
363+ size = "sm"
364+ onClick = { handleReactivate }
365+ disabled = { isRetrying }
366+ className = "flex items-center gap-2"
367+ title = { `Last updated ${ formatDistanceToNow ( new Date ( analysisData . updated_at ) ) } ago` }
368+ >
369+ { isRetrying ? (
370+ < Loader2 className = "w-4 h-4 animate-spin" />
371+ ) : (
372+ < PlayCircle className = "w-4 h-4" />
373+ ) }
374+ Reactivate
375+ </ Button >
376+ ) ;
377+ } ) ( ) }
319378 </ >
320379 ) }
321380 </ div >
@@ -392,31 +451,67 @@ export default function AnalysisDetailModal({ ticker, analysisId, isOpen, onClos
392451 ) ;
393452 } ) ( ) }
394453
395- < Tabs defaultValue = { isLiveAnalysis ? "actions" : "insights" } className = "flex-1" >
454+ < Tabs value = { activeTab } onValueChange = { setActiveTab } className = "flex-1" >
396455 < div className = "px-6 pt-4 pb-4" >
397- < TabsList className = "grid w-full grid-cols-3 max-w-3xl mx-auto" >
398- < TabsTrigger
399- value = "actions"
400- className = "flex items-center gap-2"
401- >
402- < CheckSquare className = "w-4 h-4" />
403- Actions
404- </ TabsTrigger >
405- < TabsTrigger
406- value = "workflow"
407- className = "flex items-center gap-2"
408- >
409- < Activity className = "w-4 h-4" />
410- Workflow
411- </ TabsTrigger >
412- < TabsTrigger
413- value = "insights"
414- className = "flex items-center gap-2"
415- >
416- < Brain className = "w-4 h-4" />
417- Insights
418- </ TabsTrigger >
419- </ TabsList >
456+ < div className = "relative flex items-center justify-center" >
457+ < TabsList className = "grid w-full grid-cols-3 max-w-3xl" >
458+ < TabsTrigger
459+ value = "actions"
460+ className = "flex items-center gap-2"
461+ >
462+ < CheckSquare className = "w-4 h-4" />
463+ Actions
464+ </ TabsTrigger >
465+ < TabsTrigger
466+ value = "workflow"
467+ className = "flex items-center gap-2"
468+ >
469+ < Activity className = "w-4 h-4" />
470+ Workflow
471+ </ TabsTrigger >
472+ < TabsTrigger
473+ value = "insights"
474+ className = "flex items-center gap-2"
475+ >
476+ < Brain className = "w-4 h-4" />
477+ Insights
478+ </ TabsTrigger >
479+ </ TabsList >
480+ { activeTab === "insights" && (
481+ < Button
482+ variant = "outline"
483+ size = "sm"
484+ onClick = { ( ) => {
485+ if ( collapsedCards . size === 0 ) {
486+ // Collapse all - need to get all agent keys
487+ const allAgentKeys = new Set < string > ( ) ;
488+ if ( analysisData ?. agent_insights ) {
489+ Object . keys ( analysisData . agent_insights ) . forEach ( key => {
490+ allAgentKeys . add ( key ) ;
491+ } ) ;
492+ }
493+ setCollapsedCards ( allAgentKeys ) ;
494+ } else {
495+ // Expand all
496+ setCollapsedCards ( new Set ( ) ) ;
497+ }
498+ } }
499+ className = "text-xs absolute right-0"
500+ >
501+ { collapsedCards . size === 0 ? (
502+ < >
503+ < ChevronUp className = "h-3 w-3 mr-1" />
504+ Collapse All
505+ </ >
506+ ) : (
507+ < >
508+ < ChevronDown className = "h-3 w-3 mr-1" />
509+ Expand All
510+ </ >
511+ ) }
512+ </ Button >
513+ ) }
514+ </ div >
420515 </ div >
421516
422517 < ScrollArea className = "h-[calc(90vh-280px)]" >
@@ -439,6 +534,7 @@ export default function AnalysisDetailModal({ ticker, analysisId, isOpen, onClos
439534 onApproveOrder = { handleApproveOrder }
440535 onRejectOrder = { handleRejectOrder }
441536 isOrderExecuted = { isOrderExecuted }
537+ onNavigateToInsight = { handleNavigateToInsight }
442538 />
443539 ) : (
444540 < div className = "flex flex-col items-center justify-center py-12 text-muted-foreground" >
@@ -454,6 +550,8 @@ export default function AnalysisDetailModal({ ticker, analysisId, isOpen, onClos
454550 getMessageIcon = { getMessageIcon }
455551 getAgentIcon = { getAgentIcon }
456552 formatAgentName = { formatAgentName }
553+ collapsedCards = { collapsedCards }
554+ setCollapsedCards = { setCollapsedCards }
457555 />
458556 </ TabsContent >
459557
0 commit comments