524 lines
19 KiB
TypeScript
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;
|