..
This commit is contained in:
112
pages/Stocks.tsx
112
pages/Stocks.tsx
@@ -5,6 +5,7 @@ import { StockItem, MarketType, OrderType } from '../types';
|
||||
import StockDetailModal from '../components/StockDetailModal';
|
||||
import TradeModal from '../components/TradeModal';
|
||||
import { StockRow } from '../components/StockRow';
|
||||
import { StockMasterRow } from '../components/StockMasterRow';
|
||||
|
||||
interface StocksProps {
|
||||
marketMode: MarketType;
|
||||
@@ -30,8 +31,8 @@ const Stocks: React.FC<StocksProps> = ({ marketMode, stocks, onAddToWatchlist, w
|
||||
(s.name.includes(search) || s.code.toLowerCase().includes(search.toLowerCase()))
|
||||
);
|
||||
result.sort((a, b) => {
|
||||
const valA = a[sortField] || 0;
|
||||
const valB = b[sortField] || 0;
|
||||
const valA = (a[sortField] as number) || 0;
|
||||
const valB = (b[sortField] as number) || 0;
|
||||
return sortOrder === 'asc' ? (valA > valB ? 1 : -1) : (valB > valA ? 1 : -1);
|
||||
});
|
||||
return result;
|
||||
@@ -43,49 +44,84 @@ const Stocks: React.FC<StocksProps> = ({ marketMode, stocks, onAddToWatchlist, w
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-10 animate-in fade-in duration-500 pb-20">
|
||||
<div className="bg-white p-10 rounded-[3.5rem] shadow-sm border border-slate-100 flex flex-col lg:flex-row gap-8 items-center justify-between">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="p-5 bg-blue-50 text-blue-600 rounded-3xl"><Filter size={28} /></div>
|
||||
<div><h3 className="text-3xl font-black text-slate-900 tracking-tight">종목 마스터</h3></div>
|
||||
<div className="space-y-6 animate-in fade-in duration-500 pb-20">
|
||||
<div className="bg-white p-6 rounded-2xl shadow-sm border border-slate-100 flex flex-col lg:flex-row gap-6 items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-4 bg-blue-50 text-blue-600 rounded-2xl shadow-sm"><Filter size={20} /></div>
|
||||
<div>
|
||||
<h3 className="text-[20px] font-black text-slate-900 tracking-tighter uppercase">종목 마스터</h3>
|
||||
<p className="text-[11px] font-bold text-slate-400">데이터 기반 종목 분석 및 관리</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<input type="text" placeholder="검색..." className="p-4 bg-slate-50 rounded-2xl outline-none border-2 border-transparent focus:border-blue-500" value={search} onChange={(e) => setSearch(e.target.value)} />
|
||||
<button onClick={onSync} className="p-4 bg-slate-900 text-white rounded-2xl flex items-center gap-2"><RotateCw size={18} /> 동기화</button>
|
||||
<div className="flex gap-3 w-full lg:w-auto">
|
||||
<div className="relative flex-1 lg:w-64">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-300" size={16} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="종목명 또는 코드 검색..."
|
||||
className="w-full pl-11 pr-4 py-3 bg-slate-50 rounded-xl outline-none border-2 border-transparent focus:border-blue-500 focus:bg-white transition-all text-sm font-bold"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button onClick={onSync} className="px-5 py-3 bg-slate-900 text-white rounded-xl flex items-center gap-2 hover:bg-slate-800 transition-all text-sm font-black shadow-lg">
|
||||
<RotateCw size={16} /> 동기화
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-[3.5rem] shadow-sm border border-slate-100 overflow-hidden">
|
||||
<table className="w-full text-left">
|
||||
<thead className="bg-slate-50 text-[11px] font-black text-slate-400 uppercase tracking-widest">
|
||||
<tr>
|
||||
<th className="px-8 py-6 cursor-pointer" onClick={() => handleSort('name')}>종목 <ArrowUpDown size={12} className="inline" /></th>
|
||||
<th className="px-8 py-6 cursor-pointer" onClick={() => handleSort('price')}>현재가 <ArrowUpDown size={12} className="inline" /></th>
|
||||
<th className="px-8 py-6">AI 등락률 예탁</th>
|
||||
<th className="px-8 py-6 text-right">액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-50">
|
||||
{filteredStocks.map(stock => {
|
||||
const isWatch = watchlistCodes.includes(stock.code);
|
||||
return (
|
||||
<StockRow
|
||||
key={stock.code}
|
||||
stock={stock}
|
||||
isWatchlisted={isWatch}
|
||||
showActions={true}
|
||||
onTrade={(type) => setTradeContext({ stock, type })}
|
||||
onToggleWatchlist={() => onAddToWatchlist(stock)}
|
||||
onClick={() => setDetailStock(stock)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden">
|
||||
<div className="overflow-x-auto scrollbar-hide">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead className="bg-slate-50/50 text-[10px] font-black text-slate-400 uppercase tracking-widest border-b border-slate-100">
|
||||
<tr>
|
||||
<th className="px-5 py-4 w-12 text-center">No</th>
|
||||
<th className="px-5 py-4 cursor-pointer hover:text-blue-600 transition-colors" onClick={() => handleSort('name')}>
|
||||
종목정보 <ArrowUpDown size={10} className="inline ml-1" />
|
||||
</th>
|
||||
<th className="px-5 py-4 text-right cursor-pointer hover:text-blue-600 transition-colors" onClick={() => handleSort('price')}>
|
||||
현재가 <ArrowUpDown size={10} className="inline ml-1" />
|
||||
</th>
|
||||
<th className="px-5 py-4 text-right">시가/고/저</th>
|
||||
<th className="px-5 py-4 text-right cursor-pointer hover:text-blue-600 transition-colors" onClick={() => handleSort('volume')}>
|
||||
거래량 <ArrowUpDown size={10} className="inline ml-1" />
|
||||
</th>
|
||||
<th className="px-5 py-4">기업건강 (P/R/E)</th>
|
||||
<th className="px-5 py-4 text-center cursor-pointer hover:text-blue-600 transition-colors" onClick={() => handleSort('aiScoreBuy')}>
|
||||
AI SCORE <ArrowUpDown size={10} className="inline ml-1" />
|
||||
</th>
|
||||
<th className="px-5 py-4 text-right">트레이딩 액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-50">
|
||||
{filteredStocks.map((stock, idx) => {
|
||||
const isWatch = watchlistCodes.includes(stock.code);
|
||||
return (
|
||||
<StockMasterRow
|
||||
key={stock.code}
|
||||
rank={idx + 1}
|
||||
stock={stock}
|
||||
isWatchlisted={isWatch}
|
||||
onTrade={(type) => setTradeContext({ stock, type })}
|
||||
onToggleWatchlist={() => onAddToWatchlist(stock)}
|
||||
onClick={() => setDetailStock(stock)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{detailStock && <StockDetailModal stock={detailStock} onClose={() => setDetailStock(null)} />}
|
||||
{tradeContext && <TradeModal stock={tradeContext.stock} type={tradeContext.type} onClose={() => setTradeContext(null)} onExecute={async (o) => alert("주문 예약됨")} />}
|
||||
{tradeContext && (
|
||||
<TradeModal
|
||||
stock={tradeContext.stock}
|
||||
type={tradeContext.type}
|
||||
onClose={() => setTradeContext(null)}
|
||||
onExecute={async (o) => alert("주문 예약됨")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user