You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

797 lines
36 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import Layout from '@/components/Layout';
import NewsSocialDisplay from '@/components/NewsSocialDisplay';
import {
Search,
TrendingUp,
BarChart3,
Building,
Calendar,
DollarSign,
Percent,
Users,
AlertCircle,
RefreshCw,
ChevronDown,
ChevronUp,
TrendingDown,
Activity,
PieChart,
LineChart,
Eye,
BarChart,
Target,
Layers,
Shield,
Award,
Briefcase,
Calculator
} from 'lucide-react';
import {
stockApi,
Company,
FinancialData,
PriceData,
formatCurrency,
formatNumber,
formatPercentage,
formatDate
} from '@/lib/api';
import dynamic from 'next/dynamic';
const Plot = dynamic(() => import('react-plotly.js'), { ssr: false });
interface StockDetailPageProps {}
type ChartPeriod = '1D' | '5D' | '1M' | '3M' | '6M' | '1Y' | '2Y' | '5Y' | '10Y';
const StockDetailPage: React.FC<StockDetailPageProps> = () => {
const [ticker, setTicker] = useState<string>('');
const [searchTicker, setSearchTicker] = useState<string>('');
const [company, setCompany] = useState<Company | null>(null);
const [financialData, setFinancialData] = useState<FinancialData[]>([]);
const [priceData, setPriceData] = useState<PriceData[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>('');
const [showAllFinancialData, setShowAllFinancialData] = useState<boolean>(false);
const [periodType, setPeriodType] = useState<'quarterly' | 'annual' | 'all'>('all');
const [chartPeriod, setChartPeriod] = useState<ChartPeriod>('6M');
const [chartType, setChartType] = useState<'line' | 'candlestick'>('line');
const searchStock = async () => {
if (!searchTicker.trim()) {
setError('종목 코드를 입력해주세요');
return;
}
setLoading(true);
setError('');
setTicker(searchTicker.toUpperCase());
try {
// Get date range for price data based on chart selection
const getDateRangeForChart = (period: ChartPeriod): { start_date: string; end_date: string } => {
const today = new Date();
const endDate = today.toISOString().split('T')[0];
let startDate: Date;
switch (period) {
case '1D':
startDate = new Date(today.getTime() - 1 * 24 * 60 * 60 * 1000);
break;
case '5D':
startDate = new Date(today.getTime() - 5 * 24 * 60 * 60 * 1000);
break;
case '1M':
startDate = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000);
break;
case '3M':
startDate = new Date(today.getTime() - 90 * 24 * 60 * 60 * 1000);
break;
case '6M':
startDate = new Date(today.getTime() - 180 * 24 * 60 * 60 * 1000);
break;
case '1Y':
startDate = new Date(today.getTime() - 365 * 24 * 60 * 60 * 1000);
break;
case '2Y':
startDate = new Date(today.getTime() - 2 * 365 * 24 * 60 * 60 * 1000);
break;
case '5Y':
startDate = new Date(today.getTime() - 5 * 365 * 24 * 60 * 60 * 1000);
break;
case '10Y':
startDate = new Date(today.getTime() - 10 * 365 * 24 * 60 * 60 * 1000);
break;
default:
startDate = new Date(today.getTime() - 180 * 24 * 60 * 60 * 1000); // Default to 6M
}
return {
start_date: startDate.toISOString().split('T')[0],
end_date: endDate
};
};
const { start_date, end_date } = getDateRangeForChart(chartPeriod);
// 재무 데이터와 주가 데이터를 동시에 가져오기
const [financialResponse, priceResponse] = await Promise.allSettled([
stockApi.getFinancialData({
ticker: searchTicker.toUpperCase(),
start_date: '2020-01-01',
end_date: new Date().toISOString().split('T')[0],
period_type: periodType,
include_metrics: true
}),
stockApi.getPriceData({
ticker: searchTicker.toUpperCase(),
start_date,
end_date,
interval: '1d'
})
]);
// 재무 데이터 처리
if (financialResponse.status === 'fulfilled') {
console.log('🔍 Debug: Financial data loaded', {
company: financialResponse.value.company,
dataCount: financialResponse.value.financial_data?.length,
firstRecord: financialResponse.value.financial_data?.[0],
lastRecord: financialResponse.value.financial_data?.[financialResponse.value.financial_data.length - 1]
});
setCompany(financialResponse.value.company);
setFinancialData(financialResponse.value.financial_data);
}
// 주가 데이터 처리
if (priceResponse.status === 'fulfilled') {
console.log('🔍 Debug: Price data loaded', {
ticker: priceResponse.value.ticker,
dataCount: priceResponse.value.data?.length,
firstRecord: priceResponse.value.data?.[0],
lastRecord: priceResponse.value.data?.[priceResponse.value.data?.length - 1]
});
setPriceData(priceResponse.value.data || []);
}
// 둘 다 실패한 경우에만 에러 표시
if (financialResponse.status === 'rejected' && priceResponse.status === 'rejected') {
setError(`${searchTicker} 종목에 대한 데이터를 찾을 수 없습니다.`);
}
} catch (err: any) {
setError(err.message || '데이터를 불러오는 중 오류가 발생했습니다.');
} finally {
setLoading(false);
}
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
searchStock();
}
};
// 차트 기간이 변경될 때 데이터 다시 로드
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally only re-fetch when chartPeriod changes
useEffect(() => {
if (ticker) {
searchStock();
}
}, [chartPeriod]);
// Helper function to format chart data
const formatChartData = () => {
if (!priceData || priceData.length === 0) return [];
// Sort by date ascending (oldest to newest) for proper chronological display
const sortedData = [...priceData].sort((a, b) =>
new Date(a.date).getTime() - new Date(b.date).getTime()
);
return sortedData.map(item => ({
date: formatDate(item.date, 'MM/dd'),
price: item.close,
volume: item.volume || 0,
high: item.high,
low: item.low,
open: item.open,
}));
};
// Calculate price change from previous to latest price
const getPriceChange = () => {
if (!priceData || priceData.length < 2) return { change: 0, changePercent: 0 };
// API returns data in ascending order, so latest is last, previous is second to last
const latest = priceData[priceData.length - 1];
const previous = priceData[priceData.length - 2];
const change = latest.close - previous.close;
const changePercent = (change / previous.close) * 100;
return { change, changePercent };
};
// Calculate 52-week range
const get52WeekRange = () => {
if (!priceData || priceData.length === 0) return { low: 0, high: 0 };
const prices = priceData.map(p => p.close);
return {
low: Math.min(...prices),
high: Math.max(...prices)
};
};
// Display financial data with most recent first
const displayedFinancialData = showAllFinancialData
? [...financialData].reverse()
: [...(financialData || [])].reverse().slice(0, 8);
const getLatestPrice = () => {
if (!priceData || priceData.length === 0) {
console.log('🔍 Debug: No price data available', { priceData });
return null;
}
// API returns data in ascending order (oldest first), so get the last element for most recent
const latest = priceData[priceData.length - 1];
console.log('🔍 Debug: Latest price data', { latest, totalRecords: priceData.length });
return latest;
};
const getLatestFinancials = () => {
if (!financialData || financialData.length === 0) {
console.log('🔍 Debug: No financial data available', { financialData });
return null;
}
// Get the most recent financial data (last element if sorted ascending)
const latest = financialData[financialData.length - 1];
console.log('🔍 Debug: Latest financial data', { latest, totalRecords: financialData.length });
return latest;
};
const latestPrice = getLatestPrice();
const latestFinancials = getLatestFinancials();
const chartData = formatChartData();
const priceChange = getPriceChange();
const weekRange = get52WeekRange();
const chartPeriods: ChartPeriod[] = ['1D', '5D', '1M', '3M', '6M', '1Y', '2Y', '5Y', '10Y'];
return (
<Layout title="Stock Analysis - Stock Oracle">
<div className="space-y-6">
{/* 검색 섹션 */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<h1 className="text-2xl font-bold text-gray-900 mb-4 flex items-center">
<Search className="h-6 w-6 mr-2 text-blue-600" />
</h1>
<div className="flex gap-4">
<div className="flex-1">
<label htmlFor="ticker" className="block text-sm font-medium text-gray-700 mb-2">
(: AAPL, TSLA, MSFT)
</label>
<input
type="text"
id="ticker"
value={searchTicker}
onChange={(e) => setSearchTicker(e.target.value.toUpperCase())}
onKeyPress={handleKeyPress}
placeholder="종목 코드를 입력하세요"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<select
value={periodType}
onChange={(e) => setPeriodType(e.target.value as 'quarterly' | 'annual' | 'all')}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
>
<option value="all"></option>
<option value="quarterly"></option>
<option value="annual"></option>
</select>
</div>
<div className="flex items-end">
<button
onClick={searchStock}
disabled={loading}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
{loading ? (
<RefreshCw className="h-4 w-4 animate-spin" />
) : (
<Search className="h-4 w-4" />
)}
</button>
</div>
</div>
{error && (
<div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg flex items-center gap-2">
<AlertCircle className="h-5 w-5 text-red-500" />
<span className="text-red-700">{error}</span>
</div>
)}
</div>
{/* Professional Stock Analysis Dashboard */}
{company && (
<div className="space-y-6">
{/* Premium Company Header */}
<div className="bg-gradient-to-r from-slate-900 to-slate-800 rounded-xl shadow-xl text-white p-8">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-white/10 rounded-lg">
<Building className="h-8 w-8 text-white" />
</div>
<div>
<h1 className="text-4xl font-bold">{company.name}</h1>
<div className="flex items-center gap-3 mt-1">
<span className="text-2xl font-mono text-blue-300">{company.ticker}</span>
{company.sector && (
<span className="text-slate-300 text-lg"> {company.sector}</span>
)}
</div>
</div>
</div>
{/* Live Price Display */}
{latestPrice && (
<div className="flex items-center gap-6 mt-4">
<div className="text-5xl font-bold font-mono">
{formatCurrency(latestPrice.close)}
</div>
<div className={`flex items-center gap-2 px-4 py-2 rounded-lg text-lg font-semibold ${
priceChange.change >= 0
? 'bg-green-500/20 text-green-300'
: 'bg-red-500/20 text-red-300'
}`}>
{priceChange.change >= 0 ? <TrendingUp className="h-5 w-5" /> : <TrendingDown className="h-5 w-5" />}
<span>
{priceChange.change >= 0 ? '+' : ''}{priceChange.change.toFixed(2)}
({priceChange.changePercent >= 0 ? '+' : ''}{priceChange.changePercent.toFixed(2)}%)
</span>
</div>
</div>
)}
</div>
{/* Market Status */}
<div className="text-right">
<div className="flex items-center gap-2 mb-2">
<div className="h-3 w-3 bg-green-400 rounded-full animate-pulse"></div>
<span className="text-green-300 font-medium">Live Data</span>
</div>
{latestPrice && (
<div className="text-slate-300 text-sm">
Last Updated: {formatDate(latestPrice.date)}
</div>
)}
</div>
</div>
</div>
{/* Key Performance Indicators Grid */}
{(latestPrice || latestFinancials) && (
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
{latestPrice && (
<>
<div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
<div className="flex items-center justify-between mb-2">
<Target className="h-5 w-5 text-indigo-500" />
<span className="text-xs text-gray-500 uppercase tracking-wide">52W High</span>
</div>
<div className="text-xl font-bold text-gray-900">{formatCurrency(weekRange.high)}</div>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
<div className="flex items-center justify-between mb-2">
<TrendingDown className="h-5 w-5 text-red-500" />
<span className="text-xs text-gray-500 uppercase tracking-wide">52W Low</span>
</div>
<div className="text-xl font-bold text-gray-900">{formatCurrency(weekRange.low)}</div>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
<div className="flex items-center justify-between mb-2">
<Activity className="h-5 w-5 text-purple-500" />
<span className="text-xs text-gray-500 uppercase tracking-wide">Volume</span>
</div>
<div className="text-xl font-bold text-gray-900">{formatNumber(latestPrice.volume)}</div>
</div>
</>
)}
{latestFinancials && (
<>
<div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
<div className="flex items-center justify-between mb-2">
<Calculator className="h-5 w-5 text-blue-500" />
<span className="text-xs text-gray-500 uppercase tracking-wide">P/E Ratio</span>
</div>
<div className="text-xl font-bold text-gray-900">
{latestFinancials.eps && latestPrice ?
(latestPrice.close / latestFinancials.eps).toFixed(2) : 'N/A'
}
</div>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
<div className="flex items-center justify-between mb-2">
<Award className="h-5 w-5 text-green-500" />
<span className="text-xs text-gray-500 uppercase tracking-wide">ROE</span>
</div>
<div className="text-xl font-bold text-gray-900">{formatPercentage(latestFinancials.roe)}</div>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
<div className="flex items-center justify-between mb-2">
<Percent className="h-5 w-5 text-orange-500" />
<span className="text-xs text-gray-500 uppercase tracking-wide">Margin</span>
</div>
<div className="text-xl font-bold text-gray-900">{formatPercentage(latestFinancials.net_margin)}</div>
</div>
</>
)}
</div>
)}
{/* Main Dashboard Grid */}
<div className="grid lg:grid-cols-3 gap-6">
{/* Chart Section - Takes 2/3 width */}
<div className="lg:col-span-2 space-y-6">
{/* Stock Chart */}
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-50 rounded-lg">
<BarChart className="h-6 w-6 text-blue-600" />
</div>
<h2 className="text-xl font-bold text-gray-900"> </h2>
</div>
<div className="flex items-center gap-4">
{/* Period Selector */}
<div className="flex bg-gray-50 rounded-lg p-1">
{chartPeriods.map((period) => (
<button
key={period}
onClick={() => setChartPeriod(period)}
className={`px-3 py-1.5 text-sm font-medium rounded-md transition-all ${
chartPeriod === period
? 'bg-white text-blue-600 shadow-sm ring-1 ring-blue-200'
: 'text-gray-600 hover:text-gray-900'
}`}
>
{period}
</button>
))}
</div>
</div>
</div>
{/* Chart Display */}
<div className="bg-gray-50 rounded-xl p-4 border">
<Plot
data={[
{
x: chartData.map((d:any) => d.date),
y: chartData.map((d:any) => d.price),
type: 'scatter',
mode: 'lines',
name: '주가',
line: { color: '#10b981', width: 3 },
yaxis: 'y1',
},
{
x: chartData.map((d:any) => d.date),
y: chartData.map((d:any) => d.volume),
type: 'bar',
name: '거래량',
marker: { color: 'rgba(59,130,246,0.4)' },
yaxis: 'y2',
},
]}
layout={{
autosize: true,
height: 400,
margin: { t: 20, r: 20, b: 40, l: 40 },
paper_bgcolor: 'rgba(0,0,0,0)',
plot_bgcolor: 'rgba(0,0,0,0)',
xaxis: { title: '날짜', tickfont: { size: 12 } },
yaxis: { title: '주가', titlefont: { size: 12 }, tickprefix: '$' },
yaxis2: {
title: '거래량',
overlaying: 'y',
side: 'right',
showgrid: false,
},
legend: { orientation: 'h', y: -0.2 },
}}
style={{ width: '100%', height: '100%' }}
config={{ displayModeBar: false, responsive: true }}
/>
</div>
</div>
{/* Financial Trend Chart */}
{financialData.length > 1 && (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div className="flex items-center gap-3 mb-6">
<div className="p-2 bg-green-50 rounded-lg">
<TrendingUp className="h-6 w-6 text-green-600" />
</div>
<h2 className="text-xl font-bold text-gray-900"> </h2>
</div>
<div className="bg-gray-50 rounded-xl p-4 border">
<Plot
data={(() => {
const sorted = [...financialData]
.slice(0, 8)
.sort((a, b) => new Date(a.period_date).getTime() - new Date(b.period_date).getTime());
const x = sorted.map((d) => formatDate(d.period_date, 'MM/yy'));
return [
{
x,
y: sorted.map((d) => d.revenue ?? null),
type: 'scatter',
mode: 'lines+markers',
name: '매출액',
line: { color: '#3b82f6', width: 3 },
marker: { color: '#3b82f6' },
},
{
x,
y: sorted.map((d) => d.net_income ?? null),
type: 'scatter',
mode: 'lines+markers',
name: '순이익',
line: { color: '#10b981', width: 3 },
marker: { color: '#10b981' },
},
];
})()}
layout={{
autosize: true,
height: 300,
margin: { t: 10, r: 20, b: 40, l: 50 },
paper_bgcolor: 'rgba(0,0,0,0)',
plot_bgcolor: 'rgba(0,0,0,0)',
xaxis: { title: '기간', tickfont: { size: 12 } },
yaxis: { title: '금액', tickprefix: '$' },
legend: { orientation: 'h', y: -0.2 },
}}
style={{ width: '100%', height: '100%' }}
config={{ displayModeBar: false, responsive: true }}
/>
</div>
</div>
)}
</div>
{/* Sidebar - Takes 1/3 width */}
<div className="space-y-6">
{/* Company Overview */}
{company.business_description && (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-purple-50 rounded-lg">
<Briefcase className="h-5 w-5 text-purple-600" />
</div>
<h3 className="text-lg font-bold text-gray-900"> </h3>
</div>
<p className="text-gray-700 leading-relaxed text-sm">{company.business_description}</p>
</div>
)}
{/* Key Financial Metrics */}
{latestFinancials && (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-blue-50 rounded-lg">
<BarChart3 className="h-5 w-5 text-blue-600" />
</div>
<h3 className="text-lg font-bold text-gray-900"> </h3>
</div>
<div className="space-y-4">
<div className="flex justify-between items-center p-3 bg-blue-50 rounded-lg">
<span className="text-blue-700 font-medium"></span>
<span className="font-bold text-blue-900">{formatCurrency(latestFinancials.revenue)}</span>
</div>
<div className="flex justify-between items-center p-3 bg-green-50 rounded-lg">
<span className="text-green-700 font-medium"></span>
<span className="font-bold text-green-900">{formatCurrency(latestFinancials.net_income)}</span>
</div>
<div className="flex justify-between items-center p-3 bg-purple-50 rounded-lg">
<span className="text-purple-700 font-medium"> </span>
<span className="font-bold text-purple-900">{formatCurrency(latestFinancials.total_assets)}</span>
</div>
<div className="flex justify-between items-center p-3 bg-orange-50 rounded-lg">
<span className="text-orange-700 font-medium">EPS</span>
<span className="font-bold text-orange-900">
{latestFinancials.eps ? `$${latestFinancials.eps.toFixed(2)}` : 'N/A'}
</span>
</div>
</div>
</div>
)}
{/* Price Statistics */}
{latestPrice && (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-indigo-50 rounded-lg">
<DollarSign className="h-5 w-5 text-indigo-600" />
</div>
<h3 className="text-lg font-bold text-gray-900"> </h3>
</div>
<div className="space-y-3">
<div className="flex justify-between">
<span className="text-gray-600"></span>
<span className="font-semibold">{formatCurrency(latestPrice.open)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600"></span>
<span className="font-semibold">{formatCurrency(latestPrice.high)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600"></span>
<span className="font-semibold">{formatCurrency(latestPrice.low)}</span>
</div>
<div className="flex justify-between border-t pt-3">
<span className="text-gray-600"></span>
<span className="font-semibold">{formatNumber(latestPrice.volume)}</span>
</div>
</div>
</div>
)}
{/* Financial Ratios */}
{latestFinancials && (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-emerald-50 rounded-lg">
<Calculator className="h-5 w-5 text-emerald-600" />
</div>
<h3 className="text-lg font-bold text-gray-900"> </h3>
</div>
<div className="space-y-3">
<div className="flex justify-between">
<span className="text-gray-600">ROA</span>
<span className="font-semibold">{formatPercentage(latestFinancials.roa)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600"></span>
<span className="font-semibold">{formatPercentage(latestFinancials.net_margin)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600"></span>
<span className="font-semibold">
{latestFinancials.total_debt && latestFinancials.total_assets ?
formatPercentage((latestFinancials.total_debt / latestFinancials.total_assets) * 100) : 'N/A'
}
</span>
</div>
</div>
</div>
)}
</div>
</div>
{/* Financial Data Table */}
{financialData && financialData.length > 0 && (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<div className="p-2 bg-slate-50 rounded-lg">
<Layers className="h-6 w-6 text-slate-600" />
</div>
<h2 className="text-xl font-bold text-gray-900"> </h2>
<span className="bg-gray-100 text-gray-600 px-3 py-1 rounded-full text-sm font-medium">
{financialData.length}
</span>
</div>
{financialData.length > 8 && (
<button
onClick={() => setShowAllFinancialData(!showAllFinancialData)}
className="flex items-center gap-2 text-blue-600 hover:text-blue-800 font-medium"
>
{showAllFinancialData ? (
<> <ChevronUp className="h-4 w-4" /></>
) : (
<> <ChevronDown className="h-4 w-4" /></>
)}
</button>
)}
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"></th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"></th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"></th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"></th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">EPS</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">ROE</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"></th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-100">
{displayedFinancialData.map((data, index) => (
<tr key={index} className="hover:bg-gray-50 transition-colors">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{formatDate(data.period_date)}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`px-3 py-1 text-xs font-semibold rounded-full ${
data.period_type === 'quarterly'
? 'bg-blue-100 text-blue-800 border border-blue-200'
: 'bg-green-100 text-green-800 border border-green-200'
}`}>
{data.period_type === 'quarterly' ? '분기' : '연간'}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-semibold text-gray-900">
{formatCurrency(data.revenue)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-semibold text-gray-900">
{formatCurrency(data.net_income)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-semibold text-gray-900">
{data.eps ? `$${data.eps.toFixed(2)}` : 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-semibold text-gray-900">
{formatPercentage(data.roe)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-semibold text-gray-900">
{formatPercentage(data.net_margin)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
)}
{/* 뉴스 및 소셜 미디어 섹션 */}
{ticker && (
<NewsSocialDisplay ticker={ticker} />
)}
{/* 데이터 없음 메시지 */}
{ticker && !loading && !company && (!financialData || financialData.length === 0) && (!priceData || priceData.length === 0) && (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8 text-center">
<AlertCircle className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2"> </h3>
<p className="text-gray-500">
"{ticker}" . .
</p>
</div>
)}
</div>
</Layout>
);
};
export default StockDetailPage;