목록 로딩 로직 개선
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { TrendSearchResultDto } from '../../utils/trendSearchApi';
|
||||
import { formatUpbitKrwPrice } from '../../utils/safeFormat';
|
||||
import { VirtualScroll } from '../common/VirtualScroll';
|
||||
|
||||
interface Props {
|
||||
results: TrendSearchResultDto[];
|
||||
@@ -36,6 +37,10 @@ const TrendSearchResultsGrid: React.FC<Props> = ({
|
||||
return Math.round(results.reduce((s, r) => s + r.matchRate, 0) / results.length);
|
||||
}, [results]);
|
||||
|
||||
const emptyBody = loading && results.length === 0
|
||||
? <div className="tsd-virtual-empty tsd-empty">스캔 중…</div>
|
||||
: <div className="tsd-virtual-empty tsd-empty">조건에 맞는 종목이 없습니다. 필터를 조정해 보세요.</div>;
|
||||
|
||||
return (
|
||||
<div className="tsd-results">
|
||||
<div className="tsd-results-head">
|
||||
@@ -49,8 +54,8 @@ const TrendSearchResultsGrid: React.FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="tsd-results-table-wrap">
|
||||
<table className="tsd-results-table">
|
||||
<div className="tsd-results-table-wrap tsd-results-table-wrap--virtual">
|
||||
<table className="tsd-results-table tsd-results-table--head">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>종목명</th>
|
||||
@@ -60,48 +65,51 @@ const TrendSearchResultsGrid: React.FC<Props> = ({
|
||||
<th>비고</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading && results.length === 0 && (
|
||||
<tr><td colSpan={5} className="tsd-empty">스캔 중…</td></tr>
|
||||
)}
|
||||
{!loading && results.length === 0 && (
|
||||
<tr><td colSpan={5} className="tsd-empty">조건에 맞는 종목이 없습니다. 필터를 조정해 보세요.</td></tr>
|
||||
)}
|
||||
{results.map(row => {
|
||||
const up = row.changeRate >= 0;
|
||||
const active = selectedMarket === row.market;
|
||||
const flash = flashMarkets?.has(row.market);
|
||||
return (
|
||||
<tr
|
||||
key={row.market}
|
||||
className={[
|
||||
active ? 'tsd-row--sel' : '',
|
||||
flash ? (up ? 'tsd-row--flash-up' : 'tsd-row--flash-down') : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
onClick={() => onSelect(row)}
|
||||
>
|
||||
<td className="tsd-col-symbol">
|
||||
<span className="tsd-symbol">{coinLabel(row.market, row.koreanName)}</span>
|
||||
</td>
|
||||
<td className="tsd-col-price">{fmtPrice(row.currentPrice)}</td>
|
||||
<td className={`tsd-col-change${up ? ' up' : ' down'}`}>{fmtPct(row.changeRate)}</td>
|
||||
<td className="tsd-col-match">
|
||||
<div className="tsd-match-bar-wrap">
|
||||
<div className="tsd-match-bar">
|
||||
<div className="tsd-match-bar-fill" style={{ width: `${row.matchRate}%` }} />
|
||||
</div>
|
||||
<span className="tsd-match-pct">{row.matchRate}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="tsd-col-badge">
|
||||
{row.highMatch && <span className="tsd-badge-high">HIGH MATCH</span>}
|
||||
{!row.highMatch && row.matchRate >= 70 && <span className="tsd-badge-mid">MATCH</span>}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<VirtualScroll
|
||||
className="tsd-virtual-body vl-scroll"
|
||||
items={results}
|
||||
estimateSize={40}
|
||||
getItemKey={row => row.market}
|
||||
empty={emptyBody}
|
||||
role="rowgroup"
|
||||
aria-label="추세검색 결과"
|
||||
>
|
||||
{row => {
|
||||
const up = row.changeRate >= 0;
|
||||
const active = selectedMarket === row.market;
|
||||
const flash = flashMarkets?.has(row.market);
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
'tsd-virtual-row',
|
||||
active ? 'tsd-row--sel' : '',
|
||||
flash ? (up ? 'tsd-row--flash-up' : 'tsd-row--flash-down') : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
role="row"
|
||||
onClick={() => onSelect(row)}
|
||||
>
|
||||
<div className="tsd-virtual-cell tsd-col-symbol">
|
||||
<span className="tsd-symbol">{coinLabel(row.market, row.koreanName)}</span>
|
||||
</div>
|
||||
<div className="tsd-virtual-cell tsd-col-price">{fmtPrice(row.currentPrice)}</div>
|
||||
<div className={`tsd-virtual-cell tsd-col-change${up ? ' up' : ' down'}`}>{fmtPct(row.changeRate)}</div>
|
||||
<div className="tsd-virtual-cell tsd-col-match">
|
||||
<div className="tsd-match-bar-wrap">
|
||||
<div className="tsd-match-bar">
|
||||
<div className="tsd-match-bar-fill" style={{ width: `${row.matchRate}%` }} />
|
||||
</div>
|
||||
<span className="tsd-match-pct">{row.matchRate}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tsd-virtual-cell tsd-col-badge">
|
||||
{row.highMatch && <span className="tsd-badge-high">HIGH MATCH</span>}
|
||||
{!row.highMatch && row.matchRate >= 70 && <span className="tsd-badge-mid">MATCH</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</VirtualScroll>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user