import React from 'react'; import Loading from '../../../components/Loading'; import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip as ChartTooltip, } from 'recharts'; import { formatNumber, formatPercent, toNumeric, profitColorClass, numFitClass } from '../stockUtils'; const formatPriceTime = (iso) => { if (!iso) return ''; const d = new Date(iso); if (Number.isNaN(d.getTime())) return ''; return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; }; const PriceSessionBadge = ({ session, asOf }) => { if (session !== 'NXT_AFTER' && session !== 'NXT_PRE') return null; const isPre = session === 'NXT_PRE'; const label = isPre ? 'NXT 프리' : 'NXT'; const desc = isPre ? 'NXT 프리마켓 거래가' : 'NXT 야간거래 (15:30~20:00)'; const time = formatPriceTime(asOf); return ( {label} ); }; const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => ( <> {pf.portfolioError ? (

{pf.portfolioError}

) : null} {/* 포트폴리오 관리 헤더 + 추가 폼 */}

포트폴리오

수동 입력 종목 관리

증권사별 보유 종목을 수동 등록하면 현재가를 자동 조회합니다. (3분 캐시)

{pf.portfolioLoading ? ( ) : null}
{/* Add form */} {pf.addFormOpen && (
{pf.addError &&

{pf.addError}

}
)} {/* Portfolio total summary */} {pf.portfolioHoldings.length > 0 && (
{[ { label: '총 매입', value: pf.portfolioSummary.total_buy }, { label: '총 평가', value: pf.portfolioSummary.total_eval }, { label: '총 손익', value: pf.portfolioSummary.total_profit, isProfit: true }, { label: '수익률', value: pf.portfolioSummary.total_profit_rate, isRate: true }, ].map((s) => { const display = s.isRate ? formatPercent(s.value) : formatNumber(s.value); const profitCls = s.isProfit || s.isRate ? `stock-profit ${profitColorClass(toNumeric(s.value))}` : ''; return (
{s.label} {display}
); })} {pf.totalCash != null && (() => { const display = `${formatNumber(pf.totalCash)}원`; return (
예수금 합계 {display}
); })()} {pf.totalAssets != null && (() => { const display = `${formatNumber(pf.totalAssets)}원`; return (
총 자산 {display}
); })()}
)} {/* 자산 추이 차트 */}

총 자산 추이

{[ { label: '7일', value: 7 }, { label: '30일', value: 30 }, { label: '90일', value: 90 }, { label: '전체', value: 0 }, ].map(({ label, value }) => ( ))}
{asset.assetHistoryLoading ? (
) : Array.isArray(asset.assetHistory) && asset.assetHistory.length >= 1 ? ( v?.slice(5)} tickLine={false} axisLine={false} interval="preserveStartEnd" /> [`${new Intl.NumberFormat('ko-KR').format(v)}원`, '총 자산']} /> ) : (
저장된 자산 추이 데이터가 없습니다. 📸 스냅샷 버튼으로 오늘 자산을 기록하세요.
)}
{/* 예수금 패널 */}

예수금 관리

증권사별 예수금

증권사별 예수금을 입력하면 총 자산에 자동 반영됩니다.

{pf.cashList.length > 0 && (
{pf.cashList.map((item) => { const isEditing = pf.cashEditingBroker === item.broker; return (
{item.broker} {isEditing ? ( pf.setCashEditingValue(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') pf.handleCashInlineSave(item.broker); if (e.key === 'Escape') pf.handleCashInlineCancel(); }} autoFocus /> ) : ( {formatNumber(item.cash)}원 )} {item.updated_at ? new Date(item.updated_at).toLocaleDateString('ko-KR') : ''} {isEditing ? ( <> ) : ( <> )}
); })}
)} {pf.cashList.length === 0 && (

등록된 예수금이 없습니다.

)}
{pf.cashError &&

{pf.cashError}

}
{/* Broker cards stacked */} {pf.brokerGroups.map(([broker, items]) => { const bSummary = pf.getBrokerSummary(items); const color = pf.brokerColors[broker]; return (

{broker}

{broker} 보유 현황

{items.length}종목 · 총 매입{' '} {formatNumber(bSummary.totalBuy)} · 평가{' '} {formatNumber(bSummary.totalEval)} · 손익{' '} {formatNumber(bSummary.totalProfit)} ( {formatPercent(bSummary.totalProfitRate)}) {(() => { const bc = pf.cashList.find((c) => c.broker === broker); return bc ? ( 예수금 {formatNumber(bc.cash)}원 ) : null; })()}

{items.map((item) => { const profitAmt = item.profit_amount; const profitRate = item.profit_rate; const profitAmtN = toNumeric(profitAmt); const profitRateN = toNumeric(profitRate); const isEditing = pf.editingId === item.id; const isDeleting = pf.deleteConfirmId === item.id; const isSelling = pf.sellConfirmId === item.id; const sellPrice = item.current_price ?? item.avg_price; const saleAmount = sellPrice != null ? sellPrice * (item.quantity ?? 0) : null; return (
{isEditing ? (
) : ( <>

{item.name ?? item.ticker ?? 'N/A'}

{item.ticker ?? ''}
수량 {formatNumber(item.quantity)}
평균단가 {formatNumber(item.avg_price)}
매입가 {formatNumber(item.purchase_price ?? item.avg_price)}
현재가 {item.current_price != null ? formatNumber(item.current_price) : '조회 실패'}
평가금액 {item.current_price != null && item.quantity != null ? formatNumber(item.current_price * item.quantity) : '-'}
수익률 {profitRate != null ? formatPercent(profitRate) : '-'}
평가손익 {profitAmt != null ? formatNumber(profitAmt) : '-'}
{!isSelling && !isDeleting && ( )} {isSelling ? (
{item.current_price == null && ( 현재가 미조회 — 매입가 기준 )} {saleAmount != null ? `${formatNumber(saleAmount)}원 매도 후 예수금 반영` : '매도 처리'}
) : isDeleting ? ( <> ) : ( <> )}
)}
); })}
); })} {pf.portfolioLoaded && pf.portfolioHoldings.length === 0 && !pf.portfolioError && (

등록된 종목이 없습니다. 상단의 + 종목 추가 버튼으로 보유 종목을 등록하세요.

)} ); export default PortfolioTab;