This commit is contained in:
2026-02-01 15:25:08 +09:00
parent 01acc19401
commit 35dfce6818
6 changed files with 305 additions and 74 deletions

View File

@@ -0,0 +1,143 @@
import React from 'react';
import { Zap, ShoppingCart, Star, TrendingUp, TrendingDown } from 'lucide-react';
import { StockItem, MarketType, OrderType } from '../types';
interface StockMasterRowProps {
stock: StockItem;
rank?: number;
isWatchlisted?: boolean;
onClick?: () => void;
onTrade?: (type: OrderType) => void;
onToggleWatchlist?: () => void;
}
export const StockMasterRow: React.FC<StockMasterRowProps> = ({
stock, rank, isWatchlisted, onClick, onTrade, onToggleWatchlist
}) => {
const formatValue = (val?: number) => {
if (val === undefined) return '-';
return stock.market === MarketType.DOMESTIC ? val.toLocaleString() : '$' + val;
};
const formatVolume = (vol: number) => {
if (vol >= 1000000) return (vol / 1000000).toFixed(1) + 'M';
if (vol >= 1000) return (vol / 1000).toFixed(1) + 'K';
return vol.toString();
};
return (
<tr
onClick={onClick}
className="group cursor-pointer transition-colors hover:bg-blue-50/40 border-b border-slate-50 last:border-0"
>
{/* 1. 번호 (순위) */}
<td className="px-5 py-4 font-mono font-bold text-slate-300 group-hover:text-blue-500 text-[11px]">
{rank !== undefined ? rank.toString().padStart(2, '0') : '-'}
</td>
{/* 2. 종목 기본 정보 */}
<td className="px-5 py-4">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-slate-100 flex items-center justify-center text-slate-600 text-[10px] font-black group-hover:bg-slate-900 group-hover:text-white transition-all">
{stock.name[0]}
</div>
<div className="flex flex-col min-w-0">
<span className="font-black text-slate-800 text-[13px] truncate tracking-tight">{stock.name}</span>
<span className="text-[10px] text-slate-400 font-mono font-bold">{stock.code}</span>
</div>
</div>
</td>
{/* 3. 현재가 및 등락 */}
<td className="px-5 py-4">
<div className="flex flex-col items-end">
<span className="font-mono font-black text-slate-900 text-[13px]">{formatValue(stock.price)}</span>
<div className={`flex items-center gap-1 text-[10px] font-bold ${stock.changePercent >= 0 ? 'text-rose-500' : 'text-blue-600'}`}>
{stock.changePercent >= 0 ? <TrendingUp size={10} /> : <TrendingDown size={10} />}
{Math.abs(stock.changePercent)}%
</div>
</div>
</td>
{/* 4. 시가 / 고가 / 저가 */}
<td className="px-5 py-4 text-right">
<div className="flex flex-col">
<span className="text-[10px] text-slate-400 font-bold">: {formatValue(stock.openPrice)}</span>
<div className="flex gap-2 justify-end text-[10px] font-bold">
<span className="text-rose-400">: {formatValue(stock.highPrice)}</span>
<span className="text-blue-400">: {formatValue(stock.lowPrice)}</span>
</div>
</div>
</td>
{/* 5. 거래량 및 거래대금 */}
<td className="px-5 py-4 text-right">
<div className="flex flex-col">
<span className="text-[11px] font-black text-slate-700 font-mono">{formatVolume(stock.volume)}</span>
{stock.tradingValue && (
<span className="text-[9px] text-slate-400 font-bold uppercase tracking-tighter">
{stock.market === MarketType.DOMESTIC ? (stock.tradingValue / 100000000).toFixed(1) + '억' : '$' + (stock.tradingValue / 1000000).toFixed(1) + 'M'}
</span>
)}
</div>
</td>
{/* 6. 기업 건강상태 (Fundamental) */}
<td className="px-5 py-4">
<div className="flex flex-col gap-1">
<div className="flex justify-between items-center gap-2">
<span className="text-[9px] font-black text-slate-400 uppercase">PER/PBR</span>
<span className="text-[11px] font-mono font-bold text-slate-700">{stock.per || '-'} / {stock.pbr || '-'}</span>
</div>
<div className="flex justify-between items-center gap-2">
<span className="text-[9px] font-black text-slate-400 uppercase">ROE/DY</span>
<span className="text-[11px] font-mono font-bold text-slate-700">{stock.roe || '-'}% / {stock.dividendYield || '-'}%</span>
</div>
</div>
</td>
{/* 7. AI 스코어 (매수/매도) */}
<td className="px-5 py-4">
<div className="flex items-center justify-end gap-3">
<div className="flex flex-col items-center">
<span className="text-[8px] font-black text-rose-300 uppercase">Buy</span>
<span className="text-[14px] font-black text-rose-500 font-mono italic">{stock.aiScoreBuy}</span>
</div>
<div className="w-[1px] h-6 bg-slate-100" />
<div className="flex flex-col items-center">
<span className="text-[8px] font-black text-blue-300 uppercase">Sell</span>
<span className="text-[14px] font-black text-blue-500 font-mono italic">{stock.aiScoreSell}</span>
</div>
</div>
</td>
{/* 7. 액션 버튼 */}
<td className="px-5 py-4 text-right">
<div className="flex gap-2 justify-end">
<button
onClick={(e) => { e.stopPropagation(); onToggleWatchlist?.(); }}
className={`p-2 rounded-xl transition-all ${isWatchlisted ? 'bg-amber-50 text-amber-500' : 'bg-slate-50 text-slate-300 hover:text-amber-500'}`}
>
<Star size={14} fill={isWatchlisted ? "currentColor" : "none"} />
</button>
<div className="w-[1px] h-4 bg-slate-200 self-center mx-1" />
<button
onClick={(e) => { e.stopPropagation(); onTrade?.(OrderType.BUY); }}
className="px-3 py-1.5 bg-slate-900 text-white rounded-xl hover:bg-rose-600 transition-all text-[11px] font-black flex items-center gap-1.5"
>
<Zap size={10} fill="currentColor" />
</button>
<button
onClick={(e) => { e.stopPropagation(); onTrade?.(OrderType.SELL); }}
className="px-3 py-1.5 border-2 border-slate-900 text-slate-900 rounded-xl hover:bg-blue-600 hover:border-blue-600 hover:text-white transition-all text-[11px] font-black flex items-center gap-1.5"
>
<ShoppingCart size={10} />
</button>
</div>
</td>
</tr>
);
};