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.
284 lines
12 KiB
TypeScript
284 lines
12 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import dynamic from 'next/dynamic';
|
|
import Layout from '@/components/Layout';
|
|
import { TrendingUp, Database, BarChart3, Users, Calendar, DollarSign } from 'lucide-react';
|
|
import { stockApi, DatabaseStats, formatNumber } from '@/lib/api';
|
|
|
|
const Dashboard: React.FC = () => {
|
|
const [stats, setStats] = useState<DatabaseStats | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const fetchStats = async () => {
|
|
try {
|
|
const data = await stockApi.getDatabaseStats();
|
|
setStats(data);
|
|
} catch (error) {
|
|
console.error('Failed to fetch database stats:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchStats();
|
|
}, []);
|
|
|
|
const quickStats = [
|
|
{
|
|
name: '등록된 회사',
|
|
value: stats?.companies.total || 0,
|
|
icon: Users,
|
|
color: 'text-blue-600',
|
|
bgColor: 'bg-blue-50',
|
|
description: '데이터베이스에 등록된 총 회사 수',
|
|
},
|
|
{
|
|
name: '재무 데이터',
|
|
value: stats?.financial_data.total_records || 0,
|
|
icon: BarChart3,
|
|
color: 'text-green-600',
|
|
bgColor: 'bg-green-50',
|
|
description: '저장된 재무 데이터 레코드 수',
|
|
},
|
|
{
|
|
name: '주가 데이터',
|
|
value: stats?.price_data.total_records || 0,
|
|
icon: TrendingUp,
|
|
color: 'text-purple-600',
|
|
bgColor: 'bg-purple-50',
|
|
description: '저장된 주가 데이터 포인트 수',
|
|
},
|
|
{
|
|
name: '계산된 지표',
|
|
value: stats?.calculated_metrics.total_records || 0,
|
|
icon: Calendar,
|
|
color: 'text-orange-600',
|
|
bgColor: 'bg-orange-50',
|
|
description: '계산된 재무 지표 수',
|
|
},
|
|
];
|
|
|
|
return (
|
|
<Layout title="Stock Oracle - 대시보드">
|
|
<div className="space-y-6">
|
|
{/* 환영 섹션 */}
|
|
<div className="bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg shadow-sm p-8 text-white">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold mb-2">Stock Oracle에 오신 것을 환영합니다</h1>
|
|
<p className="text-blue-100 text-lg">
|
|
실시간 주식 데이터와 재무 분석 플랫폼
|
|
</p>
|
|
<p className="text-blue-200 mt-2">
|
|
SEC 실제 데이터를 기반으로 한 정확한 재무 분석을 제공합니다.
|
|
</p>
|
|
</div>
|
|
<div className="hidden md:block">
|
|
<TrendingUp className="h-24 w-24 text-blue-200" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 빠른 통계 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{quickStats.map((stat) => {
|
|
const Icon = stat.icon;
|
|
return (
|
|
<div key={stat.name} className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow">
|
|
<div className="flex items-center">
|
|
<div className={`flex-shrink-0 p-3 rounded-lg ${stat.bgColor}`}>
|
|
<Icon className={`h-6 w-6 ${stat.color}`} />
|
|
</div>
|
|
<div className="ml-4 flex-1">
|
|
<p className="text-sm font-medium text-gray-500">{stat.name}</p>
|
|
<div className="text-2xl font-bold text-gray-900">
|
|
{loading ? (
|
|
<span className="inline-block h-6 w-16 bg-gray-200 rounded animate-pulse"></span>
|
|
) : (
|
|
<span>{formatNumber(stat.value)}</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="text-xs text-gray-600 mt-2">{stat.description}</p>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* 데이터 품질 개요 */}
|
|
{stats && (
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* 재무 데이터 품질 */}
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
|
|
<BarChart3 className="h-5 w-5 mr-2 text-green-600" />
|
|
재무 데이터 품질
|
|
</h3>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<div className="flex justify-between items-center mb-2">
|
|
<span className="text-sm text-gray-600">실제 데이터 비율</span>
|
|
<span className="text-sm font-medium text-green-600">
|
|
{((stats.financial_data.real_data / stats.financial_data.total_records) * 100).toFixed(1)}%
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-gray-200 rounded-full h-3">
|
|
<div
|
|
className="bg-green-500 h-3 rounded-full transition-all duration-500"
|
|
style={{ width: `${(stats.financial_data.real_data / stats.financial_data.total_records) * 100}%` }}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4 mt-4">
|
|
<div className="text-center p-3 bg-green-50 rounded-lg">
|
|
<p className="text-2xl font-bold text-green-600">{formatNumber(stats.financial_data.real_data)}</p>
|
|
<p className="text-sm text-green-700">실제 데이터</p>
|
|
</div>
|
|
<div className="text-center p-3 bg-yellow-50 rounded-lg">
|
|
<p className="text-2xl font-bold text-yellow-600">{formatNumber(stats.financial_data.estimated_data)}</p>
|
|
<p className="text-sm text-yellow-700">추정 데이터</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 커버리지 정보 */}
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
|
|
<Database className="h-5 w-5 mr-2 text-blue-600" />
|
|
데이터 커버리지
|
|
</h3>
|
|
|
|
<div className="space-y-4">
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-gray-600">재무 데이터 보유 회사</span>
|
|
<span className="text-lg font-semibold text-gray-900">
|
|
{stats.companies.with_financial_data} / {stats.companies.total}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-gray-600">주가 데이터 보유 회사</span>
|
|
<span className="text-lg font-semibold text-gray-900">
|
|
{stats.companies.with_price_data} / {stats.companies.total}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="mt-4">
|
|
<p className="text-sm text-gray-600 mb-2">주가 데이터 보유 종목</p>
|
|
<div className="flex flex-wrap gap-1">
|
|
{stats.price_data.tickers.slice(0, 8).map((ticker) => (
|
|
<span key={ticker} className="inline-flex px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 rounded">
|
|
{ticker}
|
|
</span>
|
|
))}
|
|
{stats.price_data.tickers.length > 8 && (
|
|
<span className="inline-flex px-2 py-1 text-xs font-medium bg-gray-100 text-gray-600 rounded">
|
|
+{stats.price_data.tickers.length - 8}개 더
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 빠른 액션 */}
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">빠른 액션</h3>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<a
|
|
href="/stock"
|
|
className="flex items-center p-4 border border-gray-200 rounded-lg hover:border-orange-300 hover:bg-orange-50 transition-colors group"
|
|
>
|
|
<TrendingUp className="h-8 w-8 text-orange-600 group-hover:text-orange-700" />
|
|
<div className="ml-3">
|
|
<p className="text-sm font-medium text-gray-900">종목 조회</p>
|
|
<p className="text-xs text-gray-500">개별 종목 상세 정보</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a
|
|
href="/query"
|
|
className="flex items-center p-4 border border-gray-200 rounded-lg hover:border-blue-300 hover:bg-blue-50 transition-colors group"
|
|
>
|
|
<BarChart3 className="h-8 w-8 text-blue-600 group-hover:text-blue-700" />
|
|
<div className="ml-3">
|
|
<p className="text-sm font-medium text-gray-900">데이터 조회</p>
|
|
<p className="text-xs text-gray-500">주식 재무 데이터 검색</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a
|
|
href="/database"
|
|
className="flex items-center p-4 border border-gray-200 rounded-lg hover:border-green-300 hover:bg-green-50 transition-colors group"
|
|
>
|
|
<Database className="h-8 w-8 text-green-600 group-hover:text-green-700" />
|
|
<div className="ml-3">
|
|
<p className="text-sm font-medium text-gray-900">DB 상태</p>
|
|
<p className="text-xs text-gray-500">데이터베이스 현황 확인</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a
|
|
href="/fred"
|
|
className="flex items-center p-4 border border-gray-200 rounded-lg hover:border-purple-300 hover:bg-purple-50 transition-colors group"
|
|
>
|
|
<DollarSign className="h-8 w-8 text-purple-600 group-hover:text-purple-700" />
|
|
<div className="ml-3">
|
|
<p className="text-sm font-medium text-gray-900">FRED 경제데이터</p>
|
|
<p className="text-xs text-gray-500">연방준비제도 경제 지표</p>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 시스템 상태 */}
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">시스템 상태</h3>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg">
|
|
<div className="flex items-center">
|
|
<div className="h-3 w-3 bg-green-400 rounded-full mr-2"></div>
|
|
<span className="text-sm font-medium text-green-800">API 서버</span>
|
|
</div>
|
|
<span className="text-xs text-green-600">정상</span>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg">
|
|
<div className="flex items-center">
|
|
<div className="h-3 w-3 bg-green-400 rounded-full mr-2"></div>
|
|
<span className="text-sm font-medium text-green-800">데이터베이스</span>
|
|
</div>
|
|
<span className="text-xs text-green-600">연결됨</span>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg">
|
|
<div className="flex items-center">
|
|
<div className="h-3 w-3 bg-green-400 rounded-full mr-2"></div>
|
|
<span className="text-sm font-medium text-green-800">외부 API</span>
|
|
</div>
|
|
<span className="text-xs text-green-600">활성</span>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg">
|
|
<div className="flex items-center">
|
|
<div className="h-3 w-3 bg-blue-400 rounded-full mr-2"></div>
|
|
<span className="text-sm font-medium text-blue-800">FRED API</span>
|
|
</div>
|
|
<span className="text-xs text-blue-600">연결됨</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Layout>
|
|
);
|
|
};
|
|
|
|
export default Dashboard; |