데스크톱 앱 리프레시 기능 추가

This commit is contained in:
Macbook
2026-06-22 16:47:20 +09:00
parent 1180cd53f0
commit 5871eb2465
17 changed files with 165 additions and 5 deletions
@@ -22,6 +22,7 @@ import {
} from '../utils/backendApi';
import type { Theme, Timeframe } from '../types';
import { fetchBacktestCandleBundle } from '../utils/backtestWarmup';
import { usePageRefresh } from '../utils/pageRefreshEvents';
import { buildEquityFromSignals } from '../utils/backtestEquity';
import {
formatChartTypeKo,
@@ -210,6 +211,8 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
useEffect(() => { void fetchAll(); }, [fetchAll]);
usePageRefresh('backtest', () => { void fetchAll(); });
useEffect(() => {
const onTradesChanged = () => { void refreshLiveTrades(); };
window.addEventListener(PAPER_TRADES_CHANGED_EVENT, onTradesChanged);
@@ -16,6 +16,7 @@ import DashboardSegmentBar from './dashboard/DashboardSegmentBar';
import DashboardSparkChart from './dashboard/DashboardSparkChart';
import DashboardIndicatorBar from './dashboard/DashboardIndicatorBar';
import DashboardSystemCard from './dashboard/DashboardSystemCard';
import { usePageRefresh } from '../utils/pageRefreshEvents';
import '../styles/strategyEditorTheme.css';
import '../styles/dashboardCommand.css';
@@ -110,6 +111,8 @@ const DashboardPage: React.FC<Props> = ({ theme, onGoChart }) => {
return () => window.clearInterval(id);
}, [refresh]);
usePageRefresh('dashboard', refresh);
const mon = data?.systemMonitor;
const app = data?.app as Record<string, unknown> | undefined;
const health = mon ? healthLevel(mon) : 'yellow';
@@ -28,6 +28,7 @@ interface NotifyTopMenuBarProps {
onFullscreen?: () => void;
onOpenFloatingWidgets?: () => void;
floatingWidgetCount?: number;
onRefresh?: () => void;
}
export const NotifyTopMenuBar: React.FC<NotifyTopMenuBarProps> = props => {
@@ -35,6 +35,7 @@ import { useUpbitRecentTrades } from '../hooks/useUpbitRecentTrades';
import { loadVirtualSession, loadVirtualTargets } from '../utils/virtualTradingStorage';
import { resolveVirtualTargetStrategyId } from '../utils/virtualTargetStrategy';
import { formatVirtualTargetOptionLabel } from '../utils/virtualTargetNames';
import { usePageRefresh } from '../utils/pageRefreshEvents';
type HistoryTab = 'open' | 'recent' | 'ledger';
type CenterTab = 'invest' | 'chart';
@@ -152,6 +153,8 @@ const PaperTradingPage: React.FC<Props> = ({
useEffect(() => { if (refreshKey > 0) void loadData(); }, [refreshKey, loadData]);
usePageRefresh('paper', () => { void loadData(); });
useEffect(() => {
if (initialLoading) return;
setVirtualTargets(loadVirtualTargets());
@@ -28,6 +28,7 @@ import StrategyEvaluationStrategyPanel from './strategyEvaluation/StrategyEvalua
import StrategyEvaluationIndicatorPalettePanel from './strategyEvaluation/StrategyEvaluationIndicatorPalettePanel';
import StrategyEvaluationSettingsTab from './strategyEvaluation/StrategyEvaluationSettingsTab';
import StrategyEvaluationMarketTab from './strategyEvaluation/StrategyEvaluationMarketTab';
import { usePageRefresh } from '../utils/pageRefreshEvents';
import StrategyEditorModal from './strategyEvaluation/StrategyEditorModal';
import SePanelCollapseHandle from './strategyEditor/SePanelCollapseHandle';
import { readStoredSize, readStoredBool, storeBool, storeSize, usePanelResize } from './strategyEditor/usePanelResize';
@@ -375,6 +376,11 @@ function StrategyEvaluationPageInner({ theme = 'dark' }: Props) {
void refreshStrategies();
}, [refreshStrategies]);
usePageRefresh('strategy-evaluation', () => {
void refreshStrategies();
setParamsRevision(v => v + 1);
});
useEffect(() => {
if (!selectedStrategyId) {
setSelectedStrategy(null);
+33
View File
@@ -50,6 +50,8 @@ interface TopMenuBarProps {
onOpenFloatingWidgets?: () => void;
/** 실행 중인 플로팅 위젯 창 수 */
floatingWidgetCount?: number;
/** 데스크톱 — 현재 화면 데이터 새로고침 */
onRefresh?: () => void;
/** 메뉴별 접근 허용 (없으면 전체 표시) */
menuPermissions?: Record<string, boolean>;
}
@@ -116,6 +118,15 @@ const IcDesktopUpdate = () => (
</svg>
);
const IcRefresh = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M2 8a6 6 0 0 1 10.2-4.2"/>
<polyline points="12,2 12,5.5 8.5,5.5"/>
<path d="M14 8a6 6 0 0 1-10.2 4.2"/>
<polyline points="4,14 4,10.5 7.5,10.5"/>
</svg>
);
const IcNotify = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M8 1.5a4.5 4.5 0 0 0-4.5 4.5c0 3.5-1 4.5-1.5 4.5h12c-.5 0-1.5-1-1.5-4.5A4.5 4.5 0 0 0 8 1.5z"/>
@@ -293,10 +304,12 @@ export const TopMenuBar = memo(function TopMenuBar({
onFullscreen,
onOpenFloatingWidgets,
floatingWidgetCount = 0,
onRefresh,
menuPermissions,
}: TopMenuBarProps) {
const [isFullscreen, setIsFullscreen] = useState(() => !!document.fullscreenElement);
const [desktopUpdateOpen, setDesktopUpdateOpen] = useState(false);
const [refreshBusy, setRefreshBusy] = useState(false);
const desktopClient = isDesktop();
const appVersion = useAppTitleBarVersion();
@@ -340,6 +353,26 @@ export const TopMenuBar = memo(function TopMenuBar({
{/* 우측 영역 */}
<div className="tmb-right">
{desktopClient && onRefresh && (
<button
type="button"
className={`tmb-refresh-btn${refreshBusy ? ' busy' : ''}`}
onClick={() => {
if (refreshBusy) return;
setRefreshBusy(true);
try {
onRefresh();
} finally {
window.setTimeout(() => setRefreshBusy(false), 600);
}
}}
title="현재 화면 새로고침"
aria-label="현재 화면 새로고침"
>
<IcRefresh />
</button>
)}
{onOpenFloatingWidgets && (
<div className="tmb-floating-widget-wrap">
<button
@@ -13,6 +13,7 @@ import {
type StrategyDto,
} from '../utils/backendApi';
import { prefetchNotificationStrategies, resolveNotificationStrategy } from '../utils/resolveNotificationStrategy';
import { usePageRefresh } from '../utils/pageRefreshEvents';
import { normalizeMarketCode } from '../utils/strategyHydrate';
import { fetchBacktestCandleBundle } from '../utils/backtestWarmup';
import { buildChartBacktestReportModel } from '../utils/backtestReportModel';
@@ -182,6 +183,11 @@ export const TradeNotificationListPage: React.FC<Props> = ({
void refreshPaperData();
}, [refreshPaperData]);
usePageRefresh('notifications', () => {
void refreshHistory();
void refreshPaperData();
});
useEffect(() => {
let cancelled = false;
let retryTimer: ReturnType<typeof setTimeout> | null = null;
@@ -30,6 +30,7 @@ import {
type TrendSearchAppSettings,
} from '../utils/trendSearchAppSettings';
import { autoAddTrendSearchTargets } from '../utils/trendSearchAutoAddTargets';
import { usePageRefresh } from '../utils/pageRefreshEvents';
import '../styles/trendSearchDashboard.css';
import '../styles/virtualTradingDashboard.css';
@@ -136,6 +137,8 @@ const TrendSearchPage: React.FC<Props> = ({
}
}, [applyResults]);
usePageRefresh('trend-search', () => { void loadFromDb(); });
const runScan = useCallback(async () => {
setSearching(true);
try {
@@ -16,6 +16,7 @@ import {
type VerificationIssueDto,
} from '../utils/verificationBoardApi';
import { prepareAttachmentsForUpload } from '../utils/verificationAttachmentFiles';
import { usePageRefresh } from '../utils/pageRefreshEvents';
import '../styles/verificationBoard.css';
interface Props {
@@ -79,6 +80,8 @@ const VerificationBoardPage: React.FC<Props> = ({
void reload(stageFilter);
}, [stageFilter]); // eslint-disable-line react-hooks/exhaustive-deps
usePageRefresh('verification-board', () => { void reload(stageFilter); });
useEffect(() => {
if (focusIssueId == null) return;
setSelectedId(focusIssueId);
@@ -29,6 +29,7 @@ import {
import type { VirtualCardDisplayMode } from './virtual/VirtualTargetCard';
import { useVirtualIndicatorSnapshots } from '../hooks/useVirtualIndicatorSnapshots';
import { PAPER_TRADES_CHANGED_EVENT } from '../utils/paperTradeEvents';
import { usePageRefresh } from '../utils/pageRefreshEvents';
import { useVirtualTargetLiveStatus } from '../hooks/useVirtualTargetLiveStatus';
import type { ChartRealtimeSource } from '../hooks/useChartRealtimeData';
import {
@@ -261,6 +262,8 @@ const VirtualTradingPage: React.FC<Props> = ({
if (opts?.focusHistory) setRightTab('history');
}, [onPaperOrderFilled]);
usePageRefresh('virtual', () => { void refreshPaperData(); });
const handleGlobalDisplayModeChange = useCallback((mode: VirtualCardDisplayMode) => {
setGlobalDisplayMode(mode);
}, []);