TradingAgents/frontend/src/pages/StockDetail.tsx

232 lines
9.4 KiB
TypeScript

import { useParams, Link } from 'react-router-dom';
import { ArrowLeft, Building2, TrendingUp, TrendingDown, Minus, AlertTriangle, Info, Calendar, Activity } from 'lucide-react';
import { NIFTY_50_STOCKS } from '../types';
import { sampleRecommendations, getStockHistory } from '../data/recommendations';
import { DecisionBadge, ConfidenceBadge, RiskBadge } from '../components/StockCard';
export default function StockDetail() {
const { symbol } = useParams<{ symbol: string }>();
const stock = NIFTY_50_STOCKS.find(s => s.symbol === symbol);
const latestRecommendation = sampleRecommendations[0];
const analysis = latestRecommendation?.analysis[symbol || ''];
const history = symbol ? getStockHistory(symbol) : [];
if (!stock) {
return (
<div className="min-h-[60vh] flex items-center justify-center">
<div className="text-center">
<AlertTriangle className="w-12 h-12 text-amber-500 mx-auto mb-4" />
<h2 className="text-xl font-semibold text-gray-700 mb-2">Stock Not Found</h2>
<p className="text-gray-500 mb-4">The stock "{symbol}" was not found in Nifty 50.</p>
<Link to="/stocks" className="btn-primary">
View All Stocks
</Link>
</div>
</div>
);
}
const decisionIcon = {
BUY: TrendingUp,
SELL: TrendingDown,
HOLD: Minus,
};
const decisionColor = {
BUY: 'from-green-500 to-green-600',
SELL: 'from-red-500 to-red-600',
HOLD: 'from-amber-500 to-amber-600',
};
const DecisionIcon = analysis?.decision ? decisionIcon[analysis.decision] : Activity;
const bgGradient = analysis?.decision ? decisionColor[analysis.decision] : 'from-gray-500 to-gray-600';
return (
<div className="space-y-8">
{/* Back Button */}
<div>
<Link
to="/stocks"
className="inline-flex items-center gap-2 text-gray-600 hover:text-nifty-600 transition-colors"
>
<ArrowLeft className="w-4 h-4" />
Back to All Stocks
</Link>
</div>
{/* Stock Header */}
<section className="card overflow-hidden">
<div className={`bg-gradient-to-r ${bgGradient} p-6 text-white`}>
<div className="flex items-start justify-between">
<div>
<div className="flex items-center gap-3 mb-2">
<h1 className="text-3xl font-display font-bold">{stock.symbol}</h1>
{analysis?.decision && (
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm font-semibold bg-white/20 backdrop-blur-sm">
<DecisionIcon className="w-4 h-4" />
{analysis.decision}
</span>
)}
</div>
<p className="text-white/90 text-lg">{stock.company_name}</p>
<div className="flex items-center gap-2 mt-3 text-white/80">
<Building2 className="w-4 h-4" />
<span>{stock.sector || 'N/A'}</span>
</div>
</div>
<div className="text-right">
<div className="text-sm text-white/80 mb-1">Latest Analysis</div>
<div className="flex items-center gap-2 text-white/90">
<Calendar className="w-4 h-4" />
{latestRecommendation?.date ? new Date(latestRecommendation.date).toLocaleDateString('en-IN', {
month: 'short',
day: 'numeric',
year: 'numeric',
}) : 'N/A'}
</div>
</div>
</div>
</div>
{/* Analysis Details */}
<div className="p-6">
{analysis ? (
<div className="grid md:grid-cols-3 gap-6">
<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-500 uppercase tracking-wide">Decision</h3>
<DecisionBadge decision={analysis.decision} />
</div>
<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-500 uppercase tracking-wide">Confidence</h3>
<ConfidenceBadge confidence={analysis.confidence} />
</div>
<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-500 uppercase tracking-wide">Risk Level</h3>
<RiskBadge risk={analysis.risk} />
</div>
</div>
) : (
<div className="flex items-center gap-3 text-gray-500">
<Info className="w-5 h-5" />
<span>No analysis available for this stock yet.</span>
</div>
)}
</div>
</section>
{/* Quick Stats Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="card p-4 text-center">
<div className="text-2xl font-bold text-gray-900">{history.length}</div>
<div className="text-sm text-gray-500">Total Analyses</div>
</div>
<div className="card p-4 text-center">
<div className="text-2xl font-bold text-green-600">
{history.filter(h => h.decision === 'BUY').length}
</div>
<div className="text-sm text-gray-500">Buy Signals</div>
</div>
<div className="card p-4 text-center">
<div className="text-2xl font-bold text-amber-600">
{history.filter(h => h.decision === 'HOLD').length}
</div>
<div className="text-sm text-gray-500">Hold Signals</div>
</div>
<div className="card p-4 text-center">
<div className="text-2xl font-bold text-red-600">
{history.filter(h => h.decision === 'SELL').length}
</div>
<div className="text-sm text-gray-500">Sell Signals</div>
</div>
</div>
{/* Analysis History */}
<section className="card">
<div className="p-6 border-b border-gray-100">
<h2 className="section-title">Recommendation History</h2>
</div>
{history.length > 0 ? (
<div className="divide-y divide-gray-100">
{history.map((entry, idx) => (
<div key={idx} className="p-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="text-sm text-gray-500">
{new Date(entry.date).toLocaleDateString('en-IN', {
weekday: 'short',
month: 'short',
day: 'numeric',
year: 'numeric',
})}
</div>
</div>
<DecisionBadge decision={entry.decision} />
</div>
))}
</div>
) : (
<div className="p-12 text-center">
<Calendar className="w-12 h-12 text-gray-300 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-gray-700 mb-2">No History Yet</h3>
<p className="text-gray-500">Recommendation history will appear here as we analyze this stock daily.</p>
</div>
)}
</section>
{/* Top Pick / Avoid Status */}
{latestRecommendation && (
<>
{latestRecommendation.top_picks.some(p => p.symbol === symbol) && (
<section className="card bg-gradient-to-r from-green-50 to-emerald-50 border-green-200">
<div className="p-6">
<div className="flex items-start gap-4">
<div className="w-12 h-12 rounded-xl bg-green-100 flex items-center justify-center">
<TrendingUp className="w-6 h-6 text-green-600" />
</div>
<div>
<h3 className="text-lg font-semibold text-green-800 mb-2">Top Pick</h3>
<p className="text-green-700">
{latestRecommendation.top_picks.find(p => p.symbol === symbol)?.reason}
</p>
</div>
</div>
</div>
</section>
)}
{latestRecommendation.stocks_to_avoid.some(s => s.symbol === symbol) && (
<section className="card bg-gradient-to-r from-red-50 to-rose-50 border-red-200">
<div className="p-6">
<div className="flex items-start gap-4">
<div className="w-12 h-12 rounded-xl bg-red-100 flex items-center justify-center">
<AlertTriangle className="w-6 h-6 text-red-600" />
</div>
<div>
<h3 className="text-lg font-semibold text-red-800 mb-2">Stock to Avoid</h3>
<p className="text-red-700">
{latestRecommendation.stocks_to_avoid.find(s => s.symbol === symbol)?.reason}
</p>
</div>
</div>
</div>
</section>
)}
</>
)}
{/* Disclaimer */}
<section className="card p-6 bg-gray-50">
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-gray-400 flex-shrink-0 mt-0.5" />
<p className="text-sm text-gray-600">
<strong>Disclaimer:</strong> This AI-generated recommendation is for educational purposes only.
It should not be considered as financial advice. Always do your own research and consult with
a qualified financial advisor before making investment decisions.
</p>
</div>
</section>
</div>
);
}