Skip to content

Commit 24b93dc

Browse files
committed
Improve loading states and streamline analysis checks
Added explicit loading indicators for rebalance and analysis checks in PortfolioPositions and AnalysisControls. Refactored analysisChecker to reduce redundant database queries by filtering completed and running analyses in memory, improving efficiency. Updated AgentsTab to clean up formatting and update help text for analysis optimization.
1 parent beb21a2 commit 24b93dc

File tree

5 files changed

+124
-133
lines changed

5 files changed

+124
-133
lines changed

src/components/Footer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const Footer = () => {
9191
<div className="border-t border-border py-6">
9292
<div className="text-center">
9393
<p className="text-sm text-muted-foreground">
94-
© {currentYear} TradingGoose. All rights reserved. - Pioneering Trading Intelligence with AI Precision
94+
© {currentYear} TradingGoose. All rights reserved. - Pioneering Trading Intelligence with AI Analysis - HONK!
9595
</p>
9696
</div>
9797
</div>

src/components/PortfolioPositions.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export default function PortfolioPositions({ onSelectStock, selectedStock }: Por
6565
const [runningAnalysesCount, setRunningAnalysesCount] = useState(0);
6666
const [showAnalysisAlert, setShowAnalysisAlert] = useState(false);
6767
const [showRebalanceAccessAlert, setShowRebalanceAccessAlert] = useState(false);
68+
const [checkingRebalance, setCheckingRebalance] = useState(true); // Loading state for initial rebalance check
6869

6970
// Use ref to track previous running rebalance
7071
const previousRunningRef = useRef<string | null>(null);
@@ -336,12 +337,14 @@ export default function PortfolioPositions({ onSelectStock, selectedStock }: Por
336337

337338
const checkRunningRebalance = async () => {
338339
if (!user || !isAuthenticated) {
340+
setCheckingRebalance(false);
339341
return;
340342
}
341343

342344
// Double-check session validity inside the async function
343345
if (!isSessionValid()) {
344346
console.log('PortfolioPositions: Skipping rebalance check - session invalid or not authenticated');
347+
setCheckingRebalance(false);
345348
return;
346349
}
347350

@@ -394,6 +397,7 @@ export default function PortfolioPositions({ onSelectStock, selectedStock }: Por
394397
if (data && data.length > 0) {
395398
const activeRebalance = data[0];
396399
setRunningRebalance(activeRebalance.id);
400+
setCheckingRebalance(false); // Set loading to false when rebalance is found
397401

398402
// Check if rebalance just started (wasn't running before)
399403
if (!previousRunningRef.current && activeRebalance.id) {
@@ -435,8 +439,12 @@ export default function PortfolioPositions({ onSelectStock, selectedStock }: Por
435439
setRunningRebalance(null);
436440
previousRunningRef.current = null;
437441
}
442+
443+
// Set loading to false after first check completes
444+
setCheckingRebalance(false);
438445
} catch (error) {
439446
console.error('Error checking running rebalance:', error);
447+
setCheckingRebalance(false);
440448
}
441449
};
442450

@@ -452,7 +460,8 @@ export default function PortfolioPositions({ onSelectStock, selectedStock }: Por
452460
}
453461
};
454462
} else {
455-
// If not authenticated, clean up any existing interval
463+
// If not authenticated, set loading to false and clean up any existing interval
464+
setCheckingRebalance(false);
456465
return () => {
457466
if (intervalRef) {
458467
clearInterval(intervalRef);
@@ -477,7 +486,17 @@ export default function PortfolioPositions({ onSelectStock, selectedStock }: Por
477486
</div>
478487
<div className="flex items-center gap-2">
479488

480-
{runningRebalance ? (
489+
{checkingRebalance ? (
490+
// Show loading state while checking for running rebalance
491+
<Button
492+
variant="outline"
493+
size="sm"
494+
disabled
495+
>
496+
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
497+
Checking...
498+
</Button>
499+
) : runningRebalance ? (
481500
<Button
482501
variant="outline"
483502
size="sm"

src/components/workflow/components/AnalysisControls.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,7 @@ export function AnalysisControls({
2727
}: AnalysisControlsProps) {
2828
return (
2929
<div className="flex items-center justify-center p-2 rounded-lg mb-2 min-h-[36px]">
30-
{isInitialLoading ? (
31-
// Show loading state while checking for running analyses
32-
<div className="flex items-center justify-center w-full">
33-
<Loader2 className="w-4 h-4 mr-2 animate-spin text-muted-foreground" />
34-
<span className="text-sm text-muted-foreground">
35-
Checking for running analyses...
36-
</span>
37-
</div>
38-
) : activeAnalysisTicker && isAnalyzing ? (
30+
{activeAnalysisTicker && isAnalyzing ? (
3931
<div className="flex items-center justify-between w-full">
4032
<div className="flex items-center">
4133
<Activity className="w-4 h-4 mr-2 animate-pulse text-primary" />
@@ -64,15 +56,25 @@ export function AnalysisControls({
6456
value={searchTicker}
6557
onChange={setSearchTicker}
6658
placeholder="Enter ticker to analyze"
59+
disabled={isInitialLoading}
6760
/>
6861
</div>
6962
<Button
7063
type="submit"
71-
disabled={!searchTicker || isAnalyzing}
64+
disabled={!searchTicker || isAnalyzing || isInitialLoading}
7265
size="sm"
7366
>
74-
<Play className="h-4 w-4 mr-1" />
75-
Analyze
67+
{isInitialLoading ? (
68+
<>
69+
<Loader2 className="h-4 w-4 mr-1 animate-spin" />
70+
Checking...
71+
</>
72+
) : (
73+
<>
74+
<Play className="h-4 w-4 mr-1" />
75+
Analyze
76+
</>
77+
)}
7678
</Button>
7779
</form>
7880
)}

src/components/workflow/hooks/helpers/analysisChecker.ts

Lines changed: 49 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,20 @@ export async function checkRunningAnalyses({
3535
// Check database for running analyses if user is authenticated
3636
if (user) {
3737
try {
38-
// Only fetch analyses from the last 24 hours (for checking running analyses)
39-
const twentyFourHoursAgo = new Date();
40-
twentyFourHoursAgo.setHours(twentyFourHoursAgo.getHours() - 24);
38+
// Fetch ALL analyses from today in a single query
39+
const today = new Date();
40+
today.setHours(0, 0, 0, 0);
4141

42-
const { data, error } = await supabase
42+
const { data: todayAnalyses, error } = await supabase
4343
.from('analysis_history')
44-
.select('ticker, analysis_status, full_analysis, created_at, id, decision, agent_insights, rebalance_request_id, is_canceled')
44+
.select('*')
4545
.eq('user_id', user.id)
46-
.gte('created_at', twentyFourHoursAgo.toISOString()) // Only last 24 hours
46+
.gte('created_at', today.toISOString()) // All of today's analyses
4747
.order('created_at', { ascending: false });
4848

49-
if (!error && data) {
50-
// Filter to only actually running analyses using centralized logic
51-
const runningData = data.filter(item => {
49+
if (!error && todayAnalyses) {
50+
// Filter for running analyses in memory (no additional query)
51+
const runningData = todayAnalyses.filter(item => {
5252
// Convert legacy numeric status if needed
5353
const currentStatus = typeof item.analysis_status === 'number'
5454
? convertLegacyAnalysisStatus(item.analysis_status)
@@ -64,13 +64,25 @@ export async function checkRunningAnalyses({
6464
return isRunning;
6565
});
6666

67+
// Filter for completed analyses in memory (no additional query)
68+
const completedData = todayAnalyses.filter(item => {
69+
const currentStatus = typeof item.analysis_status === 'number'
70+
? convertLegacyAnalysisStatus(item.analysis_status)
71+
: item.analysis_status;
72+
73+
return !item.is_canceled &&
74+
currentStatus !== ANALYSIS_STATUS.CANCELLED &&
75+
!isAnalysisActive(currentStatus);
76+
});
77+
6778
// Only log if there are actually running analyses
6879
if (runningData.length > 0) {
6980
console.log('Running analyses from DB:', runningData.map(d => ({
7081
ticker: d.ticker,
7182
status: d.analysis_status
7283
})));
7384
}
85+
7486
for (const item of runningData) {
7587
running.add(item.ticker);
7688
}
@@ -80,9 +92,7 @@ export async function checkRunningAnalyses({
8092

8193
// Use the most recent running analysis for display
8294
if (runningData.length > 0) {
83-
const mostRecent = runningData.sort((a, b) =>
84-
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
85-
)[0];
95+
const mostRecent = runningData[0]; // Already sorted by created_at desc
8696
console.log('Most recent running analysis:', {
8797
ticker: mostRecent.ticker,
8898
rebalance_request_id: mostRecent.rebalance_request_id,
@@ -92,77 +102,44 @@ export async function checkRunningAnalyses({
92102
setActiveAnalysisTicker(mostRecent.ticker);
93103
const stillRunning = updateWorkflowFromAnalysis(mostRecent);
94104
setIsAnalyzing(stillRunning);
105+
} else if (completedData.length > 0 && !activeAnalysisTicker) {
106+
// No running analyses, show most recent completed one
107+
const mostRecentCompleted = completedData[0];
108+
setCurrentAnalysis(mostRecentCompleted);
109+
setActiveAnalysisTicker(mostRecentCompleted.ticker);
110+
const stillRunning = updateWorkflowFromAnalysis(mostRecentCompleted);
111+
setIsAnalyzing(stillRunning);
95112
}
96-
}
97-
} catch (error) {
98-
console.error('Error checking running analyses:', error);
99-
}
100-
}
101113

102-
// Check if any analyses just completed (were running before but not now)
103-
const justCompleted = Array.from(previousRunningRef.current).filter(ticker => !running.has(ticker));
104-
if (justCompleted.length > 0) {
105-
console.log('Analyses completed, reloading for:', justCompleted);
106-
107-
// Fetch the completed analysis data (should be recent)
108-
try {
109-
// Only look for analyses completed in the last hour
110-
const oneHourAgo = new Date();
111-
oneHourAgo.setHours(oneHourAgo.getHours() - 1);
112-
113-
const { data, error } = await supabase
114-
.from('analysis_history')
115-
.select('*')
116-
.eq('user_id', user!.id)
117-
.eq('is_canceled', false)
118-
.in('ticker', justCompleted)
119-
.neq('analysis_status', ANALYSIS_STATUS.CANCELLED)
120-
.gte('created_at', oneHourAgo.toISOString()) // Only recent completions
121-
.order('created_at', { ascending: false })
122-
.limit(1)
123-
.maybeSingle();
124-
125-
if (!error && data) {
126-
setCurrentAnalysis(data);
127-
setActiveAnalysisTicker(data.ticker);
128-
const stillRunning = updateWorkflowFromAnalysis(data);
129-
setIsAnalyzing(stillRunning);
114+
// Check if any analyses just completed (were running before but not now)
115+
const justCompleted = Array.from(previousRunningRef.current).filter(ticker => !running.has(ticker));
116+
if (justCompleted.length > 0) {
117+
console.log('Analyses completed, reloading for:', justCompleted);
118+
119+
// Find the completed analysis from our already-fetched data (no additional query!)
120+
const completedAnalysis = completedData.find(item => justCompleted.includes(item.ticker));
121+
122+
if (completedAnalysis) {
123+
setCurrentAnalysis(completedAnalysis);
124+
setActiveAnalysisTicker(completedAnalysis.ticker);
125+
const stillRunning = updateWorkflowFromAnalysis(completedAnalysis);
126+
setIsAnalyzing(stillRunning);
127+
}
128+
}
130129
}
131130
} catch (error) {
132-
console.error('Error fetching completed analysis:', error);
131+
console.error('Error checking running analyses:', error);
133132
}
133+
} else {
134+
// User not authenticated
135+
setRunningAnalysesCount(0);
136+
setIsAnalyzing(false);
134137
}
135138

136139
// If no running analyses and we were analyzing, keep showing the last one
137140
if (running.size === 0 && previousRunningRef.current.size > 0) {
138141
setIsAnalyzing(false);
139142
// Keep the current analysis display, don't reset
140-
} else if (running.size === 0 && !activeAnalysisTicker && user) {
141-
// No analyses at all - check for recent completed ones
142-
try {
143-
const thirtyMinutesAgo = new Date();
144-
thirtyMinutesAgo.setMinutes(thirtyMinutesAgo.getMinutes() - 30);
145-
146-
const { data, error } = await supabase
147-
.from('analysis_history')
148-
.select('*')
149-
.eq('user_id', user.id)
150-
.eq('is_canceled', false)
151-
.neq('analysis_status', ANALYSIS_STATUS.CANCELLED)
152-
.gte('created_at', thirtyMinutesAgo.toISOString())
153-
.order('created_at', { ascending: false })
154-
.limit(1)
155-
.maybeSingle();
156-
157-
if (!error && data) {
158-
setCurrentAnalysis(data);
159-
setActiveAnalysisTicker(data.ticker);
160-
const stillRunning = updateWorkflowFromAnalysis(data);
161-
setIsAnalyzing(stillRunning);
162-
}
163-
} catch (error) {
164-
console.error('Error fetching recent analysis:', error);
165-
}
166143
}
167144

168145
// Only log if there are running analyses or if status changed

0 commit comments

Comments
 (0)