TradingAgents/web/frontend/src/pages/Dashboard/Dashboard.js

354 lines
9.3 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { Card, Row, Col, Statistic, Typography, Button, Table, Tag, Space } from 'antd';
import {
LineChartOutlined,
TrophyOutlined,
ClockCircleOutlined,
RocketOutlined,
PlayCircleOutlined,
EyeOutlined
} from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
import { useWebSocket } from '../../contexts/WebSocketContext';
import { tradingAPI } from '../../services/api';
import styled from 'styled-components';
import Loading from '../../components/Loading/Loading';
const { Title, Text } = Typography;
const DashboardContainer = styled.div`
max-width: 1200px;
margin: 0 auto;
`;
const WelcomeCard = styled(Card)`
background: linear-gradient(135deg, #1890ff 0%, #722ed1 100%);
color: white;
margin-bottom: ${props => props.theme.spacing.lg};
border: none;
.ant-card-body {
padding: ${props => props.theme.spacing.xl};
}
`;
const WelcomeContent = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
@media (max-width: ${props => props.theme.breakpoints.md}) {
flex-direction: column;
text-align: center;
gap: ${props => props.theme.spacing.lg};
}
`;
const WelcomeText = styled.div`
h2 {
color: white !important;
margin-bottom: ${props => props.theme.spacing.sm};
}
p {
color: rgba(255, 255, 255, 0.85);
font-size: ${props => props.theme.typography.fontSize.lg};
margin: 0;
}
`;
const QuickActions = styled.div`
display: flex;
gap: ${props => props.theme.spacing.md};
@media (max-width: ${props => props.theme.breakpoints.sm}) {
flex-direction: column;
width: 100%;
}
`;
const StatsCard = styled(Card)`
height: 100%;
.ant-statistic-title {
color: ${props => props.theme.colors.textSecondary};
font-weight: ${props => props.theme.typography.fontWeight.medium};
}
.ant-statistic-content {
color: ${props => props.theme.colors.text};
}
`;
const RecentAnalysisCard = styled(Card)`
.ant-card-head-title {
font-weight: ${props => props.theme.typography.fontWeight.semibold};
}
`;
const Dashboard = () => {
const [loading, setLoading] = useState(true);
const [stats, setStats] = useState({
totalAnalyses: 0,
runningAnalyses: 0,
completedAnalyses: 0,
thisMonth: 0
});
const [recentAnalyses, setRecentAnalyses] = useState([]);
const { user } = useAuth();
const { connected, messages } = useWebSocket();
const navigate = useNavigate();
useEffect(() => {
loadDashboardData();
}, []);
const loadDashboardData = async () => {
try {
setLoading(true);
// 분석 기록 가져오기
const historyResponse = await tradingAPI.getAnalysisHistory();
const analyses = historyResponse.data.results || [];
// 실행 중인 분석 가져오기
const runningResponse = await tradingAPI.getRunningAnalyses();
const runningAnalyses = runningResponse.data.results || [];
// 통계 계산
const totalCount = analyses.length;
const runningCount = runningAnalyses.length;
const completedCount = analyses.filter(a => a.status === 'completed').length;
// 이번 달 분석 수
const currentMonth = new Date().getMonth();
const currentYear = new Date().getFullYear();
const thisMonthCount = analyses.filter(analysis => {
const analysisDate = new Date(analysis.created_at);
return analysisDate.getMonth() === currentMonth &&
analysisDate.getFullYear() === currentYear;
}).length;
setStats({
totalAnalyses: totalCount,
runningAnalyses: runningCount,
completedAnalyses: completedCount,
thisMonth: thisMonthCount
});
// 최근 분석 5개만 표시
setRecentAnalyses(analyses.slice(0, 5));
} catch (error) {
console.error('대시보드 데이터 로드 실패:', error);
} finally {
setLoading(false);
}
};
const getStatusColor = (status) => {
const colors = {
pending: 'orange',
running: 'blue',
completed: 'green',
failed: 'red',
cancelled: 'default'
};
return colors[status] || 'default';
};
const getStatusText = (status) => {
const texts = {
pending: '대기 중',
running: '실행 중',
completed: '완료',
failed: '실패',
cancelled: '취소됨'
};
return texts[status] || status;
};
const columns = [
{
title: '종목',
dataIndex: 'ticker',
key: 'ticker',
render: (ticker) => <strong>{ticker}</strong>
},
{
title: '분석 날짜',
dataIndex: 'analysis_date',
key: 'analysis_date',
},
{
title: '상태',
dataIndex: 'status',
key: 'status',
render: (status) => (
<Tag color={getStatusColor(status)}>
{getStatusText(status)}
</Tag>
)
},
{
title: '생성일',
dataIndex: 'created_at',
key: 'created_at',
render: (date) => new Date(date).toLocaleDateString('ko-KR')
},
{
title: '작업',
key: 'actions',
render: (_, record) => (
<Space>
<Button
type="link"
icon={<EyeOutlined />}
onClick={() => navigate(`/history`)}
>
보기
</Button>
</Space>
)
}
];
if (loading) {
return <Loading text="대시보드를 로드하는 중..." />;
}
return (
<DashboardContainer>
{/* 환영 메시지 */}
<WelcomeCard>
<WelcomeContent>
<WelcomeText>
<Title level={2}>
안녕하세요, {user?.first_name || user?.username}! 👋
</Title>
<Text>
AI 기반 거래 분석으로 나은 투자 결정을 내려보세요.
</Text>
</WelcomeText>
<QuickActions>
<Button
type="primary"
size="large"
icon={<RocketOutlined />}
onClick={() => navigate('/analysis')}
style={{
background: 'rgba(255, 255, 255, 0.2)',
borderColor: 'rgba(255, 255, 255, 0.4)',
color: 'white'
}}
>
분석 시작
</Button>
<Button
size="large"
icon={<EyeOutlined />}
onClick={() => navigate('/history')}
style={{
background: 'rgba(255, 255, 255, 0.1)',
borderColor: 'rgba(255, 255, 255, 0.3)',
color: 'white'
}}
>
분석 기록
</Button>
</QuickActions>
</WelcomeContent>
</WelcomeCard>
{/* 통계 카드들 */}
<Row gutter={[16, 16]} style={{ marginBottom: '24px' }}>
<Col xs={24} sm={12} lg={6}>
<StatsCard>
<Statistic
title="총 분석 수"
value={stats.totalAnalyses}
prefix={<LineChartOutlined />}
/>
</StatsCard>
</Col>
<Col xs={24} sm={12} lg={6}>
<StatsCard>
<Statistic
title="실행 중"
value={stats.runningAnalyses}
prefix={<PlayCircleOutlined />}
valueStyle={{ color: '#1890ff' }}
/>
</StatsCard>
</Col>
<Col xs={24} sm={12} lg={6}>
<StatsCard>
<Statistic
title="완료된 분석"
value={stats.completedAnalyses}
prefix={<TrophyOutlined />}
valueStyle={{ color: '#52c41a' }}
/>
</StatsCard>
</Col>
<Col xs={24} sm={12} lg={6}>
<StatsCard>
<Statistic
title="이번 달"
value={stats.thisMonth}
prefix={<ClockCircleOutlined />}
/>
</StatsCard>
</Col>
</Row>
{/* 최근 분석 */}
<RecentAnalysisCard
title="최근 분석"
extra={
<Button type="link" onClick={() => navigate('/history')}>
모두 보기
</Button>
}
>
{recentAnalyses.length > 0 ? (
<Table
columns={columns}
dataSource={recentAnalyses}
pagination={false}
rowKey="id"
size="small"
/>
) : (
<div style={{ textAlign: 'center', padding: '40px' }}>
<Text type="secondary">아직 분석 기록이 없습니다.</Text>
<br />
<Button
type="primary"
style={{ marginTop: '16px' }}
onClick={() => navigate('/analysis')}
>
번째 분석 시작하기
</Button>
</div>
)}
</RecentAnalysisCard>
{/* WebSocket 연결 상태 정보 */}
{!connected && (
<Card
style={{ marginTop: '16px', borderColor: '#ff4d4f' }}
size="small"
>
<Text type="danger">
실시간 업데이트 연결이 끊어졌습니다. 일부 기능이 제한될 있습니다.
</Text>
</Card>
)}
</DashboardContainer>
);
};
export default Dashboard;