모의투자 관리 기능 추가
This commit is contained in:
+16
-45
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user