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;