150 lines
4.9 KiB
TypeScript
150 lines
4.9 KiB
TypeScript
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
import type { Theme } from '../types';
|
|
import {
|
|
loadPaperSummary,
|
|
loadPaperTrades,
|
|
loadStrategies,
|
|
type PaperSummaryDto,
|
|
type PaperTradeDto,
|
|
type StrategyDto,
|
|
} from '../utils/backendApi';
|
|
import {
|
|
flushPendingUiPreferences,
|
|
getUiPreferences,
|
|
patchUiPreferences,
|
|
subscribeUiPreferences,
|
|
} from '../utils/uiPreferencesDb';
|
|
import { defaultWidgetLayout, normalizeWidgetLayout, type WidgetLayoutSchema } from '../types/widgetDashboard';
|
|
import { WidgetDashboardProvider } from '../widgets/WidgetDashboardContext';
|
|
import WidgetDashboardShell from './widgetDashboard/WidgetDashboardShell';
|
|
import { PAPER_TRADES_CHANGED_EVENT } from '../utils/paperTradeEvents';
|
|
import { useMarketTicker } from '../hooks/useMarketTicker';
|
|
import type { ChartRealtimeSource } from '../hooks/useChartRealtimeData';
|
|
import { useChartWorkspaceContext } from '../chart/ChartWorkspaceContext';
|
|
import { useAppSettings } from '../hooks/useAppSettings';
|
|
import '../styles/widgetDashboard.css';
|
|
|
|
interface Props {
|
|
theme: Theme;
|
|
paperTradingEnabled: boolean;
|
|
paperAutoTradeEnabled: boolean;
|
|
onPaperOrderFilled?: () => void;
|
|
chartRealtimeSource?: ChartRealtimeSource;
|
|
}
|
|
|
|
function readWidgetLayoutFromPrefs(): WidgetLayoutSchema {
|
|
return normalizeWidgetLayout(getUiPreferences().widgetDashboard ?? defaultWidgetLayout());
|
|
}
|
|
|
|
const WidgetDashboardPage: React.FC<Props> = ({
|
|
theme,
|
|
paperTradingEnabled,
|
|
paperAutoTradeEnabled,
|
|
onPaperOrderFilled,
|
|
chartRealtimeSource = 'BACKEND_STOMP',
|
|
}) => {
|
|
const { isLoaded: appSettingsLoaded } = useAppSettings();
|
|
const { symbol: defaultMarket } = useChartWorkspaceContext();
|
|
const { tickers, marketInfos, loading: marketLoading, usdRate } = useMarketTicker(
|
|
useMemo(() => ({ enabled: true, loadFull: true }), []),
|
|
);
|
|
const [layout, setLayout] = useState<WidgetLayoutSchema>(defaultWidgetLayout);
|
|
const [strategies, setStrategies] = useState<StrategyDto[]>([]);
|
|
const [summary, setSummary] = useState<PaperSummaryDto | null>(null);
|
|
const [trades, setTrades] = useState<PaperTradeDto[]>([]);
|
|
const layoutJsonRef = useRef('');
|
|
|
|
useEffect(() => {
|
|
if (!appSettingsLoaded) return;
|
|
const next = readWidgetLayoutFromPrefs();
|
|
const json = JSON.stringify(next);
|
|
if (json !== layoutJsonRef.current) {
|
|
layoutJsonRef.current = json;
|
|
setLayout(next);
|
|
}
|
|
}, [appSettingsLoaded]);
|
|
|
|
useEffect(() => {
|
|
if (!appSettingsLoaded) return;
|
|
return subscribeUiPreferences(() => {
|
|
const next = readWidgetLayoutFromPrefs();
|
|
const json = JSON.stringify(next);
|
|
if (json === layoutJsonRef.current) return;
|
|
layoutJsonRef.current = json;
|
|
setLayout(next);
|
|
});
|
|
}, [appSettingsLoaded]);
|
|
|
|
useEffect(() => {
|
|
const flush = () => { flushPendingUiPreferences(); };
|
|
window.addEventListener('beforeunload', flush);
|
|
window.addEventListener('pagehide', flush);
|
|
return () => {
|
|
window.removeEventListener('beforeunload', flush);
|
|
window.removeEventListener('pagehide', flush);
|
|
flush();
|
|
};
|
|
}, []);
|
|
|
|
const persistLayout = useCallback((
|
|
next: WidgetLayoutSchema,
|
|
opts?: { immediate?: boolean },
|
|
) => {
|
|
const normalized = normalizeWidgetLayout(next);
|
|
layoutJsonRef.current = JSON.stringify(normalized);
|
|
setLayout(normalized);
|
|
if (!appSettingsLoaded) return;
|
|
patchUiPreferences({ widgetDashboard: normalized }, opts?.immediate ?? false);
|
|
}, [appSettingsLoaded]);
|
|
|
|
const refreshPaperData = useCallback(async () => {
|
|
try {
|
|
const [sum, tr] = await Promise.all([loadPaperSummary(), loadPaperTrades()]);
|
|
setSummary(sum);
|
|
setTrades(tr ?? []);
|
|
} catch { /* ignore */ }
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
void loadStrategies().then(list => setStrategies(list ?? []));
|
|
void refreshPaperData();
|
|
}, [refreshPaperData]);
|
|
|
|
useEffect(() => {
|
|
const onChanged = () => { void refreshPaperData(); };
|
|
window.addEventListener(PAPER_TRADES_CHANGED_EVENT, onChanged);
|
|
return () => window.removeEventListener(PAPER_TRADES_CHANGED_EVENT, onChanged);
|
|
}, [refreshPaperData]);
|
|
|
|
if (!appSettingsLoaded) {
|
|
return <div className="wd-loading">위젯 대시보드 로딩…</div>;
|
|
}
|
|
|
|
return (
|
|
<WidgetDashboardProvider
|
|
theme={theme}
|
|
tickers={tickers}
|
|
marketInfos={marketInfos}
|
|
marketLoading={marketLoading}
|
|
usdRate={usdRate}
|
|
defaultMarket={defaultMarket}
|
|
strategies={strategies}
|
|
summary={summary}
|
|
trades={trades}
|
|
refreshPaperData={() => { void refreshPaperData(); }}
|
|
paperTradingEnabled={paperTradingEnabled}
|
|
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
|
onPaperOrderFilled={onPaperOrderFilled}
|
|
chartRealtimeSource={chartRealtimeSource}
|
|
>
|
|
<WidgetDashboardShell
|
|
theme={theme}
|
|
layout={layout}
|
|
onLayoutChange={persistLayout}
|
|
/>
|
|
</WidgetDashboardProvider>
|
|
);
|
|
};
|
|
|
|
export default WidgetDashboardPage;
|