위젯 기능 추가

This commit is contained in:
Macbook
2026-06-14 01:03:51 +09:00
parent 95595f7e9e
commit 6936792ad4
57 changed files with 6728 additions and 540 deletions
@@ -0,0 +1,149 @@
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;