118 lines
3.4 KiB
TypeScript
118 lines
3.4 KiB
TypeScript
import type { MarketInfo, TickerData } from '../hooks/useMarketTicker';
|
|
|
|
export type MarketSortKey = 'name' | 'price' | 'change' | 'volume';
|
|
export type MarketSortDir = 'asc' | 'desc';
|
|
|
|
export interface MarketSearchRow {
|
|
market: string;
|
|
korean_name: string;
|
|
english_name: string;
|
|
symbol: string;
|
|
}
|
|
|
|
function compareNullableNum(
|
|
a: number | null | undefined,
|
|
b: number | null | undefined,
|
|
mul: number,
|
|
): number {
|
|
if (a == null && b == null) return 0;
|
|
if (a == null) return 1;
|
|
if (b == null) return -1;
|
|
return mul * (a - b);
|
|
}
|
|
|
|
export function marketInfosToRows(infos: MarketInfo[]): MarketSearchRow[] {
|
|
return infos.map(m => ({
|
|
market: m.market,
|
|
korean_name: m.koreanName,
|
|
english_name: m.englishName,
|
|
symbol: m.market.replace('KRW-', ''),
|
|
}));
|
|
}
|
|
|
|
export function filterMarketRows(rows: MarketSearchRow[], query: string): MarketSearchRow[] {
|
|
const q = query.trim();
|
|
if (!q) return rows;
|
|
const upper = q.toUpperCase();
|
|
const lower = q.toLowerCase();
|
|
return rows.filter(m =>
|
|
m.korean_name.includes(q)
|
|
|| m.symbol.toUpperCase().includes(upper)
|
|
|| m.english_name.toLowerCase().includes(lower)
|
|
|| m.market.toUpperCase().includes(upper),
|
|
);
|
|
}
|
|
|
|
/** MarketSearchPanel 과 동일 — 관심종목 우선·거래대금 또는 사용자 정렬 */
|
|
export function buildSortedMarketRows(
|
|
rows: MarketSearchRow[],
|
|
tickers: Map<string, TickerData>,
|
|
favs: Set<string>,
|
|
sortKey: MarketSortKey | null,
|
|
sortDir: MarketSortDir,
|
|
): MarketSearchRow[] {
|
|
if (!sortKey) {
|
|
return [...rows].sort((a, b) => {
|
|
const fa = favs.has(a.market) ? 1 : 0;
|
|
const fb = favs.has(b.market) ? 1 : 0;
|
|
if (fb !== fa) return fb - fa;
|
|
const va = tickers.get(a.market)?.accTradePrice24 ?? 0;
|
|
const vb = tickers.get(b.market)?.accTradePrice24 ?? 0;
|
|
return vb - va;
|
|
});
|
|
}
|
|
const mul = sortDir === 'asc' ? 1 : -1;
|
|
return [...rows].sort((a, b) => {
|
|
switch (sortKey) {
|
|
case 'name':
|
|
return mul * a.korean_name.localeCompare(b.korean_name, 'ko');
|
|
case 'price':
|
|
return compareNullableNum(
|
|
tickers.get(a.market)?.tradePrice,
|
|
tickers.get(b.market)?.tradePrice,
|
|
mul,
|
|
);
|
|
case 'change':
|
|
return compareNullableNum(
|
|
tickers.get(a.market)?.changeRate,
|
|
tickers.get(b.market)?.changeRate,
|
|
mul,
|
|
);
|
|
case 'volume':
|
|
return mul * (
|
|
(tickers.get(a.market)?.accTradePrice24 ?? 0)
|
|
- (tickers.get(b.market)?.accTradePrice24 ?? 0)
|
|
);
|
|
default:
|
|
return 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
export function buildSortedMarketCodes(
|
|
marketInfos: MarketInfo[],
|
|
tickers: Map<string, TickerData>,
|
|
favs: Set<string>,
|
|
query: string,
|
|
sortKey: MarketSortKey | null,
|
|
sortDir: MarketSortDir,
|
|
): string[] {
|
|
const rows = filterMarketRows(marketInfosToRows(marketInfos), query);
|
|
return buildSortedMarketRows(rows, tickers, favs, sortKey, sortDir).map(r => r.market);
|
|
}
|
|
|
|
export function adjacentMarketInList(
|
|
orderedMarkets: string[],
|
|
currentMarket: string,
|
|
direction: 'prev' | 'next',
|
|
): string | null {
|
|
if (orderedMarkets.length === 0) return null;
|
|
let idx = orderedMarkets.indexOf(currentMarket);
|
|
if (idx < 0) {
|
|
idx = direction === 'next' ? -1 : orderedMarkets.length;
|
|
}
|
|
const nextIdx = direction === 'prev' ? idx - 1 : idx + 1;
|
|
if (nextIdx < 0 || nextIdx >= orderedMarkets.length) return null;
|
|
return orderedMarkets[nextIdx] ?? null;
|
|
}
|