대시보드 분리

This commit is contained in:
2026-02-24 16:39:04 +09:00
parent 3e19862c02
commit 1c07a59352
8 changed files with 654 additions and 449 deletions

View File

@@ -0,0 +1,105 @@
import React from 'react'
import { Activity } from 'lucide-react'
interface PortfolioPanelProps {
portfolio: any[] | undefined
livePrices: Record<string, number> | undefined
selectedStock: string
onSelectStock: (code: string) => void
onRefetchAiAnalysis: () => void
onSell: (stockCode: string, stockName: string, quantity: number) => Promise<void>
}
const PortfolioPanel = React.memo(({
portfolio,
livePrices,
selectedStock,
onSelectStock,
onRefetchAiAnalysis,
onSell,
}: PortfolioPanelProps) => {
return (
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl overflow-hidden flex flex-col h-full shadow-lg">
<div className="p-3 border-b border-gray-700/50 flex justify-between items-center bg-[#1c222c]">
<h3 className="font-semibold flex items-center gap-2 text-gray-200 text-sm">
<Activity className="w-4 h-4 text-blue-400" />
보유 종목
</h3>
<span className="text-xs bg-blue-500/20 text-blue-300 px-2 py-0.5 rounded-full">
{portfolio?.length || 0}
</span>
</div>
<div className="divide-y divide-gray-700/50 overflow-y-auto custom-scrollbar flex-1">
{portfolio && portfolio.length > 0 ? (
portfolio.map((stock: any) => {
const currentPrice = livePrices?.[stock.stock_code] ?? Number(stock.current_price || 0)
const avgPrice = Number(stock.average_price || 0)
const quantity = Number(stock.quantity || 0)
const totalValue = currentPrice * quantity
const profitLossRate = avgPrice > 0
? ((currentPrice - avgPrice) / avgPrice) * 100
: Number(stock.profit_loss_rate || 0)
const handleSellClick = async (e: React.MouseEvent) => {
e.stopPropagation()
if (!confirm(`${stock.stock_name} 전량(${quantity}주)을 시장가로 매도하시겠습니까?`)) return
try {
await onSell(stock.stock_code, stock.stock_name, quantity)
} catch (error: any) {
alert(`매도 실패: ${error.response?.data?.detail || error.message}`)
}
}
return (
<div
key={stock.stock_code}
className={`w-full p-3 hover:bg-gray-700/30 transition-all duration-200 group ${selectedStock === stock.stock_code ? 'bg-blue-500/10 border-l-2 border-blue-500' : 'border-l-2 border-transparent'}`}
>
<div
onClick={() => { onSelectStock(stock.stock_code); onRefetchAiAnalysis() }}
className="cursor-pointer"
>
<div className="flex justify-between items-start mb-1">
<span className={`font-bold text-sm ${selectedStock === stock.stock_code ? 'text-blue-400' : 'text-gray-200'}`}>
{stock.stock_name}
</span>
<span className={`text-xs font-bold ${profitLossRate > 0 ? 'text-red-400' : 'text-blue-400'}`}>
{profitLossRate > 0 ? '+' : ''}{profitLossRate.toFixed(2)}%
</span>
</div>
<div className="flex justify-between items-center text-[11px] text-gray-400 mb-0.5">
<span className="font-mono">{stock.stock_code}</span>
<span>{quantity}주</span>
</div>
<div className="flex justify-between items-center text-[11px]">
<span className="text-gray-500">{avgPrice.toLocaleString()}원</span>
<span className="text-white font-medium">{currentPrice.toLocaleString()}원</span>
</div>
<div className="flex justify-between items-center text-[11px] mt-0.5">
<span className="text-gray-500">평가금액</span>
<span className={`font-medium ${profitLossRate > 0 ? 'text-red-400' : 'text-blue-400'}`}>
{totalValue.toLocaleString()}원
</span>
</div>
</div>
<button
onClick={handleSellClick}
className="w-full mt-2 px-2 py-1 text-[10px] bg-blue-500/20 hover:bg-blue-500/30 text-blue-400 rounded border border-blue-500/30 transition-colors font-bold"
>
🔵 전량 청산 (시장가)
</button>
</div>
)
})
) : (
<div className="h-full flex flex-col items-center justify-center text-gray-500 p-4 text-center text-xs">
<p>보유 중인 종목이 없습니다.</p>
</div>
)}
</div>
</div>
)
})
PortfolioPanel.displayName = 'PortfolioPanel'
export default PortfolioPanel