Files
goldenChart/frontend_golden/src/App.tsx
T
2026-05-23 15:11:48 +09:00

524 lines
19 KiB
TypeScript

import React, { useState } from 'react';
import {
ThemeProvider,
CssBaseline,
Box,
AppBar,
Toolbar,
Typography,
Button,
Container,
IconButton,
Tooltip,
Chip,
Menu,
MenuItem,
ListItemIcon,
ListItemText,
Divider,
Badge,
} from '@mui/material';
import {
Dashboard as DashboardIcon,
ShowChart,
CloudDownload,
Schedule,
Settings as SettingsIcon,
Analytics,
LightMode,
DarkMode,
Login as LoginIcon,
Logout as LogoutIcon,
AccountCircle,
Person,
Rule as RuleIcon,
AccountBalance as SimulationIcon,
TrendingUp as TrendingUpIcon,
Fullscreen,
FullscreenExit,
Notifications as NotificationsIcon,
ListAlt as OrderbookIcon,
FormatListBulleted as AlertListIcon,
AutoAwesome as AutoTradingIcon,
GridView as MultiChartIcon,
ClearAll as ClearAllIcon,
CompareArrows as CompareIcon,
} from '@mui/icons-material';
import { useAppTheme } from './contexts/ThemeContext';
import { useIndicatorPopup } from './contexts/IndicatorPopupContext';
import { useAuth } from './contexts/AuthContext';
import { useAlertNotification } from './contexts/AlertNotificationContext';
import { useNavigation } from './contexts/NavigationContext';
import { useIsMobile } from './hooks/useIsMobile';
import { MobileAppShell } from './components/MobileAppShell';
import DashboardPage from './pages/DashboardPage';
import PatternSearchPage from './pages/PatternSearchPage';
import RisingStocksPage from './pages/RisingStocksPage';
import StrategyTreeBuilderPage from './pages/StrategyTreeBuilderPage';
import SimulationPage from './pages/SimulationPage';
import DataCollectionPage from './pages/DataCollectionPage';
import SchedulerPage from './pages/SchedulerPage';
import SettingsPage from './pages/SettingsPage';
import CandlestickTestPage from './pages/CandlestickTestPage';
import CandlestickTestPage3 from './pages/CandlestickTestPage3';
import AlertPage from './pages/AlertPage';
import NotificationListPage from './pages/NotificationListPage';
import OrderbookPage from './pages/OrderbookPage';
import AutoTradingPage from './pages/AutoTradingPage';
import MultiChartPage from './pages/MultiChartPage';
import MultiChartPage2 from './pages/MultiChartPage2';
import AlertListPage from './pages/AlertListPage';
import ChartValidationPage from './pages/admin/ChartValidationPage';
import LoginDialog from './components/LoginDialog';
import SignUpDialog from './components/SignUpDialog';
import IndicatorConfigPanel from './components/IndicatorConfigPanel';
import IndicatorPopupWindow from './components/IndicatorPopupWindow';
import AlertSnackbar from './components/alert/AlertSnackbar';
interface MenuItemType {
shortText: string;
fullText: string;
icon: React.ReactNode;
page: string;
}
const menuItems: MenuItemType[] = [
{ shortText: '대시보드', fullText: '대시보드', icon: <DashboardIcon />, page: 'dashboard' },
{ shortText: '멀티차트', fullText: '멀티차트', icon: <MultiChartIcon />, page: 'multi-chart' },
{ shortText: '멀티2', fullText: '멀티차트2', icon: <MultiChartIcon />, page: 'multi-chart-2' },
{ shortText: '추세', fullText: '상승종목 추세분석', icon: <TrendingUpIcon />, page: 'rising-stocks' },
{ shortText: '패턴', fullText: '패턴검색', icon: <ShowChart />, page: 'pattern-search' },
{ shortText: '투자', fullText: '모의투자', icon: <SimulationIcon />, page: 'simulation' },
{ shortText: '전략', fullText: '전략설정', icon: <RuleIcon />, page: 'strategy-tree' },
{ shortText: '분석', fullText: '투자분석', icon: <Analytics />, page: 'candlestick-test' },
{ shortText: '분석3', fullText: '투자분석3 (프론트엔드 지표)', icon: <Analytics />, page: 'candlestick-test-3' },
{ shortText: '알림', fullText: '알림 설정', icon: <NotificationsIcon />, page: 'alerts' },
{ shortText: '알림목록', fullText: '알림목록', icon: <AlertListIcon />, page: 'alert-list' },
{ shortText: '호가', fullText: '실시간 호가', icon: <OrderbookIcon />, page: 'orderbook' },
{ shortText: '자동매매', fullText: '자동매매', icon: <AutoTradingIcon />, page: 'auto-trading' },
{ shortText: '검증', fullText: '차트 비교검증', icon: <CompareIcon />, page: 'chart-validation' },
{ shortText: '데이터', fullText: '데이터 수집', icon: <CloudDownload />, page: 'data-collection' },
{ shortText: '스케쥴', fullText: '스케쥴러', icon: <Schedule />, page: 'scheduler' },
{ shortText: '설정', fullText: '설정', icon: <SettingsIcon />, page: 'settings' },
];
function MainContent() {
const isMobile = useIsMobile();
const { currentPage, navigateToPage } = useNavigation();
const { themeMode, setThemeMode, theme } = useAppTheme();
const { isAuthenticated, user, logout } = useAuth();
const { isPopupOpen, closePopup, chartData, chartMargin } = useIndicatorPopup();
const { unreadCount, notifications, dismissAllNotifications } = useAlertNotification();
// 로그인/회원가입 다이얼로그 상태
const [loginDialogOpen, setLoginDialogOpen] = useState(false);
const [signUpDialogOpen, setSignUpDialogOpen] = useState(false);
// 사용자 메뉴 상태
const [userMenuAnchor, setUserMenuAnchor] = useState<null | HTMLElement>(null);
// 전체화면 상태
const [isFullscreen, setIsFullscreen] = useState(false);
const handleUserMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setUserMenuAnchor(event.currentTarget);
};
const handleUserMenuClose = () => {
setUserMenuAnchor(null);
};
const handleLogout = () => {
logout();
handleUserMenuClose();
};
const renderPage = () => {
// 투자분석/투자분석3: 상태 유지를 위해 항상 마운트하되 css display로 노출 여부 제어
if (currentPage === 'candlestick-test') return null;
if (currentPage === 'candlestick-test-3') return null;
switch (currentPage) {
case 'dashboard':
return <DashboardPage />;
case 'multi-chart':
return <MultiChartPage />;
case 'multi-chart-2':
return <MultiChartPage2 />;
case 'rising-stocks':
return <RisingStocksPage />;
case 'pattern-search':
return <PatternSearchPage />;
case 'simulation':
return <SimulationPage />;
case 'strategy-tree':
return <StrategyTreeBuilderPage />;
case 'data-collection':
return <DataCollectionPage />;
case 'scheduler':
return <SchedulerPage />;
case 'alerts':
return <AlertPage />;
case 'alert-list':
return <AlertListPage />;
case 'notifications':
return <NotificationListPage />;
case 'orderbook':
return <OrderbookPage />;
case 'auto-trading':
return <AutoTradingPage />;
case 'chart-validation':
return <ChartValidationPage />;
case 'settings':
return <SettingsPage />;
default:
return <DashboardPage />;
}
};
const handleThemeToggle = () => {
setThemeMode(themeMode === 'dark' ? 'light' : 'dark');
};
// 전체화면 토글 함수
const handleFullscreenToggle = () => {
if (!document.fullscreenElement) {
// 전체화면으로 전환
document.documentElement.requestFullscreen().catch((err) => {
console.error('전체화면 전환 실패:', err);
});
} else {
// 전체화면 해제
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
};
// 전체화면 상태 변경 감지
React.useEffect(() => {
const handleFullscreenChange = () => {
setIsFullscreen(!!document.fullscreenElement);
};
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => {
document.removeEventListener('fullscreenchange', handleFullscreenChange);
};
}, []);
// 다크 모드 AppBar 스타일
const darkAppBarStyle = {
background: 'linear-gradient(90deg, #0a0a0f 0%, #12121a 50%, #1a1a2e 100%)',
borderBottom: '1px solid rgba(100, 149, 237, 0.2)',
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.3)',
};
// 라이트 모드 AppBar 스타일
const lightAppBarStyle = {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.15)',
};
// 모바일 접속 시 모바일 앱 UI (전체 화면 모바일 최적화)
if (isMobile) {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<MobileAppShell />
</ThemeProvider>
);
}
// 데스크톱: 기존 레이아웃 유지
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
{/* Top Navigation Bar */}
<AppBar
position="fixed"
sx={{
...(themeMode === 'dark' ? darkAppBarStyle : lightAppBarStyle),
zIndex: (theme: any) => theme.zIndex.drawer + 100,
}}
>
<Toolbar sx={{ justifyContent: 'space-between' }}>
{/* Logo & Title */}
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Analytics sx={{ fontSize: 32, color: themeMode === 'dark' ? 'primary.main' : '#ffffff', mr: 1.5 }} />
<Typography
variant="h6"
noWrap
component="div"
sx={{
color: themeMode === 'dark' ? 'primary.main' : '#ffffff',
fontWeight: 700,
letterSpacing: '0.5px',
}}
>
Golden Analysis
</Typography>
{/* <Typography
variant="body2"
sx={{
ml: 2,
color: themeMode === 'dark' ? 'text.secondary' : 'rgba(255, 255, 255, 0.8)',
display: { xs: 'none', sm: 'block' },
}}
>
투자전략 성능 분석 시스템
</Typography> */}
</Box>
{/* Navigation Menu */}
<Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center' }}>
{menuItems.map((item) => (
<Tooltip
key={item.page}
title={item.fullText}
arrow
placement="bottom"
enterDelay={200}
leaveDelay={0}
>
<IconButton
onClick={() => navigateToPage(item.page)}
sx={{
color: themeMode === 'dark'
? (currentPage === item.page ? 'primary.main' : 'text.secondary')
: (currentPage === item.page ? '#ffffff' : 'rgba(255, 255, 255, 0.7)'),
backgroundColor: currentPage === item.page
? (themeMode === 'dark' ? 'rgba(100, 149, 237, 0.15)' : 'rgba(255, 255, 255, 0.2)')
: 'transparent',
borderBottom: currentPage === item.page ? '2px solid' : '2px solid transparent',
borderBottomColor: currentPage === item.page
? (themeMode === 'dark' ? 'primary.main' : '#ffffff')
: 'transparent',
borderRadius: 0,
'&:hover': {
backgroundColor: themeMode === 'dark' ? 'rgba(100, 149, 237, 0.1)' : 'rgba(255, 255, 255, 0.15)',
color: themeMode === 'dark' ? 'primary.light' : '#ffffff',
},
transition: 'all 0.2s ease-in-out',
}}
>
{item.page === 'alerts' && unreadCount > 0 ? (
<Badge badgeContent={unreadCount} color="error">
{item.icon}
</Badge>
) : (
item.icon
)}
</IconButton>
</Tooltip>
))}
{/* Theme Toggle Button */}
<Tooltip title={themeMode === 'dark' ? '라이트 모드로 전환' : '다크 모드로 전환'}>
<IconButton
onClick={handleThemeToggle}
sx={{
ml: 2,
color: themeMode === 'dark' ? 'primary.main' : '#ffffff',
'&:hover': {
backgroundColor: themeMode === 'dark' ? 'rgba(100, 149, 237, 0.1)' : 'rgba(255, 255, 255, 0.15)',
},
}}
>
{themeMode === 'dark' ? <LightMode /> : <DarkMode />}
</IconButton>
</Tooltip>
{/* Fullscreen Toggle Button */}
<Tooltip title={isFullscreen ? '전체화면 해제' : '전체화면으로 전환'}>
<IconButton
onClick={handleFullscreenToggle}
sx={{
ml: 1,
color: themeMode === 'dark' ? 'primary.main' : '#ffffff',
'&:hover': {
backgroundColor: themeMode === 'dark' ? 'rgba(100, 149, 237, 0.1)' : 'rgba(255, 255, 255, 0.15)',
},
}}
>
{isFullscreen ? <FullscreenExit /> : <Fullscreen />}
</IconButton>
</Tooltip>
{/* 알림팝업 전체 닫기 버튼 */}
<Tooltip title="알림팝업 전체 닫기">
<span>
<IconButton
onClick={dismissAllNotifications}
disabled={notifications.length === 0}
sx={{
ml: 1,
color: themeMode === 'dark' ? 'primary.main' : '#ffffff',
'&:hover': {
backgroundColor: themeMode === 'dark' ? 'rgba(100, 149, 237, 0.1)' : 'rgba(255, 255, 255, 0.15)',
},
'&.Mui-disabled': {
color: themeMode === 'dark' ? 'text.disabled' : 'rgba(255, 255, 255, 0.4)',
},
}}
aria-label="알림팝업 전체 닫기"
>
<ClearAllIcon />
</IconButton>
</span>
</Tooltip>
{/* 로그인/사용자 버튼 */}
{isAuthenticated ? (
<>
<Chip
icon={<AccountCircle />}
label={user?.name || user?.username || '사용자'}
onClick={handleUserMenuOpen}
sx={{
ml: 2,
backgroundColor: themeMode === 'dark' ? 'rgba(100, 149, 237, 0.2)' : 'rgba(255, 255, 255, 0.2)',
color: themeMode === 'dark' ? 'primary.main' : '#ffffff',
cursor: 'pointer',
'&:hover': {
backgroundColor: themeMode === 'dark' ? 'rgba(100, 149, 237, 0.3)' : 'rgba(255, 255, 255, 0.3)',
},
}}
/>
<Menu
anchorEl={userMenuAnchor}
open={Boolean(userMenuAnchor)}
onClose={handleUserMenuClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
>
<MenuItem disabled>
<ListItemIcon>
<Person fontSize="small" />
</ListItemIcon>
<ListItemText
primary={user?.username}
secondary={user?.email}
/>
</MenuItem>
<Divider />
<MenuItem onClick={handleLogout}>
<ListItemIcon>
<LogoutIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="로그아웃" />
</MenuItem>
</Menu>
</>
) : (
<Button
variant="outlined"
startIcon={<LoginIcon />}
onClick={() => setLoginDialogOpen(true)}
sx={{
ml: 2,
borderColor: themeMode === 'dark' ? 'primary.main' : '#ffffff',
color: themeMode === 'dark' ? 'primary.main' : '#ffffff',
'&:hover': {
borderColor: themeMode === 'dark' ? 'primary.light' : '#ffffff',
backgroundColor: themeMode === 'dark' ? 'rgba(100, 149, 237, 0.1)' : 'rgba(255, 255, 255, 0.15)',
},
}}
>
로그인
</Button>
)}
</Box>
{/* Version */}
<Typography
variant="caption"
sx={{
color: themeMode === 'dark' ? 'text.secondary' : 'rgba(255, 255, 255, 0.7)',
display: { xs: 'none', md: 'block' },
}}
>
v1.1.0
</Typography>
</Toolbar>
</AppBar>
{/* Main Content - Full Width */}
<Box
component="main"
sx={{
flexGrow: 1,
bgcolor: 'background.default',
mt: '64px', // AppBar height offset
minHeight: 'calc(100vh - 64px)',
}}
>
<Container
maxWidth={false}
sx={{
py: 2,
px: { xs: 2, sm: 3 },
height: 'calc(100vh - 64px)',
overflow: 'auto',
}}
>
{/* 상태 유지가 필요한 투자분석 화면 */}
<Box sx={{ display: currentPage === 'candlestick-test' ? 'block' : 'none', height: '100%' }}>
<CandlestickTestPage />
</Box>
{/* 투자분석3 (프론트엔드 지표 계산 버전) — 상태 유지 */}
<Box sx={{ display: currentPage === 'candlestick-test-3' ? 'block' : 'none', height: '100%' }}>
<CandlestickTestPage3 />
</Box>
{renderPage()}
</Container>
</Box>
</Box>
{/* 로그인 다이얼로그 */}
<LoginDialog
open={loginDialogOpen}
onClose={() => setLoginDialogOpen(false)}
onSwitchToSignUp={() => {
setLoginDialogOpen(false);
setSignUpDialogOpen(true);
}}
/>
{/* 회원가입 다이얼로그 */}
<SignUpDialog
open={signUpDialogOpen}
onClose={() => setSignUpDialogOpen(false)}
onSwitchToLogin={() => {
setSignUpDialogOpen(false);
setLoginDialogOpen(true);
}}
/>
{/* 지표 설정 패널 (우측 하단 FAB) */}
<IndicatorConfigPanel />
{/* 보조지표 팝업 윈도우 */}
<IndicatorPopupWindow
open={isPopupOpen}
onClose={closePopup}
chartData={chartData}
chartMargin={chartMargin}
/>
{/* 알림 스낵바 (모든 화면에서 표시) */}
<AlertSnackbar />
</ThemeProvider>
);
}
function App() {
// Context providers are now in AppRouter.tsx
return <MainContent />;
}
export default App;