221 lines
No EOL
8.7 KiB
TypeScript
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>
|
|
);
|
|
} |