diff --git a/backend/src/main/java/com/goldenchart/auth/MenuIds.java b/backend/src/main/java/com/goldenchart/auth/MenuIds.java index 20e60b5..25396c6 100644 --- a/backend/src/main/java/com/goldenchart/auth/MenuIds.java +++ b/backend/src/main/java/com/goldenchart/auth/MenuIds.java @@ -7,7 +7,7 @@ public final class MenuIds { private MenuIds() {} public static final List ALL = List.of( - "dashboard", "chart", "paper", "virtual", "trend-search", "verification-board", "strategy", "strategy-editor", "backtest", "notifications", "settings", + "dashboard", "chart", "paper", "virtual", "trend-search", "verification-board", "strategy", "strategy-editor", "backtest", "analysis-report", "notifications", "settings", "settings_general", "settings_chart", "settings_indicators", "settings_backtest", "settings_strategy", "settings_paper", "settings_virtual", "settings_live", "settings_trend-search", diff --git a/backend/src/main/resources/db/migration/V59__analysis_report_menu.sql b/backend/src/main/resources/db/migration/V59__analysis_report_menu.sql new file mode 100644 index 0000000..00c27a3 --- /dev/null +++ b/backend/src/main/resources/db/migration/V59__analysis_report_menu.sql @@ -0,0 +1,6 @@ +-- 분석레포트 메뉴 권한 +INSERT INTO gc_role_menu_permission (role, menu_id, allowed) VALUES +('ADMIN', 'analysis-report', 1), +('USER', 'analysis-report', 1), +('GUEST', 'analysis-report', 0) +ON DUPLICATE KEY UPDATE allowed = VALUES(allowed); diff --git a/docs/TRADING_REPORT_UI_DESIGN.pdf b/docs/TRADING_REPORT_UI_DESIGN.pdf new file mode 100644 index 0000000..ef037ba Binary files /dev/null and b/docs/TRADING_REPORT_UI_DESIGN.pdf differ diff --git a/docs/TradingReport_UI_Design.pdf b/docs/TradingReport_UI_Design.pdf new file mode 100644 index 0000000..6f19d11 Binary files /dev/null and b/docs/TradingReport_UI_Design.pdf differ diff --git a/docs/_new_pdf_pages/page_01.png b/docs/_new_pdf_pages/page_01.png new file mode 100644 index 0000000..b893139 Binary files /dev/null and b/docs/_new_pdf_pages/page_01.png differ diff --git a/docs/_new_pdf_pages/page_02.png b/docs/_new_pdf_pages/page_02.png new file mode 100644 index 0000000..27a77a7 Binary files /dev/null and b/docs/_new_pdf_pages/page_02.png differ diff --git a/docs/_new_pdf_pages/page_03.png b/docs/_new_pdf_pages/page_03.png new file mode 100644 index 0000000..b51bb11 Binary files /dev/null and b/docs/_new_pdf_pages/page_03.png differ diff --git a/docs/_new_pdf_pages/page_04.png b/docs/_new_pdf_pages/page_04.png new file mode 100644 index 0000000..10dae70 Binary files /dev/null and b/docs/_new_pdf_pages/page_04.png differ diff --git a/docs/_new_pdf_pages/page_05.png b/docs/_new_pdf_pages/page_05.png new file mode 100644 index 0000000..bfc338d Binary files /dev/null and b/docs/_new_pdf_pages/page_05.png differ diff --git a/docs/_new_pdf_pages/page_06.png b/docs/_new_pdf_pages/page_06.png new file mode 100644 index 0000000..938def2 Binary files /dev/null and b/docs/_new_pdf_pages/page_06.png differ diff --git a/docs/_new_pdf_pages/page_07.png b/docs/_new_pdf_pages/page_07.png new file mode 100644 index 0000000..12e2b48 Binary files /dev/null and b/docs/_new_pdf_pages/page_07.png differ diff --git a/docs/_new_pdf_pages/page_08.png b/docs/_new_pdf_pages/page_08.png new file mode 100644 index 0000000..c201e05 Binary files /dev/null and b/docs/_new_pdf_pages/page_08.png differ diff --git a/docs/_new_pdf_pages/page_09.png b/docs/_new_pdf_pages/page_09.png new file mode 100644 index 0000000..2ddfd99 Binary files /dev/null and b/docs/_new_pdf_pages/page_09.png differ diff --git a/docs/_new_pdf_pages/page_10.png b/docs/_new_pdf_pages/page_10.png new file mode 100644 index 0000000..96d4e19 Binary files /dev/null and b/docs/_new_pdf_pages/page_10.png differ diff --git a/docs/_new_pdf_pages/page_11.png b/docs/_new_pdf_pages/page_11.png new file mode 100644 index 0000000..f1ccaae Binary files /dev/null and b/docs/_new_pdf_pages/page_11.png differ diff --git a/docs/_new_pdf_pages/page_12.png b/docs/_new_pdf_pages/page_12.png new file mode 100644 index 0000000..cb76832 Binary files /dev/null and b/docs/_new_pdf_pages/page_12.png differ diff --git a/docs/_pdf_pages/page_01.png b/docs/_pdf_pages/page_01.png new file mode 100644 index 0000000..4fda9c4 Binary files /dev/null and b/docs/_pdf_pages/page_01.png differ diff --git a/docs/_pdf_pages/page_02.png b/docs/_pdf_pages/page_02.png new file mode 100644 index 0000000..1c779d8 Binary files /dev/null and b/docs/_pdf_pages/page_02.png differ diff --git a/docs/_pdf_pages/page_03.png b/docs/_pdf_pages/page_03.png new file mode 100644 index 0000000..9cbb249 Binary files /dev/null and b/docs/_pdf_pages/page_03.png differ diff --git a/docs/_pdf_pages/page_04.png b/docs/_pdf_pages/page_04.png new file mode 100644 index 0000000..9b9aa06 Binary files /dev/null and b/docs/_pdf_pages/page_04.png differ diff --git a/docs/_pdf_pages/page_05.png b/docs/_pdf_pages/page_05.png new file mode 100644 index 0000000..35c1927 Binary files /dev/null and b/docs/_pdf_pages/page_05.png differ diff --git a/docs/_pdf_pages/page_06.png b/docs/_pdf_pages/page_06.png new file mode 100644 index 0000000..55f55ff Binary files /dev/null and b/docs/_pdf_pages/page_06.png differ diff --git a/docs/_pdf_pages/page_07.png b/docs/_pdf_pages/page_07.png new file mode 100644 index 0000000..1020a19 Binary files /dev/null and b/docs/_pdf_pages/page_07.png differ diff --git a/docs/_pdf_pages/page_08.png b/docs/_pdf_pages/page_08.png new file mode 100644 index 0000000..79492fb Binary files /dev/null and b/docs/_pdf_pages/page_08.png differ diff --git a/docs/_pdf_pages/page_09.png b/docs/_pdf_pages/page_09.png new file mode 100644 index 0000000..6c183e1 Binary files /dev/null and b/docs/_pdf_pages/page_09.png differ diff --git a/docs/_pdf_pages/page_10.png b/docs/_pdf_pages/page_10.png new file mode 100644 index 0000000..384d586 Binary files /dev/null and b/docs/_pdf_pages/page_10.png differ diff --git a/docs/_pdf_pages/page_11.png b/docs/_pdf_pages/page_11.png new file mode 100644 index 0000000..618dd31 Binary files /dev/null and b/docs/_pdf_pages/page_11.png differ diff --git a/docs/_pdf_pages/page_12.png b/docs/_pdf_pages/page_12.png new file mode 100644 index 0000000..d5df399 Binary files /dev/null and b/docs/_pdf_pages/page_12.png differ diff --git a/docs/_pdf_pages/page_13.png b/docs/_pdf_pages/page_13.png new file mode 100644 index 0000000..7a76f9d Binary files /dev/null and b/docs/_pdf_pages/page_13.png differ diff --git a/docs/_pdf_pages/page_14.png b/docs/_pdf_pages/page_14.png new file mode 100644 index 0000000..cf28571 Binary files /dev/null and b/docs/_pdf_pages/page_14.png differ diff --git a/docs/_pdf_pages/page_15.png b/docs/_pdf_pages/page_15.png new file mode 100644 index 0000000..1e355cf Binary files /dev/null and b/docs/_pdf_pages/page_15.png differ diff --git a/frontend/src/App.css b/frontend/src/App.css index c7f1da6..c9899b3 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -228,6 +228,7 @@ html.theme-blue { min-width: 0; } .tmb-nav-btn { + position: relative; /* 알림 뱃지 absolute 기준점 */ display: inline-flex; align-items: center; gap: 6px; @@ -12193,37 +12194,21 @@ html.theme-light .tam-disclaimer { color: #90a4ae; } color: var(--text); } -/* TopMenuBar 알림 버튼 */ -.tmb-notify-group { +/* TopMenuBar 알림 ── 아이콘 버튼 제거, 메뉴 뱃지 + 팝업 닫기 버튼만 유지 */ +.tmb-notify-dismiss-btn { + position: relative; display: inline-flex; align-items: center; + justify-content: center; gap: 4px; -} -.tmb-notify-btn { - position: relative; - display: flex; - align-items: center; - justify-content: center; - width: 34px; - height: 34px; - border-radius: 6px; - border: 1px solid var(--border); - background: var(--bg3); - color: var(--text2); - cursor: pointer; -} -.tmb-notify-btn:hover { background: var(--bg4); color: var(--text); } -.tmb-notify-dismiss-btn { - display: flex; - align-items: center; - justify-content: center; - width: 34px; - height: 34px; + height: 32px; + padding: 0 8px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg3); color: var(--text2); cursor: pointer; + font-size: 12px; } .tmb-notify-dismiss-btn:hover:not(:disabled) { background: rgba(239, 83, 80, 0.12); @@ -12234,10 +12219,11 @@ html.theme-light .tam-disclaimer { color: #90a4ae; } opacity: 0.35; cursor: not-allowed; } +/* 공통 뱃지 — 알림 팝업 닫기 버튼 내 또는 메뉴 버튼 위에 표시 */ .tmb-notify-badge { - position: absolute; - top: -4px; - right: -4px; + display: inline-flex; + align-items: center; + justify-content: center; min-width: 16px; height: 16px; padding: 0 4px; @@ -12246,8 +12232,15 @@ html.theme-light .tam-disclaimer { color: #90a4ae; } color: #fff; font-size: 10px; font-weight: 700; - line-height: 16px; - text-align: center; + line-height: 1; +} + +/* 메뉴 버튼 내 뱃지 (알림목록 메뉴) */ +.tmb-notify-badge--nav { + position: absolute; + top: 2px; + right: 2px; + pointer-events: none; } .tmb-notify-layout { position: relative; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2246006..4245b9b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -120,6 +120,7 @@ const StrategyPage = lazy(() => import('./components/StrategyPage')); const StrategyEditorPage = lazy(() => import('./components/StrategyEditorPage')); const TradeNotificationListPage = lazy(() => import('./components/TradeNotificationListPage')); const BacktestHistoryPage = lazy(() => import('./components/BacktestHistoryPage').then(m => ({ default: m.BacktestHistoryPage }))); +const AnalysisReportPage = lazy(() => import('./components/analysisReport/AnalysisReportPage').then(m => ({ default: m.AnalysisReportPage }))); const SettingsPage = lazy(() => import('./components/SettingsPage')); const PaperTradingPage = lazy(() => import('./components/PaperTradingPage')); const VirtualTradingPage = lazy(() => import('./components/VirtualTradingPage')); @@ -2004,6 +2005,16 @@ function App() { : )} + {menuPage === 'analysis-report' && ( + formalLogin + ? ( + }> + + + ) + : + )} + {menuPage === 'paper' && ( formalLogin ? ( }> diff --git a/frontend/src/components/TopMenuBar.tsx b/frontend/src/components/TopMenuBar.tsx index 5aae103..6183152 100644 --- a/frontend/src/components/TopMenuBar.tsx +++ b/frontend/src/components/TopMenuBar.tsx @@ -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 = () => ( ); +const IcAnalysisReport = () => ( + + + + + + + +); + const IcBacktest = () => ( @@ -98,15 +108,6 @@ const IcNotify = () => ( ); -/** 알림 팝업 전체 닫기 */ -const IcDismissPopups = () => ( - - - - - - -); const IcFullscreen = () => ( @@ -191,6 +192,7 @@ const MENU_ITEMS: { page: MenuPage; label: string; icon: React.ReactNode }[] = [ { page: 'trend-search', label: '추세검색', icon: }, { page: 'strategy-editor', label: '전략편집기', icon: }, { page: 'backtest', label: '백테스팅', icon: }, + { page: 'analysis-report', label: '분석레포트', icon: }, { page: 'notifications', label: '알림목록', icon: }, { page: 'settings', label: '설정', icon: }, { page: 'verification-board', label: '검증게시판', icon: }, @@ -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 (
{/* 로고 */} @@ -254,6 +253,11 @@ export const TopMenuBar = memo(function TopMenuBar({ >
{icon} {label} + {page === 'notifications' && tradeNotifyUnread > 0 && ( + + {tradeNotifyUnread > 99 ? '99+' : tradeNotifyUnread} + + )} ))} @@ -274,54 +278,6 @@ export const TopMenuBar = memo(function TopMenuBar({ > - {onOpenNotifications && showNotifications && ( -