전략편집기 템플릿 저장 기능 추가
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
* 관심 - 즐겨찾기 목록 (localStorage 관리)
|
||||
*/
|
||||
import React, { useState, useCallback, useMemo, useRef, useEffect, memo } from 'react';
|
||||
import type { TickerData, ChangeDir } from '../hooks/useMarketTicker';
|
||||
import type { TickerData, ChangeDir, MarketInfo } from '../hooks/useMarketTicker';
|
||||
import {
|
||||
getFavorites, saveFavorites, toggleFavorite,
|
||||
getHoldings, addHolding, removeHolding,
|
||||
@@ -22,6 +22,8 @@ type SortDir = 'asc' | 'desc';
|
||||
export interface MarketPanelProps {
|
||||
open: boolean;
|
||||
tickers: Map<string, TickerData>;
|
||||
/** 업비트 KRW 전 종목 (종목검색 드롭다운과 동일 소스) */
|
||||
marketInfos: MarketInfo[];
|
||||
loading: boolean;
|
||||
usdRate: number; // KRW per 1 USD (from KRW-USDT)
|
||||
activeSymbol: string;
|
||||
@@ -59,6 +61,7 @@ const ChangeArrow = memo(function ChangeArrow({ change }: { change: ChangeDir })
|
||||
interface RowProps {
|
||||
ticker: TickerData | undefined;
|
||||
market: string;
|
||||
koreanName: string;
|
||||
isActive: boolean;
|
||||
isFav: boolean;
|
||||
isHold: boolean;
|
||||
@@ -72,7 +75,7 @@ interface RowProps {
|
||||
}
|
||||
|
||||
const MarketRow = memo(function MarketRow({
|
||||
ticker, market, isActive, isFav, isHold,
|
||||
ticker, market, koreanName, isActive, isFav, isHold,
|
||||
showUsd, showHoldBtn, usdRate,
|
||||
onSelect, onToggleFav, onToggleHold, onRemoveHold,
|
||||
}: RowProps) {
|
||||
@@ -116,7 +119,7 @@ const MarketRow = memo(function MarketRow({
|
||||
<div className="mp-name">
|
||||
<span className="mp-korean-row">
|
||||
<ChangeArrow change={change} />
|
||||
<span className="mp-korean">{ticker?.koreanName ?? code}</span>
|
||||
<span className="mp-korean">{koreanName}</span>
|
||||
</span>
|
||||
<span className="mp-code">{code}/KRW</span>
|
||||
</div>
|
||||
@@ -151,7 +154,7 @@ const MarketRow = memo(function MarketRow({
|
||||
|
||||
// ─── 메인 패널 ─────────────────────────────────────────────────────────────
|
||||
export const MarketPanel: React.FC<MarketPanelProps> = ({
|
||||
open, tickers, loading, usdRate, activeSymbol, onSymbolSelect,
|
||||
open, tickers, marketInfos, loading, usdRate, activeSymbol, onSymbolSelect,
|
||||
}) => {
|
||||
const [tab, setTab] = useState<Tab>('krw');
|
||||
const [search, setSearch] = useState('');
|
||||
@@ -214,14 +217,27 @@ export const MarketPanel: React.FC<MarketPanelProps> = ({
|
||||
setShowHoldAdd(false);
|
||||
}, []);
|
||||
|
||||
// 탭별 마켓 목록 계산
|
||||
const koreanNameByMarket = useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
marketInfos.forEach(m => map.set(m.market, m.koreanName));
|
||||
return map;
|
||||
}, [marketInfos]);
|
||||
|
||||
const resolveKoreanName = useCallback((market: string) =>
|
||||
tickers.get(market)?.koreanName
|
||||
?? koreanNameByMarket.get(market)
|
||||
?? market.replace(/^KRW-/, ''),
|
||||
[tickers, koreanNameByMarket]);
|
||||
|
||||
// 탭별 마켓 목록 — market/all 전 종목 (종목검색 드롭다운과 동일)
|
||||
const allKrwMarkets = useMemo(() => {
|
||||
const keys = Array.from(tickers.keys()).filter(m => m.startsWith('KRW-'));
|
||||
// 24h 거래대금 내림차순 정렬
|
||||
const keys = marketInfos.length > 0
|
||||
? marketInfos.map(m => m.market)
|
||||
: Array.from(tickers.keys()).filter(m => m.startsWith('KRW-'));
|
||||
return keys.sort((a, b) =>
|
||||
(tickers.get(b)?.accTradePrice24 ?? 0) - (tickers.get(a)?.accTradePrice24 ?? 0)
|
||||
);
|
||||
}, [tickers]);
|
||||
}, [marketInfos, tickers]);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
let base: string[] = [];
|
||||
@@ -234,7 +250,7 @@ export const MarketPanel: React.FC<MarketPanelProps> = ({
|
||||
const q = search.toUpperCase();
|
||||
base = base.filter(m => {
|
||||
const code = m.replace(/^KRW-/, '');
|
||||
const name = tickers.get(m)?.koreanName ?? '';
|
||||
const name = resolveKoreanName(m);
|
||||
return code.includes(q) || name.includes(search);
|
||||
});
|
||||
}
|
||||
@@ -245,8 +261,8 @@ export const MarketPanel: React.FC<MarketPanelProps> = ({
|
||||
const tb = tickers.get(b);
|
||||
switch (sortKey) {
|
||||
case 'name': {
|
||||
const na = ta?.koreanName ?? a.replace(/^KRW-/, '');
|
||||
const nb = tb?.koreanName ?? b.replace(/^KRW-/, '');
|
||||
const na = resolveKoreanName(a);
|
||||
const nb = resolveKoreanName(b);
|
||||
return mul * na.localeCompare(nb, 'ko');
|
||||
}
|
||||
case 'price':
|
||||
@@ -257,7 +273,7 @@ export const MarketPanel: React.FC<MarketPanelProps> = ({
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}, [tab, allKrwMarkets, holdings, favorites, search, tickers, sortKey, sortDir]);
|
||||
}, [tab, allKrwMarkets, holdings, favorites, search, tickers, sortKey, sortDir, resolveKoreanName]);
|
||||
|
||||
// 보유 탭 "추가" 검색 결과
|
||||
const holdAddResults = useMemo(() => {
|
||||
@@ -266,11 +282,11 @@ export const MarketPanel: React.FC<MarketPanelProps> = ({
|
||||
return allKrwMarkets
|
||||
.filter(m => {
|
||||
const code = m.replace(/^KRW-/, '');
|
||||
const name = tickers.get(m)?.koreanName ?? '';
|
||||
const name = resolveKoreanName(m);
|
||||
return code.includes(q) || name.includes(holdSearch);
|
||||
})
|
||||
.slice(0, 10);
|
||||
}, [holdSearch, allKrwMarkets, tickers]);
|
||||
}, [holdSearch, allKrwMarkets, resolveKoreanName]);
|
||||
|
||||
const tabLabels: Record<Tab, string> = {
|
||||
krw: '원화', btc: 'BTC', hold: '보유', fav: '관심',
|
||||
@@ -352,7 +368,7 @@ export const MarketPanel: React.FC<MarketPanelProps> = ({
|
||||
<div className="mp-hold-add-dropdown">
|
||||
{holdAddResults.map(m => (
|
||||
<div key={m} className="mp-hold-add-item" onClick={() => handleAddHolding(m)}>
|
||||
<span>{tickers.get(m)?.koreanName ?? m.replace(/^KRW-/, '')}</span>
|
||||
<span>{resolveKoreanName(m)}</span>
|
||||
<span className="mp-hold-add-code">{m.replace(/^KRW-/, '')}</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -388,6 +404,7 @@ export const MarketPanel: React.FC<MarketPanelProps> = ({
|
||||
<MarketRow
|
||||
key={market}
|
||||
market={market}
|
||||
koreanName={resolveKoreanName(market)}
|
||||
ticker={tickers.get(market)}
|
||||
isActive={market === activeSymbol}
|
||||
isFav={favorites.includes(market)}
|
||||
|
||||
Reference in New Issue
Block a user