import React from 'react'; import Loading from '../../../components/Loading'; import { PieChart, Pie, Cell, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as ChartTooltip, Legend, ResponsiveContainer, } from 'recharts'; import { formatNumber, formatPercent, toNumeric, CHART_COLORS, profitColorClass, getVixLabel, getFgLabel, } from '../stockUtils'; const ReportTab = ({ pf, report, ai, marketCtx }) => ( <> {pf.portfolioLoading && (
)} {pf.portfolioError &&

{pf.portfolioError}

} {/* 자산 배분 + 수익률 차트 */} {pf.portfolioHoldings.length > 0 && (

포트폴리오 분석

자산 배분 현황

증권사별 자산 배분

{report.brokerPieData.map((_, i) => ( ))} [formatNumber(v) + '원', '평가금액']} contentStyle={{ background: '#1e293b', border: 'none', borderRadius: 8, fontSize: 12 }} /> {v}} />

종목별 수익률 (%)

`${v}%`} /> [`${v.toFixed(2)}%`, props.payload.fullName]} contentStyle={{ background: '#1e293b', border: 'none', borderRadius: 8, fontSize: 12 }} /> {report.profitBarData.map((entry, i) => ( = 0 ? '#34d399' : '#f87171'} /> ))}
)} {/* 리스크 분산 분석 */} {pf.portfolioHoldings.length > 0 && pf.portfolioSummary.total_eval != null && (

리스크 관리

분산 분석

증권사·종목 집중도를 확인합니다. 단일 비중 40% 초과 시 주의.

증권사별 집중도

{report.brokerConcentration.length === 0 ? (

평가금액 데이터 없음

) : ( <> {report.brokerConcentration.some((b) => b.ratio > 40) && (
⚠️ 단일 증권사 집중도가 40%를 초과합니다
)} {report.brokerConcentration.map(({ broker, eval: evalAmt, ratio }) => { const level = ratio >= 60 ? 'is-danger' : ratio >= 40 ? 'is-warn' : 'is-ok'; return (
{broker} {ratio.toFixed(1)}%
{formatNumber(evalAmt)}원
); })} )}

상위 5 종목 집중도

{report.stockConcentration.length === 0 ? (

현재가 데이터 없음

) : ( <> {report.stockConcentration.some((s) => s.ratio > 40) && (
⚠️ 단일 종목 집중도가 40%를 초과합니다
)} {report.stockConcentration.map(({ name, ticker, eval: evalAmt, ratio }) => { const level = ratio >= 60 ? 'is-danger' : ratio >= 40 ? 'is-warn' : 'is-ok'; return (
{name} {ratio.toFixed(1)}%
{ticker && {ticker}} {formatNumber(evalAmt)}원
); })} )}
)} {/* 수익률 랭킹 테이블 */} {pf.portfolioHoldings.length > 0 && (

수익률 랭킹

종목별 상세 현황

헤더 클릭으로 정렬 · 비중은 총 평가금액 대비

{[ { key: 'name', label: '종목명' }, { key: 'broker', label: '증권사' }, { key: 'profit_rate', label: '수익률' }, { key: 'profit_amount', label: '평가손익' }, { key: 'eval_amount', label: '평가금액' }, ].map(({ key, label }) => ( ))} {report.sortedHoldings.map((item) => { const rateN = toNumeric(item.profit_rate); const pnlN = toNumeric(item.profit_amount); const evalAmt = item.eval_amount != null ? item.eval_amount : item.current_price != null ? item.current_price * item.quantity : null; const totalEvalVal = toNumeric(pf.portfolioSummary.total_eval); const weight = evalAmt != null && totalEvalVal ? Math.round((evalAmt / totalEvalVal) * 1000) / 10 : null; return ( ); })}
report.handleReportSort(key)}> {label}{' '} {report.reportSortField === key ? report.reportSortDir === 'asc' ? '↑' : '↓' : '↕'} 비중

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

{item.ticker ?? ''}
{item.broker ?? '-'}
{item.profit_rate != null ? formatPercent(item.profit_rate) : '-'} {rateN != null && (
= 0 ? 'is-up' : 'is-down'}`} style={{ width: `${report.maxAbsRate > 0 ? Math.abs(rateN) / report.maxAbsRate * 100 : 0}%` }} />
)}
{item.profit_amount != null ? formatNumber(item.profit_amount) : '-'} {evalAmt != null ? formatNumber(evalAmt) : '-'} {weight != null ? `${weight.toFixed(1)}%` : '-'}
)} {pf.portfolioLoaded && pf.portfolioHoldings.length === 0 && !pf.portfolioError && (

등록된 종목이 없습니다. 쟁승토리 계좌 탭에서 종목을 먼저 등록하세요.

)} {/* AI 투자 코치 */}

AI 투자 코치

오늘의 투자 평가

포트폴리오를 AI가 분석하여 성취도 등급과 내일을 위한 투자 조언을 드립니다.

{/* 시장 컨텍스트 미니 패널 */} {marketCtx && (
시장 환경
{marketCtx.vix != null && ( VIX {marketCtx.vix} {getVixLabel(marketCtx.vix)} )} {marketCtx.fg != null && ( F&G {marketCtx.fg} {getFgLabel(marketCtx.fg)} )} {marketCtx.treasury != null && ( 10년물 {marketCtx.treasury}% )} {marketCtx.wti != null && ( WTI ${marketCtx.wti} )}
)} {/* 모델 선택 */}
{pf.portfolioHoldings.length === 0 && ( 종목 등록 후 이용 가능합니다. )} {ai.aiResult?.generated_at && ( {ai.aiResult.cached ? '오늘 캐시 결과 · ' : ''} {new Date(ai.aiResult.generated_at).toLocaleTimeString('ko-KR')} 생성 )}
{ai.aiError &&

{ai.aiError}

} {ai.aiResult && !ai.aiLoading && (
{ai.aiResult.grade ?? '?'}
{ai.aiResult.score ?? 0} / 100

{ai.aiResult.summary}

{ai.aiResult.evaluation}

{ai.aiResult.advice?.length > 0 && (
{ai.aiResult.advice.map((a, i) => (

{a.title}

{a.body}

))}
)}
)}
); export default ReportTab;