가상투자 화면 추가

This commit is contained in:
Macbook
2026-05-25 13:14:36 +09:00
parent 1fe3e404fa
commit 339e2355eb
23 changed files with 2230 additions and 457 deletions
@@ -0,0 +1,70 @@
import React, { memo, useEffect, useRef, useState } from 'react';
import type { ChangeDir, TickerData } from '../../hooks/useMarketTicker';
function fmtKrw(price: number | null | undefined): string {
if (price == null || !Number.isFinite(price)) return '-';
if (price >= 1_000_000) return price.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
if (price >= 1_000) return price.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
if (price >= 1) return price.toLocaleString('ko-KR', { maximumFractionDigits: 2 });
if (price >= 0.01) return price.toFixed(4);
return price.toFixed(8);
}
function fmtPct(rate: number | null | undefined): string {
if (rate == null || !Number.isFinite(rate)) return '-';
const sign = rate >= 0 ? '+' : '';
return `${sign}${(rate * 100).toFixed(2)}%`;
}
const ChangeArrow = memo(function ChangeArrow({ change }: { change: ChangeDir }) {
if (change === 'RISE') return <span className="vtd-target-arrow vtd-target-arrow--rise"></span>;
if (change === 'FALL') return <span className="vtd-target-arrow vtd-target-arrow--fall"></span>;
return <span className="vtd-target-arrow vtd-target-arrow--even"></span>;
});
interface Props {
market: string;
ticker?: TickerData;
}
const VirtualTargetQuote: React.FC<Props> = ({ market, ticker }) => {
const code = market.replace(/^KRW-/, '');
const change: ChangeDir = ticker?.change ?? 'EVEN';
const colorCls = change === 'RISE' ? 'vtd-target-up' : change === 'FALL' ? 'vtd-target-dn' : 'vtd-target-even';
const [flashClass, setFlashClass] = useState('');
const prevPriceRef = useRef<number | null>(null);
useEffect(() => {
const price = ticker?.tradePrice ?? null;
if (price === null) return;
const prev = prevPriceRef.current;
if (prev !== null && prev !== price) {
const cls = price > prev ? 'vtd-target-quote--flash-rise' : 'vtd-target-quote--flash-fall';
setFlashClass(cls);
const t = setTimeout(() => setFlashClass(''), 700);
prevPriceRef.current = price;
return () => clearTimeout(t);
}
prevPriceRef.current = price;
}, [ticker?.tradePrice]);
return (
<div className={`vtd-target-quote ${flashClass}`.trim()} aria-label="현재가 및 전일 대비">
<div className="vtd-target-quote-left">
<ChangeArrow change={change} />
<span className="vtd-target-quote-pair">{code}/KRW</span>
</div>
<div className="vtd-target-quote-right">
<span className={`vtd-target-price ${colorCls}`}>
{ticker ? fmtKrw(ticker.tradePrice) : '-'}
</span>
<span className={`vtd-target-change ${colorCls}`}>
{ticker ? fmtPct(ticker.changeRate) : '-'}
</span>
</div>
</div>
);
};
export default VirtualTargetQuote;