모의투자 관리 기능 추가

This commit is contained in:
Macbook
2026-05-31 03:47:07 +09:00
parent 9d7dddfa57
commit 16d0e2c226
78 changed files with 4688 additions and 972 deletions
+16 -45
View File
@@ -100,11 +100,7 @@ import VerificationBoardPage from './components/VerificationBoardPage';
import DashboardPage from './components/DashboardPage';
import { loadPaperSummary, resetPaperAccount, loadActiveLiveStrategySettings, expandLiveStrategySubscriptions } from './utils/backendApi';
import ChartLegendBar from './components/ChartLegendBar';
import {
formatChartLiveChangePct,
formatChartLivePrice,
resolveChartLiveQuote,
} from './utils/chartLiveQuote';
import { resolveChartLiveQuote } from './utils/chartLiveQuote';
import RightSidePanel from './components/RightSidePanel';
import {
type BacktestSettingsDto,
@@ -129,7 +125,7 @@ import { getAuthSession, setAuthSession, clearAuthSession, type AuthSession } fr
import { isAppEntered, setAppEntered, clearAppEntered } from './utils/appEntry';
import { syncDocumentTheme } from './utils/documentTheme';
import { useMenuPermissions, invalidateMenuPermissionsCache } from './hooks/useMenuPermissions';
import { firstAllowedTopMenu, normalizeRole } from './utils/permissions';
import { firstAllowedTopMenu, normalizeRole, type SettingsCategoryId } from './utils/permissions';
import { clearAdminUnlock } from './utils/adminUnlock';
import type { LoginResponse } from './utils/backendApi';
import { invalidateAppSettingsCache } from './hooks/useAppSettings';
@@ -137,25 +133,13 @@ import { invalidateIndicatorSettingsCache } from './hooks/useIndicatorSettings';
import './App.css';
import './styles/appPopup.css';
import './styles/paperDashboard.css';
import './styles/tradeRightPanel.css';
import './styles/virtualTradingDashboard.css';
import './styles/backtestDashboard.css';
let _indCounter = 0;
function newIndId() { return `ind_${++_indCounter}_${Date.now()}`; }
// ─── 연결 상태 배지 ────────────────────────────────────────────────────────
const WsBadge: React.FC<{ status: WsStatus; isUpbit: boolean }> = ({ status, isUpbit }) => {
if (!isUpbit) return null;
const map: Record<WsStatus, { dot: string; label: string }> = {
connecting: { dot: 'dot-yellow', label: '연결 중...' },
connected: { dot: 'dot-lime', label: '실시간' },
disconnected: { dot: 'dot-red', label: '재연결 중' },
error: { dot: 'dot-red', label: '오류' },
};
const { dot, label } = map[status];
return <span className={`ws-badge ${dot}`}><span className="ws-dot" />{label}</span>;
};
/** 모든 .ws-dot 요소에 형광 glow 플래시 (React state 없이 직접 DOM 조작) */
let _wsFlashTimer: ReturnType<typeof setTimeout> | null = null;
function flashWsDot() {
@@ -231,7 +215,11 @@ function App() {
const [mobileRightOpen, setMobileRightOpen] = useState(false);
const [mobileRightTab, setMobileRightTab] = useState<'trade' | 'orderbook'>('trade');
const [menuPage, setMenuPage] = useState<MenuPage>('chart');
const [settingsInitialCategory, setSettingsInitialCategory] = useState<SettingsCategoryId | undefined>();
const [verificationFocusIssueId, setVerificationFocusIssueId] = useState<number | null>(null);
useEffect(() => {
if (menuPage !== 'settings') setSettingsInitialCategory(undefined);
}, [menuPage]);
const chartVisible = menuPage === 'chart';
const isMobile = useIsMobile();
@@ -1770,6 +1758,10 @@ function App() {
paperTradingEnabled={paperTradingEnabled}
paperAutoTradeEnabled={paperAutoTradeEnabled}
onPaperOrderFilled={handlePaperOrderFilled}
onOpenSettings={() => {
setSettingsInitialCategory('paper');
setMenuPage('settings');
}}
onGoChart={m => {
goToMarketChart(m);
setMenuPage('chart');
@@ -1957,6 +1949,7 @@ function App() {
const r = await sendFcmTest();
window.alert(r?.available ? 'FCM 테스트 요청을 보냈습니다.' : 'FCM 서버가 비활성 상태입니다.');
}}
initialCategory={settingsInitialCategory}
/>
)}
@@ -2083,34 +2076,12 @@ function App() {
}}
onToggleLiveStrategy={() => setShowLivePanel(v => !v)}
liveStrategyActive={showLivePanel || monitoredMarkets.length > 0}
showChartQuote={useUpbit}
chartLiveQuote={liveQuote}
chartQuoteLoading={isLoading}
chartQuoteError={error}
/>
{/* 업비트 연결 상태 바 */}
{useUpbit && (
<div className={`upbit-bar ${wsStatus}`}>
<WsBadge status={wsStatus} isUpbit={useUpbit} />
<span className="upbit-market">{getKoreanName(symbol)} <span style={{opacity:0.5, fontSize:'11px'}}>{symbol}</span></span>
{isLoading && <span className="upbit-loading"> ...</span>}
{error && <span className="upbit-error"> {error}</span>}
{!isLoading && !error && liveQuote.price != null && (
<div className="upbit-quote-group">
<span
className="upbit-price"
style={{ color: liveQuote.isUp ? 'var(--up)' : 'var(--down)' }}
>
{formatChartLivePrice(liveQuote.price)}
</span>
{liveQuote.changePct != null && (
<span className={`upbit-change ${liveQuote.isUp ? 'up' : 'down'}`}>
{formatChartLiveChangePct(liveQuote.changePct)}
</span>
)}
</div>
)}
<span className="upbit-hint">💡 마켓: KRW-BTC, KRW-ETH, KRW-XRP ...</span>
</div>
)}
{isMobile && (showMarketPanel || mobileRightOpen) && (
<button
type="button"