gs-port / src /components /StockPopup.jsx
Scribbler310's picture
Upload folder using huggingface_hub
0c5252a verified
import React, { useEffect, useState, useMemo } from 'react';
import { X, TrendingUp, TrendingDown, Star } from 'lucide-react';
import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer, ReferenceLine } from 'recharts';
import { getHistoricalData, fetchRealData } from '../data/historicalData';
import { Loader2 } from 'lucide-react';
import InvestmentCommittee from './InvestmentCommittee';
const TIMEFRAME_DAYS = {
'1W': 7,
'1M': 30,
'3M': 90,
'1Y': 365,
'ALL': 365
};
export default function StockPopup({ ticker, assetName, onClose, allHoldings = [], prices = {} }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [isDebating, setIsDebating] = useState(false);
const [timeframe, setTimeframe] = useState('1M');
useEffect(() => {
async function loadData() {
if (ticker) {
setLoading(true);
const realData = await fetchRealData(ticker);
setData(realData);
setLoading(false);
setIsDebating(false);
}
}
loadData();
}, [ticker]);
// Calculate Top Performers
const topPerformers = useMemo(() => {
if (!allHoldings.length || !prices) return [];
return allHoldings
.map(asset => ({
...asset,
perf: prices[asset.ticker]?.percent || 0,
currentPrice: prices[asset.ticker]?.price || 0
}))
.sort((a, b) => b.perf - a.perf)
.slice(0, 5);
}, [allHoldings, prices]);
const viewData = useMemo(() => {
if (!data) return null;
// Slice history based on timeframe
const daysToShow = TIMEFRAME_DAYS[timeframe] || 30;
const startIndex = Math.max(0, data.history.length - daysToShow);
const slicedHistory = data.history.slice(startIndex);
// Recalculate metrics for the specific timeframe
if (slicedHistory.length === 0) return null;
const currentPrice = slicedHistory[slicedHistory.length - 1].price;
const oldPrice = slicedHistory[0].price;
const change = Number((currentPrice - oldPrice).toFixed(2));
const percentChange = Number(((change / oldPrice) * 100).toFixed(2));
const isPositive = change >= 0;
return {
...data,
history: slicedHistory,
change,
percentChange,
isPositive
};
}, [data, timeframe]);
if (!ticker) return null;
if (loading || !viewData) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm">
<div className="text-center">
<Loader2 className="animate-spin text-gs-gold mb-4 mx-auto" size={48} />
<p className="text-white text-lg font-light">Connecting to Yahoo Finance...</p>
</div>
</div>
);
}
const color = viewData.isPositive ? '#00C805' : '#FF5000';
const bgColor = '#111111'; // Dark theme background
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200">
<div
className="w-full max-w-5xl max-h-[90vh] overflow-y-auto rounded-xl shadow-2xl flex flex-col animate-in zoom-in-95 duration-200"
style={{ backgroundColor: bgColor }}
>
{/* Header */}
<div className="p-6 pb-4 flex justify-between items-start sticky top-0 bg-[#111111] z-10 border-b border-gray-800">
<div>
<h2 className="text-2xl font-bold text-white flex items-center">
{assetName} <Star size={18} className="ml-3 text-gray-500 hover:text-yellow-400 cursor-pointer" />
</h2>
<div className="flex items-end mt-2 space-x-3">
<span className="text-4xl font-bold text-white">${viewData.currentPrice}</span>
<div className={`flex items-center text-lg font-medium pb-1 ${viewData.isPositive ? 'text-[#00C805]' : 'text-[#FF5000]'}`}>
{viewData.isPositive ? '+' : ''}{viewData.change} ({viewData.isPositive ? '+' : ''}{viewData.percentChange}%)
</div>
</div>
<p className="text-gray-400 text-xs mt-1">At close: {viewData.history[viewData.history.length-1].date}</p>
</div>
<button
onClick={onClose}
className="p-2 rounded-full bg-gray-800 text-gray-400 hover:text-white hover:bg-gray-700 transition-colors"
>
<X size={20} />
</button>
</div>
<div className="p-8 grid grid-cols-1 lg:grid-cols-12 gap-10">
{/* Main Chart Area */}
<div className="lg:col-span-8">
<div className="h-80 w-full mt-4">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={viewData.history} margin={{ top: 10, right: 0, left: 0, bottom: 0 }}>
<defs>
<linearGradient id="colorPrice" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={color} stopOpacity={0.3}/>
<stop offset="95%" stopColor={color} stopOpacity={0}/>
</linearGradient>
</defs>
<XAxis dataKey="date" hide />
<YAxis domain={['dataMin', 'dataMax']} hide />
<Tooltip
contentStyle={{ backgroundColor: '#222', border: 'none', borderRadius: '8px', color: '#fff' }}
itemStyle={{ color: '#fff', fontWeight: 'bold' }}
labelStyle={{ color: '#888' }}
/>
<ReferenceLine y={viewData.history[0].price} stroke="#333" strokeDasharray="3 3" />
<Area
type="monotone"
dataKey="price"
stroke={color}
strokeWidth={2}
fillOpacity={1}
fill="url(#colorPrice)"
/>
</AreaChart>
</ResponsiveContainer>
</div>
{/* Time Controls */}
<div className="flex space-x-6 mt-6 border-b border-gray-800 pb-2">
{['1W', '1M', '3M', '1Y', 'ALL'].map(t => (
<button
key={t}
onClick={() => setTimeframe(t)}
className={`text-sm font-bold pb-2 border-b-2 transition-colors ${
t === timeframe
? (viewData.isPositive ? 'text-[#00C805] border-[#00C805]' : 'text-[#FF5000] border-[#FF5000]')
: 'text-gray-600 border-transparent hover:text-gray-300'
}`}
>
{t}
</button>
))}
</div>
<div className="mt-8">
<InvestmentCommittee
ticker={ticker}
isDebating={isDebating}
setIsDebating={setIsDebating}
/>
</div>
</div>
{/* Right Sidebar: Top Performers */}
<div className="lg:col-span-4 space-y-8">
<div>
<h3 className="text-white text-xs font-black uppercase tracking-[0.2em] mb-8 flex items-center gap-3">
<span className="text-lg">πŸ†</span> Top Performers
</h3>
<div className="space-y-6">
{topPerformers.map((asset, idx) => (
<div key={asset.ticker} className="flex items-center justify-between group">
<div className="flex items-center gap-4">
<div className="w-6 flex justify-center">
{idx === 0 ? <span className="text-lg">πŸ₯‡</span> :
idx === 1 ? <span className="text-lg">πŸ₯ˆ</span> :
idx === 2 ? <span className="text-lg">πŸ₯‰</span> :
<div className="p-1.5 bg-gs-navy rounded-md text-gs-gold/50"><TrendingUp size={12} /></div>}
</div>
<div
className="w-12 h-12 rounded-xl flex items-center justify-center text-[10px] font-black text-gs-navy"
style={{ backgroundColor: asset.color }}
>
{asset.ticker}
</div>
<div>
<p className="text-sm font-bold text-white tracking-tight">{asset.ticker}</p>
<p className="text-[10px] text-gray-500 font-medium tracking-wide">${asset.currentPrice.toLocaleString()}</p>
</div>
</div>
<div className="text-right">
<span className={`text-sm font-black ${asset.perf >= 0 ? 'text-[#00C805]' : 'text-[#FF5000]'}`}>
{asset.perf >= 0 ? '+' : ''}{asset.perf.toFixed(2)}%
</span>
</div>
</div>
))}
</div>
</div>
<div className="pt-8 border-t border-gray-800">
<div className="bg-gs-navy/30 rounded-2xl p-5 border border-white/5">
<h4 className="text-[10px] font-bold text-gs-gold uppercase tracking-widest mb-3">Portfolio Insight</h4>
<p className="text-xs text-gray-400 leading-relaxed">
{assetName} currently ranks #{topPerformers.findIndex(a => a.ticker === ticker) + 1} among your top-tier performers.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
}