94 lines
4.3 KiB
TypeScript
94 lines
4.3 KiB
TypeScript
|
|
import React, { useState, useMemo } from 'react';
|
|
import { Search, Filter, ArrowUpDown, RotateCw } from 'lucide-react';
|
|
import { StockItem, MarketType, OrderType } from '../types';
|
|
import StockDetailModal from '../components/StockDetailModal';
|
|
import TradeModal from '../components/TradeModal';
|
|
import { StockRow } from '../components/StockRow';
|
|
|
|
interface StocksProps {
|
|
marketMode: MarketType;
|
|
stocks: StockItem[];
|
|
onTrade: (stock: StockItem, type: OrderType) => void;
|
|
onAddToWatchlist: (stock: StockItem) => void;
|
|
watchlistCodes: string[];
|
|
onSync: () => Promise<void>;
|
|
onAddReservedOrder?: (order: any) => Promise<void>;
|
|
}
|
|
|
|
const Stocks: React.FC<StocksProps> = ({ marketMode, stocks, onAddToWatchlist, watchlistCodes, onSync }) => {
|
|
const [search, setSearch] = useState('');
|
|
const [sortField, setSortField] = useState<keyof StockItem>('aiScoreBuy');
|
|
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
|
|
|
const [detailStock, setDetailStock] = useState<StockItem | null>(null);
|
|
const [tradeContext, setTradeContext] = useState<{ stock: StockItem, type: OrderType } | null>(null);
|
|
|
|
const filteredStocks = useMemo(() => {
|
|
let result = stocks.filter(s =>
|
|
s.market === marketMode &&
|
|
(s.name.includes(search) || s.code.toLowerCase().includes(search.toLowerCase()))
|
|
);
|
|
result.sort((a, b) => {
|
|
const valA = a[sortField] || 0;
|
|
const valB = b[sortField] || 0;
|
|
return sortOrder === 'asc' ? (valA > valB ? 1 : -1) : (valB > valA ? 1 : -1);
|
|
});
|
|
return result;
|
|
}, [stocks, search, marketMode, sortField, sortOrder]);
|
|
|
|
const handleSort = (field: keyof StockItem) => {
|
|
if (sortField === field) setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
|
|
else { setSortField(field); setSortOrder('desc'); }
|
|
};
|
|
|
|
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>
|
|
<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>
|
|
</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>
|
|
|
|
{detailStock && <StockDetailModal stock={detailStock} onClose={() => setDetailStock(null)} />}
|
|
{tradeContext && <TradeModal stock={tradeContext.stock} type={tradeContext.type} onClose={() => setTradeContext(null)} onExecute={async (o) => alert("주문 예약됨")} />}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Stocks;
|