89 lines
3.9 KiB
TypeScript
89 lines
3.9 KiB
TypeScript
|
|
import React, { useState, useMemo, useEffect } from 'react';
|
|
import { Search, ArrowRightLeft, ShieldCheck, Wallet, History, TrendingUp, Info } from 'lucide-react';
|
|
import { StockItem, OrderType, MarketType, TradeOrder } from '../types';
|
|
|
|
interface TradingProps {
|
|
marketMode: MarketType;
|
|
stocks: StockItem[];
|
|
onOrder: (order: Omit<TradeOrder, 'id' | 'timestamp' | 'status'>) => void;
|
|
}
|
|
|
|
const Trading: React.FC<TradingProps> = ({ marketMode, stocks, onOrder }) => {
|
|
const [search, setSearch] = useState('');
|
|
const [selectedStock, setSelectedStock] = useState<StockItem | null>(null);
|
|
const [orderAmount, setOrderAmount] = useState<number>(1);
|
|
const [orderType, setOrderType] = useState<OrderType>(OrderType.BUY);
|
|
|
|
// 모드 변경 시 수량 초기화
|
|
useEffect(() => {
|
|
setOrderAmount(1);
|
|
}, [orderType]);
|
|
|
|
// 시장 변경 시 선택 해제
|
|
useEffect(() => {
|
|
setSelectedStock(null);
|
|
}, [marketMode]);
|
|
|
|
const filteredStocks = useMemo(() => {
|
|
return stocks.filter(s =>
|
|
s.market === marketMode &&
|
|
!s.isHidden &&
|
|
(s.name.includes(search) || s.code.toLowerCase().includes(search.toLowerCase()))
|
|
);
|
|
}, [stocks, search, marketMode]);
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!selectedStock) return;
|
|
|
|
const confirmMsg = `${selectedStock.name} 종목을 ${orderAmount}주 ${orderType === OrderType.BUY ? '매수' : '매도'}하시겠습니까?`;
|
|
if(window.confirm(confirmMsg)) {
|
|
onOrder({
|
|
stockCode: selectedStock.code,
|
|
stockName: selectedStock.name,
|
|
type: orderType,
|
|
price: selectedStock.price,
|
|
quantity: orderAmount
|
|
});
|
|
}
|
|
};
|
|
|
|
const isBuyMode = orderType === OrderType.BUY;
|
|
|
|
return (
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-10 animate-in fade-in duration-500 pb-20 max-w-[1600px] mx-auto">
|
|
{/* Left: Inventory List */}
|
|
<div className="lg:col-span-7 space-y-8">
|
|
<div className="bg-white p-10 rounded-[3.5rem] shadow-sm border border-slate-100 flex flex-col h-[750px]">
|
|
<div className="flex flex-col sm:flex-row gap-8 justify-between items-center mb-10">
|
|
<div>
|
|
<h3 className="text-2xl font-black text-slate-800 uppercase tracking-tight flex items-center gap-3">
|
|
<Wallet size={24} className="text-blue-600" /> {marketMode === MarketType.DOMESTIC ? '국내' : '해외'} 자산 인벤토리
|
|
</h3>
|
|
</div>
|
|
<div className="relative w-full sm:w-[350px]">
|
|
<Search size={20} className="absolute left-6 top-1/2 -translate-y-1/2 text-slate-400" />
|
|
<input
|
|
type="text"
|
|
placeholder="종목명 또는 코드 검색..."
|
|
className="w-full pl-14 pr-6 py-4 bg-slate-50 border-2 border-transparent rounded-[1.8rem] focus:border-blue-500 focus:bg-white outline-none text-sm font-bold shadow-inner transition-all"
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto pr-2 scrollbar-hide space-y-4">
|
|
{filteredStocks.map(stock => (
|
|
<div
|
|
key={stock.code}
|
|
onClick={() => setSelectedStock(stock)}
|
|
className={`p-6 rounded-[2.5rem] border-2 transition-all cursor-pointer group flex items-center justify-between ${selectedStock?.code === stock.code ? 'border-blue-500 bg-blue-50/20 shadow-lg' : 'border-transparent bg-slate-50/60 hover:bg-white hover:border-slate-200'}`}
|
|
>
|
|
<div className="flex items-center gap-5">
|
|
<div className="w-12 h-12 rounded-2xl bg-slate-900 flex items-center justify-center font-black text-white text-[12px] uppercase">
|
|
{stock.name[0]}
|
|
</div>
|
|
<div>
|
|
<h4 className="font-black text-slate |