stock-bot/apps/stock/web-app/src/features/symbols/SymbolDetailPage.tsx

221 lines
No EOL
8.7 KiB
TypeScript

import { ArrowLeftIcon } from '@heroicons/react/24/outline';
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { ChartPage } from '../charts/ChartPage';
import type { Symbol } from './types';
const tabs = [
{ id: 'chart', name: 'Chart' },
{ id: 'financials', name: 'Financials' },
{ id: 'filings', name: 'Filings' },
{ id: 'insiders', name: 'Insiders' },
{ id: 'shorts', name: 'Shorts' },
{ id: 'sentiment', name: 'Sentiment' },
{ id: 'news', name: 'News' },
{ id: 'info', name: 'Info' },
];
// Mock symbol data - in real app would fetch from API
const mockSymbolData: Record<string, Symbol> = {
'AAPL': { symbol: 'AAPL', name: 'Apple Inc.', exchange: 'NASDAQ', country: 'US', type: 'Common Stock', currency: 'USD', marketCap: 3.48e12, sector: 'Technology', industry: 'Consumer Electronics' },
'SHOP': { symbol: 'SHOP', name: 'Shopify Inc.', exchange: 'TSX', country: 'CA', type: 'Common Stock', currency: 'CAD', marketCap: 135e9, sector: 'Technology', industry: 'E-Commerce Software' },
'TD': { symbol: 'TD', name: 'Toronto-Dominion Bank', exchange: 'TSX', country: 'CA', type: 'Common Stock', currency: 'CAD', marketCap: 150e9, sector: 'Financial', industry: 'Banks' },
};
export function SymbolDetailPage() {
const { symbol } = useParams<{ symbol: string }>();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState('chart');
const [symbolData, setSymbolData] = useState<Symbol | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Simulate API call to fetch symbol data
setIsLoading(true);
setTimeout(() => {
const data = mockSymbolData[symbol || ''] || {
symbol: symbol || 'UNKNOWN',
name: 'Unknown Company',
exchange: 'UNKNOWN',
country: 'US',
type: 'Common Stock',
currency: 'USD',
};
setSymbolData(data);
setIsLoading(false);
}, 500);
}, [symbol]);
if (isLoading) {
return (
<div className="h-full flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-primary-500"></div>
</div>
);
}
if (!symbolData) {
return (
<div className="h-full flex items-center justify-center">
<div className="text-center">
<p className="text-text-secondary mb-4">Symbol not found</p>
<button
onClick={() => navigate('/symbols')}
className="text-primary-500 hover:text-primary-600"
>
Back to Symbols
</button>
</div>
</div>
);
}
const renderTabContent = () => {
switch (activeTab) {
case 'chart':
return <ChartPage symbol={symbolData.symbol} showHeader={false} />;
case 'financials':
return (
<div className="p-8">
<h2 className="text-lg font-semibold text-text-primary mb-4">Financial Statements</h2>
<p className="text-text-secondary">Financial data coming soon...</p>
</div>
);
case 'filings':
return (
<div className="p-8">
<h2 className="text-lg font-semibold text-text-primary mb-4">SEC Filings</h2>
<p className="text-text-secondary">Filings data coming soon...</p>
</div>
);
case 'insiders':
return (
<div className="p-8">
<h2 className="text-lg font-semibold text-text-primary mb-4">Insider Trading</h2>
<p className="text-text-secondary">Insider trading data coming soon...</p>
</div>
);
case 'shorts':
return (
<div className="p-8">
<h2 className="text-lg font-semibold text-text-primary mb-4">Short Interest</h2>
<p className="text-text-secondary">Short interest data coming soon...</p>
</div>
);
case 'sentiment':
return (
<div className="p-8">
<h2 className="text-lg font-semibold text-text-primary mb-4">Market Sentiment</h2>
<p className="text-text-secondary">Sentiment analysis coming soon...</p>
</div>
);
case 'news':
return (
<div className="p-8">
<h2 className="text-lg font-semibold text-text-primary mb-4">Latest News</h2>
<p className="text-text-secondary">News feed coming soon...</p>
</div>
);
case 'info':
return (
<div className="p-8">
<h2 className="text-lg font-semibold text-text-primary mb-4">Company Information</h2>
<div className="bg-surface-secondary rounded-lg border border-border p-6">
<dl className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<dt className="text-sm font-medium text-text-secondary">Symbol</dt>
<dd className="mt-1 text-sm text-text-primary">{symbolData.symbol}</dd>
</div>
<div>
<dt className="text-sm font-medium text-text-secondary">Name</dt>
<dd className="mt-1 text-sm text-text-primary">{symbolData.name}</dd>
</div>
<div>
<dt className="text-sm font-medium text-text-secondary">Exchange</dt>
<dd className="mt-1 text-sm text-text-primary">{symbolData.exchange}</dd>
</div>
<div>
<dt className="text-sm font-medium text-text-secondary">Country</dt>
<dd className="mt-1 text-sm text-text-primary">{symbolData.country}</dd>
</div>
<div>
<dt className="text-sm font-medium text-text-secondary">Type</dt>
<dd className="mt-1 text-sm text-text-primary">{symbolData.type}</dd>
</div>
<div>
<dt className="text-sm font-medium text-text-secondary">Currency</dt>
<dd className="mt-1 text-sm text-text-primary">{symbolData.currency}</dd>
</div>
{symbolData.sector && (
<div>
<dt className="text-sm font-medium text-text-secondary">Sector</dt>
<dd className="mt-1 text-sm text-text-primary">{symbolData.sector}</dd>
</div>
)}
{symbolData.industry && (
<div>
<dt className="text-sm font-medium text-text-secondary">Industry</dt>
<dd className="mt-1 text-sm text-text-primary">{symbolData.industry}</dd>
</div>
)}
</dl>
</div>
</div>
);
default:
return null;
}
};
return (
<div className="h-full flex flex-col">
{/* Header with Tabs */}
<div className="flex-shrink-0 border-b border-border">
<div className="flex items-center justify-between">
<div className="flex items-center">
<button
onClick={() => navigate('/symbols')}
className="p-2 hover:bg-surface-tertiary transition-colors"
aria-label="Back to symbols"
>
<ArrowLeftIcon className="h-5 w-5 text-text-secondary" />
</button>
<div className="px-2">
<h1 className="text-sm font-bold text-text-primary flex items-center gap-1">
{symbolData.symbol}
<span className="text-xs font-normal text-text-secondary">
{symbolData.exchange}
</span>
</h1>
<p className="text-xs text-text-secondary">{symbolData.name}</p>
</div>
{/* Tabs */}
<nav className="flex ml-4" aria-label="Tabs">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`
whitespace-nowrap py-2 px-3 border-b-2 font-medium text-sm
${activeTab === tab.id
? 'border-primary-500 text-primary-500'
: 'border-transparent text-text-secondary hover:text-text-primary hover:border-border'
}
`}
>
{tab.name}
</button>
))}
</nav>
</div>
</div>
</div>
{/* Tab Content */}
<div className="flex-1 overflow-auto">
{renderTabContent()}
</div>
</div>
);
}