목록 로딩 로직 개선

This commit is contained in:
Macbook
2026-06-07 16:57:30 +09:00
parent 59c548cb41
commit 7ec754770d
25 changed files with 1081 additions and 431 deletions
@@ -14,6 +14,7 @@ import {
parseTargetStrategySelectValue,
targetStrategySelectValue,
} from '../../utils/virtualTargetStrategy';
import { VirtualScroll } from '../common/VirtualScroll';
interface Props {
targets: VirtualTargetItem[];
@@ -131,11 +132,18 @@ const VirtualLeftTargetPanel: React.FC<Props> = ({
<span className="vtd-target-count">{targets.length}/{virtualTargetMaxCount}</span>
</div>
<div className="vtd-target-list">
{targets.length === 0 && (
<p className="vtd-muted"> + .</p>
)}
{targets.map(item => {
{targets.length === 0 ? (
<p className="vtd-muted"> + .</p>
) : (
<VirtualScroll
className="vtd-target-list vl-scroll"
items={targets}
estimateSize={132}
measureDynamic
getItemKey={item => item.market}
aria-label="투자대상 목록"
>
{item => {
const { koreanName: ko, englishName: en } = resolveVirtualTargetNames(
item.market,
item.koreanName,
@@ -144,7 +152,6 @@ const VirtualLeftTargetPanel: React.FC<Props> = ({
const active = item.market === selectedMarket;
return (
<div
key={item.market}
className={[
'vtd-target-item',
active ? 'vtd-target-item--active' : '',
@@ -213,8 +220,9 @@ const VirtualLeftTargetPanel: React.FC<Props> = ({
</div>
</div>
);
})}
</div>
}}
</VirtualScroll>
)}
</section>
</div>
);
@@ -17,6 +17,7 @@ import {
getTradeNotificationGridPreset,
type TradeNotificationGridPresetId,
} from '../../utils/tradeNotificationGridPresets';
import { VirtualGridScroll } from '../common/VirtualScroll';
interface Props {
targets: VirtualTargetItem[];
@@ -60,6 +61,17 @@ const VirtualTargetGrid: React.FC<Props> = ({
gridPreset,
}) => {
const gridCols = getTradeNotificationGridPreset(gridPreset).cols;
const gridRowEstimate = useMemo(() => {
if (viewMode === 'detail') return 540;
if (globalDisplayMode === 'chart') return 460;
return 400;
}, [viewMode, globalDisplayMode]);
const gridRemeasureKey = useMemo(
() => `${viewMode}:${globalDisplayMode}:${targets.map(t => t.market).join(',')}:${Object.values(snapshots).map(s => s?.updatedAt ?? 0).join('|')}`,
[viewMode, globalDisplayMode, targets, snapshots],
);
const [cardDisplayOverrides, setCardDisplayOverrides] = useState<Record<string, VirtualCardDisplayMode>>({});
const [focusMarket, setFocusMarket] = useState<string | null>(null);
@@ -144,20 +156,29 @@ const VirtualTargetGrid: React.FC<Props> = ({
englishName={focusTarget.englishName}
/>
) : (
<div
<VirtualGridScroll
className={[
'vtd-grid',
'vtd-grid--layout-preset',
'vtd-grid-scroll',
'vl-scroll',
`vtd-grid--${viewMode}`,
`vtd-grid--cols-${gridCols}`,
].join(' ')}
style={{ ['--vtd-grid-cols' as string]: gridCols }}
rowClassName="vl-grid-row vtd-grid-row"
items={targets}
columns={gridCols}
estimateRowSize={gridRowEstimate}
rowGap={14}
measureDynamic
remeasureKey={gridRemeasureKey}
overscan={2}
getItemKey={t => t.market}
aria-label="가상매매 종목 그리드"
>
{targets.map(t => {
{t => {
const strat = strategies.find(s => s.id === resolveVirtualTargetStrategyId(t, session.globalStrategyId));
return (
<VirtualTargetCard
key={t.market}
market={t.market}
strategy={strat}
strategies={strategies}
@@ -178,16 +199,16 @@ const VirtualTargetGrid: React.FC<Props> = ({
theme={theme}
chartRealtimeSource={chartRealtimeSource}
chartCandleAreaPriceLabels={chartCandleAreaPriceLabels}
chartSeriesPriceLabels={chartSeriesPriceLabels}
chartPaneSeparator={chartPaneSeparator}
chartSeriesPriceLabels={chartSeriesPriceLabels}
chartPaneSeparator={chartPaneSeparator}
chartLiveReceiveHighlight={chartLiveReceiveHighlight}
ticker={tickers?.get(t.market)}
koreanName={t.koreanName}
englishName={t.englishName}
/>
);
})}
</div>
}}
</VirtualGridScroll>
)}
</div>
);