feat: top-pick and open-trade status labels on the ticker page
Two read-only pills in the ticker header, beside the watchlist toggle: - "Top Pick" when the ticker is the current #1 — the same ranking the dashboard highlights, via a shared topPickSymbol() helper so the two stay in sync. - "Open Trade" when an open paper trade exists on the ticker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,10 @@ import { useParams } from 'react-router-dom';
|
||||
import { useTickerDetail } from '../hooks/useTickerDetail';
|
||||
import { useFetchSymbolData } from '../hooks/useFetchSymbolData';
|
||||
import { useWatchlist, useAddToWatchlist, useRemoveFromWatchlist } from '../hooks/useWatchlist';
|
||||
import { useTrades } from '../hooks/useTrades';
|
||||
import { usePaperTrades } from '../hooks/usePaperTrades';
|
||||
import { useActivation } from '../hooks/useActivation';
|
||||
import { topPickSymbol } from '../lib/qualification';
|
||||
import type { FetchSelector } from '../api/ingestion';
|
||||
import { CandlestickChart } from '../components/charts/CandlestickChart';
|
||||
import { ScoreCard } from '../components/ui/ScoreCard';
|
||||
@@ -29,6 +33,21 @@ function SectionError({ message, onRetry }: { message: string; onRetry?: () => v
|
||||
);
|
||||
}
|
||||
|
||||
function StatusPill({ tone, label, title }: { tone: 'blue' | 'emerald'; label: string; title?: string }) {
|
||||
const tones = {
|
||||
blue: 'bg-blue-500/15 text-blue-300 border-blue-500/30',
|
||||
emerald: 'bg-emerald-500/15 text-emerald-300 border-emerald-500/30',
|
||||
} as const;
|
||||
return (
|
||||
<span
|
||||
title={title}
|
||||
className={`inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-medium ${tones[tone]}`}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function timeAgo(iso: string): string {
|
||||
const diff = Date.now() - new Date(iso).getTime();
|
||||
const mins = Math.floor(diff / 60_000);
|
||||
@@ -107,6 +126,21 @@ export default function TickerDetailPage() {
|
||||
[watchlist.data, symbol],
|
||||
);
|
||||
const watchlistBusy = addToWatchlist.isPending || removeFromWatchlist.isPending;
|
||||
|
||||
// Status labels: is there an open paper trade on this ticker, and is it the
|
||||
// current top pick (same ranking the dashboard highlights)?
|
||||
const openTrades = usePaperTrades('open');
|
||||
const allTrades = useTrades();
|
||||
const activation = useActivation();
|
||||
const hasOpenTrade = useMemo(
|
||||
() => (openTrades.data ?? []).some((t) => t.symbol.toUpperCase() === symbol.toUpperCase()),
|
||||
[openTrades.data, symbol],
|
||||
);
|
||||
const isTopPick = useMemo(
|
||||
() => topPickSymbol(allTrades.data, activation.data)?.toUpperCase() === symbol.toUpperCase(),
|
||||
[allTrades.data, activation.data, symbol],
|
||||
);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<DetailTab>('Analysis');
|
||||
const [refreshingLabel, setRefreshingLabel] = useState<string | null>(null);
|
||||
|
||||
@@ -226,6 +260,20 @@ export default function TickerDetailPage() {
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{isTopPick && (
|
||||
<StatusPill
|
||||
tone="blue"
|
||||
label="★ Top Pick"
|
||||
title="Current top pick — highest-momentum qualified setup right now"
|
||||
/>
|
||||
)}
|
||||
{hasOpenTrade && (
|
||||
<StatusPill
|
||||
tone="emerald"
|
||||
label="● Open Trade"
|
||||
title="You have an open paper trade on this ticker"
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() =>
|
||||
|
||||
Reference in New Issue
Block a user