추세검색 화면 적용

This commit is contained in:
Macbook
2026-05-26 00:51:07 +09:00
parent 1e950c7db4
commit 1e11884fef
29 changed files with 3753 additions and 71 deletions
@@ -0,0 +1,111 @@
import React, { useMemo } from 'react';
import type { TrendSearchResultDto } from '../../utils/trendSearchApi';
interface Props {
results: TrendSearchResultDto[];
selectedMarket: string | null;
onSelect: (row: TrendSearchResultDto) => void;
loading?: boolean;
flashMarkets?: Set<string>;
}
function fmtPrice(n: number): string {
if (n >= 1_000_000) return n.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
if (n >= 100) return n.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
return n.toLocaleString('ko-KR', { maximumFractionDigits: 4 });
}
function fmtPct(n: number): string {
const sign = n >= 0 ? '+' : '';
return `${sign}${n.toFixed(1)}%`;
}
function coinLabel(market: string, name?: string): string {
const code = market.replace(/^KRW-/, '');
return name ? `${code}/KRW` : `${code}/KRW`;
}
const TrendSearchResultsGrid: React.FC<Props> = ({
results,
selectedMarket,
onSelect,
loading,
flashMarkets,
}) => {
const avgMatch = useMemo(() => {
if (!results.length) return 0;
return Math.round(results.reduce((s, r) => s + r.matchRate, 0) / results.length);
}, [results]);
return (
<div className="tsd-results">
<div className="tsd-results-head">
<div>
<h3 className="tsd-panel-title"> </h3>
<p className="tsd-panel-sub">Search Condition Match Rate % Sorting · 100ms </p>
</div>
<div className="tsd-results-meta">
<span className="tsd-meta-chip"> {avgMatch}%</span>
<span className="tsd-meta-chip tsd-meta-chip--live"> LIVE</span>
</div>
</div>
<div className="tsd-results-table-wrap">
<table className="tsd-results-table">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<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>
</div>
</div>
);
};
export default TrendSearchResultsGrid;