91 lines
4.1 KiB
TypeScript
91 lines
4.1 KiB
TypeScript
|
|
import React from 'react';
|
|
import { Zap, ShoppingCart, Star } from 'lucide-react';
|
|
import { StockItem, MarketType, OrderType } from '../types';
|
|
|
|
interface StockRowProps {
|
|
stock: StockItem;
|
|
rank?: number;
|
|
showRank?: boolean;
|
|
showActions?: boolean;
|
|
showRatioBar?: boolean;
|
|
showPL?: { pl: number; percent: number };
|
|
isWatchlisted?: boolean;
|
|
onClick?: () => void;
|
|
onTrade?: (type: OrderType) => void;
|
|
onToggleWatchlist?: () => void;
|
|
}
|
|
|
|
export const StockRow: React.FC<StockRowProps> = ({
|
|
stock, rank, showRank, showActions, showRatioBar, showPL, isWatchlisted, onClick, onTrade, onToggleWatchlist
|
|
}) => {
|
|
return (
|
|
<tr
|
|
onClick={onClick}
|
|
className="group cursor-pointer transition-colors hover:bg-slate-50/70"
|
|
>
|
|
{showRank && (
|
|
<td className="pl-4 py-2 font-mono font-black text-slate-400 group-hover:text-blue-600 transition-colors text-[12px]">
|
|
{rank}
|
|
</td>
|
|
)}
|
|
<td className="px-3 py-2">
|
|
<div className="flex items-center gap-3">
|
|
{onToggleWatchlist && (
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); onToggleWatchlist(); }}
|
|
className="text-slate-300 hover:text-amber-500 transition-colors"
|
|
>
|
|
<Star size={16} fill={isWatchlisted ? "currentColor" : "none"} className={isWatchlisted ? "text-amber-500" : ""} />
|
|
</button>
|
|
)}
|
|
<div className="w-8 h-8 rounded-lg bg-slate-900 flex items-center justify-center text-white text-[10px] font-black shadow-sm overflow-hidden">
|
|
{stock.name[0]}
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<span className="font-black text-slate-900 text-[12.5px] tracking-tight group-hover:text-blue-600 leading-tight">{stock.name}</span>
|
|
<span className="text-[9px] text-slate-400 font-mono font-bold">{stock.code}</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className="px-3 py-2 text-right font-mono font-black text-slate-800 text-[12.5px]">
|
|
{stock.market === MarketType.DOMESTIC ? stock.price.toLocaleString() + '원' : '$' + stock.price}
|
|
</td>
|
|
<td className="px-3 py-2 text-right">
|
|
{showPL ? (
|
|
<div className={showPL.pl >= 0 ? 'text-rose-500' : 'text-blue-600'}>
|
|
<p className="font-black text-[12.5px] leading-tight">{showPL.pl.toLocaleString()}</p>
|
|
<p className="text-[9px] font-bold opacity-80 mt-0.5">({showPL.percent.toFixed(2)}%)</p>
|
|
</div>
|
|
) : (
|
|
<span className={`font-black text-[12.5px] ${stock.changePercent >= 0 ? 'text-rose-500' : 'text-blue-600'}`}>
|
|
{stock.changePercent >= 0 ? '+' : ''}{stock.changePercent}%
|
|
</span>
|
|
)}
|
|
</td>
|
|
{showRatioBar && (
|
|
<td className="px-3 py-2">
|
|
<div className="flex flex-col items-end gap-0.5">
|
|
<div className="w-full h-1 rounded-full bg-slate-100 overflow-hidden flex">
|
|
<div className="h-full bg-rose-400" style={{ width: `${stock.buyRatio || 50}%` }} />
|
|
<div className="h-full bg-blue-400" style={{ width: `${stock.sellRatio || 50}%` }} />
|
|
</div>
|
|
<div className="flex justify-between w-full text-[7.5px] font-black font-mono opacity-60">
|
|
<span className="text-rose-500">{stock.buyRatio || 50}</span>
|
|
<span className="text-blue-500">{stock.sellRatio || 50}</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
)}
|
|
{showActions && onTrade && (
|
|
<td className="px-3 py-2 text-right">
|
|
<div className="flex gap-1 justify-end opacity-0 group-hover:opacity-100 transition-opacity">
|
|
<button onClick={(e) => { e.stopPropagation(); onTrade(OrderType.BUY); }} className="p-1 px-1.5 bg-rose-50 text-rose-500 rounded-md hover:bg-rose-500 hover:text-white transition-all"><Zap size={10} fill="currentColor" /></button>
|
|
<button onClick={(e) => { e.stopPropagation(); onTrade(OrderType.SELL); }} className="p-1 px-1.5 bg-blue-50 text-blue-500 rounded-md hover:bg-blue-500 hover:text-white transition-all"><ShoppingCart size={10} /></button>
|
|
</div>
|
|
</td>
|
|
)}
|
|
</tr>
|
|
);
|
|
};
|