125 lines
4.1 KiB
TypeScript
125 lines
4.1 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import type { TrendSearchResultDto } from '../../utils/trendSearchApi';
|
|
import { sortTrendSearchByStrength } from '../../utils/trendSearchMetrics';
|
|
import type { Theme } from '../../types';
|
|
import type { ChartRealtimeSource } from '../../hooks/useChartRealtimeData';
|
|
import type { TickerData } from '../../hooks/useMarketTicker';
|
|
import TrendSearchResultCard, { type TrendSearchDisplayMode } from './TrendSearchResultCard';
|
|
import { VirtualGridScroll } from '../common/VirtualScroll';
|
|
|
|
interface Props {
|
|
results: TrendSearchResultDto[];
|
|
timeframe: string;
|
|
displayMode: TrendSearchDisplayMode;
|
|
loading?: boolean;
|
|
selectedMarket?: string | null;
|
|
onSelect?: (row: TrendSearchResultDto) => void;
|
|
flashMarkets?: Set<string>;
|
|
theme?: Theme;
|
|
chartRealtimeSource?: ChartRealtimeSource;
|
|
chartCandleAreaPriceLabels?: boolean;
|
|
chartSeriesPriceLabels?: boolean;
|
|
chartPaneSeparator?: import('../../types/chartPaneSeparator').ChartPaneSeparatorOptions;
|
|
tickers?: Map<string, TickerData>;
|
|
lastUpdatedAt?: number;
|
|
isInTargets?: (market: string) => boolean;
|
|
isPinned?: (market: string) => boolean;
|
|
onAddTarget?: (row: TrendSearchResultDto) => void;
|
|
onRemoveTarget?: (market: string) => void;
|
|
targetBusyMarket?: string | null;
|
|
}
|
|
|
|
const TrendSearchResultsCardGrid: React.FC<Props> = ({
|
|
results,
|
|
timeframe,
|
|
displayMode,
|
|
loading,
|
|
selectedMarket,
|
|
onSelect,
|
|
flashMarkets,
|
|
theme = 'dark',
|
|
chartRealtimeSource = 'BACKEND_STOMP',
|
|
chartCandleAreaPriceLabels = true,
|
|
chartSeriesPriceLabels = true,
|
|
chartPaneSeparator,
|
|
tickers,
|
|
lastUpdatedAt,
|
|
isInTargets,
|
|
isPinned,
|
|
onAddTarget,
|
|
onRemoveTarget,
|
|
targetBusyMarket = null,
|
|
}) => {
|
|
const sortedResults = useMemo(
|
|
() => sortTrendSearchByStrength(results),
|
|
[results],
|
|
);
|
|
|
|
if (loading && results.length === 0) {
|
|
return (
|
|
<div className="vtd-grid-wrap">
|
|
<div className="vtd-grid-empty">
|
|
<p className="vtd-muted">스캔 중…</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!loading && results.length === 0) {
|
|
return (
|
|
<div className="vtd-grid-wrap">
|
|
<div className="vtd-grid-empty">
|
|
<p className="vtd-muted">검색 결과가 없습니다. 조건·캔들 주기를 조정하거나 잠시 후 다시 시도해 보세요.</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const gridCols = displayMode === 'chart' ? 2 : 3;
|
|
const rowEstimate = displayMode === 'chart' ? 420 : 300;
|
|
|
|
return (
|
|
<div className="vtd-grid-wrap vtd-grid-wrap--virtual">
|
|
<VirtualGridScroll
|
|
className={`vtd-grid-scroll vl-scroll${displayMode === 'chart' ? ' vtd-grid--chart-mode' : ''}`}
|
|
style={{ ['--vtd-grid-cols' as string]: gridCols }}
|
|
rowClassName="vl-grid-row vtd-grid-row"
|
|
items={sortedResults}
|
|
columns={gridCols}
|
|
estimateRowSize={rowEstimate}
|
|
rowGap={14}
|
|
measureDynamic
|
|
remeasureKey={`${displayMode}:${sortedResults.length}`}
|
|
overscan={2}
|
|
getItemKey={row => row.market}
|
|
aria-label="추세검색 카드 그리드"
|
|
>
|
|
{row => (
|
|
<TrendSearchResultCard
|
|
result={row}
|
|
timeframe={timeframe}
|
|
displayMode={displayMode}
|
|
selected={selectedMarket === row.market}
|
|
onSelect={onSelect ? () => onSelect(row) : undefined}
|
|
theme={theme}
|
|
chartRealtimeSource={chartRealtimeSource}
|
|
chartCandleAreaPriceLabels={chartCandleAreaPriceLabels}
|
|
chartSeriesPriceLabels={chartSeriesPriceLabels}
|
|
chartPaneSeparator={chartPaneSeparator}
|
|
ticker={tickers?.get(row.market)}
|
|
updatedAt={lastUpdatedAt}
|
|
flash={flashMarkets?.has(row.market)}
|
|
inTargets={isInTargets?.(row.market) ?? false}
|
|
targetPinned={isPinned?.(row.market) ?? false}
|
|
targetBusy={targetBusyMarket === row.market}
|
|
onAddTarget={onAddTarget ? () => onAddTarget(row) : undefined}
|
|
onRemoveTarget={onRemoveTarget ? () => onRemoveTarget(row.market) : undefined}
|
|
/>
|
|
)}
|
|
</VirtualGridScroll>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default TrendSearchResultsCardGrid;
|