import React, { useEffect, useState } from 'react';
import Loading from '../../../components/Loading';
import { stockHoldingsIntel } from '../../../api';
/* ── action config ────────────────────────────────────────────────── */
const ACTION_MAP = {
add: { label: '추가매수', color: '#22c55e', bg: 'rgba(34,197,94,0.12)' },
hold: { label: '보유', color: '#94a3b8', bg: 'rgba(148,163,184,0.10)' },
trim: { label: '축소', color: '#f59e0b', bg: 'rgba(245,158,11,0.12)' },
sell: { label: '매도', color: '#ef4444', bg: 'rgba(239,68,68,0.12)' },
};
const SEV_COLOR = { high: '#ef4444', med: '#f59e0b', low: '#94a3b8' };
/* ── helpers ──────────────────────────────────────────────────────── */
const fmtRate = (v) => (v != null ? `${v >= 0 ? '+' : ''}${v.toFixed(1)}%` : '—');
const fmtPct = (v) => (v != null ? `${(v * 100).toFixed(0)}%` : '—');
/* ── sub-components ───────────────────────────────────────────────── */
const HealthBar = ({ ph }) => (
= 0 ? 'is-up' : 'is-down'}`}>
포트 손익 {fmtRate(ph.total_pnl_rate)}
·
종목 {ph.positions ?? 0}
·
최대비중 {fmtPct(ph.max_weight)}
·
현금 {fmtPct(ph.cash_ratio)}
);
const HoldingCard = ({ h }) => {
const cfg = ACTION_MAP[h.action] ?? { label: h.action, color: '#94a3b8', bg: 'rgba(148,163,184,0.1)' };
const issues = (h.issues || []).slice(0, 3);
return (
{cfg.label}
{h.name || h.ticker}
{h.ticker}
{h.close != null && (
{h.close.toLocaleString()}원
)}
= 0 ? 'is-up' : 'is-down'}`}>
{fmtRate(h.pnl_rate)}
{h.reasons && (
{h.reasons}
)}
{h.tech_score != null && (
기술강도 {h.tech_score.toFixed(0)}
)}
{issues.length > 0 && (
{issues.map((iss, i) => (
{iss.summary}
))}
)}
);
};
/* ── main component ───────────────────────────────────────────────── */
const HoldingsIntelTab = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
setLoading(true);
setError('');
stockHoldingsIntel()
.then(setData)
.catch((err) => setError(err?.message ?? String(err)))
.finally(() => setLoading(false));
}, []);
const ph = data?.portfolio_health ?? {};
const holdings = data?.holdings ?? [];
return (
보유종목 인텔리전스
보유종목 신호 분석
스크리너 엔진 기반 기술분석·매도룰·이슈를 보유종목에 적용합니다 (어드바이저리).
{loading && }
{error && {error}
}
{!loading && !error && !data && (
데이터가 없습니다.
)}
{!loading && data && (
<>
{Object.keys(ph).length > 0 && }
{data.date && (
분석 기준일: {data.date}
)}
{holdings.length === 0 ? (
📊
아직 분석 데이터가 없습니다.
보유종목 등록 후 EOD 계산이 완료되면 표시됩니다.
) : (
{holdings.map((h) => (
))}
)}
※ 투자 판단 보조용 제안입니다. 자동매매가 아니며 최종 결정은 본인 책임입니다.
>
)}
);
};
export default HoldingsIntelTab;