분석레포트 화면 추가

This commit is contained in:
Macbook
2026-06-03 15:57:02 +09:00
parent 3e17d6cfb8
commit a2ede568e0
48 changed files with 2593 additions and 94 deletions
+19 -63
View File
@@ -7,19 +7,19 @@
import React, { memo, useEffect, useState } from 'react';
import type { Theme } from '../types';
import type { AuthSession } from '../utils/auth';
import TradeAlertPopupMenubarSelect from './TradeAlertPopupMenubarSelect';
import type { TradeAlertPopupLayout, TradeAlertPopupPosition } from '../utils/tradeAlertPopupLayout';
import { canAccessMenu } from '../utils/permissions';
export type MenuPage = 'dashboard' | 'chart' | 'paper' | 'virtual' | 'trend-search' | 'verification-board' | 'strategy' | 'strategy-editor' | 'backtest' | 'notifications' | 'settings';
export type MenuPage = 'dashboard' | 'chart' | 'paper' | 'virtual' | 'trend-search' | 'verification-board' | 'strategy' | 'strategy-editor' | 'backtest' | 'analysis-report' | 'notifications' | 'settings';
interface TopMenuBarProps {
activePage: MenuPage;
theme: Theme;
onPage: (page: MenuPage) => void;
onTheme: () => void;
/** 미확인 매매 시그널 알림 수 */
/** 미확인 매매 시그널 알림 수 — 알림목록 메뉴 뱃지로 표시 */
tradeNotifyUnread?: number;
/** @deprecated 알림 아이콘 버튼 제거됨, 하위 호환 유지 */
onOpenNotifications?: () => void;
/** 모바일 앱 다운로드 모달 */
onOpenAppDownload?: () => void;
@@ -66,6 +66,16 @@ const IcChart = () => (
</svg>
);
const IcAnalysisReport = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M5 2h8a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z"/>
<path d="M3 5H2a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h8"/>
<line x1="7" y1="6" x2="11" y2="6"/>
<line x1="7" y1="9" x2="11" y2="9"/>
<line x1="7" y1="12" x2="9" y2="12"/>
</svg>
);
const IcBacktest = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="1" y="9" width="2.5" height="5" rx="0.5"/>
@@ -98,15 +108,6 @@ const IcNotify = () => (
</svg>
);
/** 알림 팝업 전체 닫기 */
const IcDismissPopups = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="9" height="10" rx="1"/>
<path d="M11 5.5 14 2.5M14 5.5 11 2.5"/>
<line x1="4.5" y1="6.5" x2="8.5" y2="10.5"/>
<line x1="8.5" y1="6.5" x2="4.5" y2="10.5"/>
</svg>
);
const IcFullscreen = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
@@ -191,6 +192,7 @@ const MENU_ITEMS: { page: MenuPage; label: string; icon: React.ReactNode }[] = [
{ page: 'trend-search', label: '추세검색', icon: <IcTrendSearch /> },
{ page: 'strategy-editor', label: '전략편집기', icon: <IcStrategyEditor /> },
{ page: 'backtest', label: '백테스팅', icon: <IcBacktest /> },
{ page: 'analysis-report', label: '분석레포트', icon: <IcAnalysisReport /> },
{ page: 'notifications', label: '알림목록', icon: <IcNotify /> },
{ page: 'settings', label: '설정', icon: <IcSettings /> },
{ page: 'verification-board', label: '검증게시판', icon: <IcVerificationBoard /> },
@@ -199,7 +201,6 @@ const MENU_ITEMS: { page: MenuPage; label: string; icon: React.ReactNode }[] = [
export const TopMenuBar = memo(function TopMenuBar({
activePage, theme, onPage, onTheme,
tradeNotifyUnread = 0,
onOpenNotifications,
onOpenAppDownload,
tradeNotifyToastCount = 0,
onDismissAllNotifyPopups,
@@ -227,8 +228,6 @@ export const TopMenuBar = memo(function TopMenuBar({
const visibleItems = menuPermissions
? MENU_ITEMS.filter(({ page }) => canAccessMenu(menuPermissions, page))
: MENU_ITEMS;
const showNotifications = menuPermissions == null || menuPermissions.notifications === true;
return (
<header className="top-menubar">
{/* 로고 */}
@@ -254,6 +253,11 @@ export const TopMenuBar = memo(function TopMenuBar({
>
<span className="tmb-nav-icon">{icon}</span>
<span className="tmb-nav-label">{label}</span>
{page === 'notifications' && tradeNotifyUnread > 0 && (
<span className="tmb-notify-badge tmb-notify-badge--nav">
{tradeNotifyUnread > 99 ? '99+' : tradeNotifyUnread}
</span>
)}
</button>
</React.Fragment>
))}
@@ -274,54 +278,6 @@ export const TopMenuBar = memo(function TopMenuBar({
>
<IcAppDownload />
</button>
{onOpenNotifications && showNotifications && (
<span className="tmb-divider" aria-hidden="true" />
)}
</>
)}
{onOpenNotifications && showNotifications && (
<>
<div className="tmb-notify-group">
<button
type="button"
className={`tmb-notify-btn ${activePage === 'notifications' ? 'tmb-nav-btn--active' : ''}`}
onClick={onOpenNotifications}
title="매매 시그널 알림"
aria-label="매매 시그널 알림"
>
<IcNotify />
{tradeNotifyUnread > 0 && (
<span className="tmb-notify-badge">
{tradeNotifyUnread > 99 ? '99+' : tradeNotifyUnread}
</span>
)}
</button>
<TradeAlertPopupMenubarSelect
position={tradeAlertPopupPosition}
layout={tradeAlertPopupLayout}
gridCols={tradeAlertPopupGridCols}
onPositionChange={onTradeAlertPopupPosition}
onLayoutChange={onTradeAlertPopupLayout}
onGridColsChange={onTradeAlertPopupGridCols}
/>
{onDismissAllNotifyPopups && (
<button
type="button"
className="tmb-notify-dismiss-btn"
onClick={onDismissAllNotifyPopups}
disabled={tradeNotifyToastCount <= 0}
title={
tradeNotifyToastCount > 0
? `알림 팝업 ${tradeNotifyToastCount}건 전체 닫기`
: '닫을 알림 팝업 없음'
}
aria-label="알림 팝업 전체 닫기"
>
<IcDismissPopups />
</button>
)}
</div>
<span className="tmb-divider" aria-hidden="true" />
</>
)}