계좌 페이지 분류
This commit is contained in:
@@ -625,6 +625,101 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Main Tabs ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.stock-main-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-main-tab {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 14px 24px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.25s, background 0.25s;
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-main-tab:hover {
|
||||||
|
color: var(--fg);
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-main-tab.is-active {
|
||||||
|
color: var(--fg);
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-main-tab.is-active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -1px;
|
||||||
|
left: 12px;
|
||||||
|
right: 12px;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, #818cf8, #a78bfa);
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 0 8px rgba(129, 140, 248, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-main-tab__icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-main-tab__label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-main-tab__badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(135deg, #818cf8, #a78bfa);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-main-tab__sub {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(251, 191, 36, 0.15);
|
||||||
|
color: rgba(251, 191, 36, 0.9);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.stock-main-tab {
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-main-tab__icon {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-main-tab__label {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Portfolio ────────────────────────────────────────────────────── */
|
/* ── Portfolio ────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.pf-section {
|
.pf-section {
|
||||||
|
|||||||
@@ -90,18 +90,24 @@ const emptyPortfolioForm = {
|
|||||||
avg_price: '',
|
avg_price: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* ── TAB IDs ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
const TAB_PORTFOLIO = 'portfolio';
|
||||||
|
const TAB_AI = 'ai';
|
||||||
|
|
||||||
/* ── component ───────────────────────────────────────────────────── */
|
/* ── component ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
const StockTrade = () => {
|
const StockTrade = () => {
|
||||||
/* Balance state */
|
/* Active tab */
|
||||||
const [balance, setBalance] = useState(null);
|
const [activeTab, setActiveTab] = useState(TAB_PORTFOLIO);
|
||||||
const [balanceLoading, setBalanceLoading] = useState(false);
|
|
||||||
const [balanceError, setBalanceError] = useState('');
|
|
||||||
|
|
||||||
/* Portfolio state */
|
/* ────────────────────────────────────────────────────────────── */
|
||||||
|
/* 쟁승토리 계좌 (Portfolio) state */
|
||||||
|
/* ────────────────────────────────────────────────────────────── */
|
||||||
const [portfolio, setPortfolio] = useState(null);
|
const [portfolio, setPortfolio] = useState(null);
|
||||||
const [portfolioLoading, setPortfolioLoading] = useState(false);
|
const [portfolioLoading, setPortfolioLoading] = useState(false);
|
||||||
const [portfolioError, setPortfolioError] = useState('');
|
const [portfolioError, setPortfolioError] = useState('');
|
||||||
|
const [portfolioLoaded, setPortfolioLoaded] = useState(false);
|
||||||
|
|
||||||
/* Portfolio add form */
|
/* Portfolio add form */
|
||||||
const [addForm, setAddForm] = useState({ ...emptyPortfolioForm });
|
const [addForm, setAddForm] = useState({ ...emptyPortfolioForm });
|
||||||
@@ -118,6 +124,14 @@ const StockTrade = () => {
|
|||||||
/* Portfolio delete */
|
/* Portfolio delete */
|
||||||
const [deleteConfirmId, setDeleteConfirmId] = useState(null);
|
const [deleteConfirmId, setDeleteConfirmId] = useState(null);
|
||||||
|
|
||||||
|
/* ────────────────────────────────────────────────────────────── */
|
||||||
|
/* AI 투자 (Balance) state */
|
||||||
|
/* ────────────────────────────────────────────────────────────── */
|
||||||
|
const [balance, setBalance] = useState(null);
|
||||||
|
const [balanceLoading, setBalanceLoading] = useState(false);
|
||||||
|
const [balanceError, setBalanceError] = useState('');
|
||||||
|
const [balanceLoaded, setBalanceLoaded] = useState(false);
|
||||||
|
|
||||||
/* Manual order state */
|
/* Manual order state */
|
||||||
const [manualForm, setManualForm] = useState({
|
const [manualForm, setManualForm] = useState({
|
||||||
code: '',
|
code: '',
|
||||||
@@ -132,25 +146,13 @@ const StockTrade = () => {
|
|||||||
|
|
||||||
/* ── loaders ─────────────────────────────────────────────────── */
|
/* ── loaders ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
const loadBalance = useCallback(async () => {
|
|
||||||
setBalanceLoading(true);
|
|
||||||
setBalanceError('');
|
|
||||||
try {
|
|
||||||
const data = await getTradeBalance();
|
|
||||||
setBalance(data);
|
|
||||||
} catch (err) {
|
|
||||||
setBalanceError(err?.message ?? String(err));
|
|
||||||
} finally {
|
|
||||||
setBalanceLoading(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadPortfolio = useCallback(async () => {
|
const loadPortfolio = useCallback(async () => {
|
||||||
setPortfolioLoading(true);
|
setPortfolioLoading(true);
|
||||||
setPortfolioError('');
|
setPortfolioError('');
|
||||||
try {
|
try {
|
||||||
const data = await getPortfolio();
|
const data = await getPortfolio();
|
||||||
setPortfolio(data);
|
setPortfolio(data);
|
||||||
|
setPortfolioLoaded(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setPortfolioError(err?.message ?? String(err));
|
setPortfolioError(err?.message ?? String(err));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -158,16 +160,35 @@ const StockTrade = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const loadBalance = useCallback(async () => {
|
||||||
loadBalance();
|
setBalanceLoading(true);
|
||||||
loadPortfolio();
|
setBalanceError('');
|
||||||
}, [loadBalance, loadPortfolio]);
|
try {
|
||||||
|
const data = await getTradeBalance();
|
||||||
|
setBalance(data);
|
||||||
|
setBalanceLoaded(true);
|
||||||
|
} catch (err) {
|
||||||
|
setBalanceError(err?.message ?? String(err));
|
||||||
|
} finally {
|
||||||
|
setBalanceLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
/* Auto-refresh portfolio every 3 min */
|
/* Lazy load: 탭 전환 시 해당 API만 호출 */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (activeTab === TAB_PORTFOLIO && !portfolioLoaded) {
|
||||||
|
loadPortfolio();
|
||||||
|
} else if (activeTab === TAB_AI && !balanceLoaded) {
|
||||||
|
loadBalance();
|
||||||
|
}
|
||||||
|
}, [activeTab, portfolioLoaded, balanceLoaded, loadPortfolio, loadBalance]);
|
||||||
|
|
||||||
|
/* Auto-refresh portfolio every 3 min (포트폴리오 탭 활성 시) */
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab !== TAB_PORTFOLIO) return;
|
||||||
const timer = window.setInterval(loadPortfolio, 180000);
|
const timer = window.setInterval(loadPortfolio, 180000);
|
||||||
return () => window.clearInterval(timer);
|
return () => window.clearInterval(timer);
|
||||||
}, [loadPortfolio]);
|
}, [activeTab, loadPortfolio]);
|
||||||
|
|
||||||
/* ── portfolio actions ───────────────────────────────────────── */
|
/* ── portfolio actions ───────────────────────────────────────── */
|
||||||
|
|
||||||
@@ -201,7 +222,6 @@ const StockTrade = () => {
|
|||||||
broker: item.broker,
|
broker: item.broker,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
});
|
});
|
||||||
/* 원본 값을 기억해서 diff 비교용 */
|
|
||||||
editOrigRef.current = {
|
editOrigRef.current = {
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
avg_price: item.avg_price,
|
avg_price: item.avg_price,
|
||||||
@@ -213,7 +233,6 @@ const StockTrade = () => {
|
|||||||
const handleEditSave = async (id) => {
|
const handleEditSave = async (id) => {
|
||||||
setEditLoading(true);
|
setEditLoading(true);
|
||||||
try {
|
try {
|
||||||
/* 변경된 필드만 추출하여 부분 수정 */
|
|
||||||
const orig = editOrigRef.current ?? {};
|
const orig = editOrigRef.current ?? {};
|
||||||
const diff = {};
|
const diff = {};
|
||||||
for (const key of Object.keys(editForm)) {
|
for (const key of Object.keys(editForm)) {
|
||||||
@@ -289,7 +308,7 @@ const StockTrade = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ── derived data ────────────────────────────────────────────── */
|
/* ── derived: AI balance ──────────────────────────────────────── */
|
||||||
|
|
||||||
const holdings = useMemo(() => {
|
const holdings = useMemo(() => {
|
||||||
if (!balance) return [];
|
if (!balance) return [];
|
||||||
@@ -304,7 +323,8 @@ const StockTrade = () => {
|
|||||||
const deposit =
|
const deposit =
|
||||||
summary.deposit ?? balance?.deposit ?? balance?.available_cash;
|
summary.deposit ?? balance?.deposit ?? balance?.available_cash;
|
||||||
|
|
||||||
/* Portfolio grouped by broker */
|
/* ── derived: Portfolio ───────────────────────────────────────── */
|
||||||
|
|
||||||
const portfolioHoldings = portfolio?.holdings ?? [];
|
const portfolioHoldings = portfolio?.holdings ?? [];
|
||||||
const portfolioSummary = portfolio?.summary ?? {};
|
const portfolioSummary = portfolio?.summary ?? {};
|
||||||
const brokerGroups = useMemo(() => {
|
const brokerGroups = useMemo(() => {
|
||||||
@@ -317,7 +337,6 @@ const StockTrade = () => {
|
|||||||
return Object.entries(map).sort(([a], [b]) => a.localeCompare(b));
|
return Object.entries(map).sort(([a], [b]) => a.localeCompare(b));
|
||||||
}, [portfolioHoldings]);
|
}, [portfolioHoldings]);
|
||||||
|
|
||||||
/* broker-level summary (eval_amount가 null인 종목은 안전하게 건너뜀) */
|
|
||||||
const getBrokerSummary = (items) => {
|
const getBrokerSummary = (items) => {
|
||||||
let totalBuy = 0;
|
let totalBuy = 0;
|
||||||
let totalEvalAmt = 0;
|
let totalEvalAmt = 0;
|
||||||
@@ -335,7 +354,6 @@ const StockTrade = () => {
|
|||||||
return { totalBuy, totalEval: totalEvalAmt, totalProfit, totalProfitRate, hasNullPrice };
|
return { totalBuy, totalEval: totalEvalAmt, totalProfit, totalProfitRate, hasNullPrice };
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ── broker color accents (deterministic from name) ──────────── */
|
|
||||||
const brokerColors = useMemo(() => {
|
const brokerColors = useMemo(() => {
|
||||||
const palette = [
|
const palette = [
|
||||||
{ border: 'rgba(129,140,248,0.5)', bg: 'rgba(129,140,248,0.06)' },
|
{ border: 'rgba(129,140,248,0.5)', bg: 'rgba(129,140,248,0.06)' },
|
||||||
@@ -356,12 +374,13 @@ const StockTrade = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="stock">
|
<div className="stock">
|
||||||
|
{/* ── Header ──────────────────────────────────────────── */}
|
||||||
<header className="stock-header">
|
<header className="stock-header">
|
||||||
<div>
|
<div>
|
||||||
<p className="stock-kicker">거래 데스크</p>
|
<p className="stock-kicker">거래 데스크</p>
|
||||||
<h1>주식 거래</h1>
|
<h1>거래 데스크</h1>
|
||||||
<p className="stock-sub">
|
<p className="stock-sub">
|
||||||
연결된 계좌 잔고를 확인하고 필요한 주문을 요청하세요.
|
실제 계좌와 AI 모의투자를 한 곳에서 관리하세요.
|
||||||
</p>
|
</p>
|
||||||
<div className="stock-actions">
|
<div className="stock-actions">
|
||||||
<Link className="button ghost" to="/stock">
|
<Link className="button ghost" to="/stock">
|
||||||
@@ -370,32 +389,24 @@ const StockTrade = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="stock-card">
|
<div className="stock-card">
|
||||||
<p className="stock-card__title">계좌 요약</p>
|
<p className="stock-card__title">
|
||||||
|
{activeTab === TAB_PORTFOLIO
|
||||||
|
? '쟁승토리 계좌 요약'
|
||||||
|
: 'AI 투자 요약'}
|
||||||
|
</p>
|
||||||
|
{activeTab === TAB_PORTFOLIO ? (
|
||||||
|
/* Portfolio summary */
|
||||||
<div className="stock-status">
|
<div className="stock-status">
|
||||||
<div>
|
<div>
|
||||||
<span>총 평가금액</span>
|
<span>총 매입</span>
|
||||||
<strong>{formatNumber(totalEval)}</strong>
|
<strong>{formatNumber(portfolioSummary.total_buy)}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>예수금</span>
|
<span>총 평가</span>
|
||||||
<strong>{formatNumber(deposit)}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>보유 종목</span>
|
|
||||||
<strong>{holdings.length}</strong>
|
|
||||||
</div>
|
|
||||||
{portfolioHoldings.length > 0 && (
|
|
||||||
<>
|
|
||||||
<div style={{ borderTop: '1px solid var(--line)', paddingTop: 8 }}>
|
|
||||||
<span>포트폴리오 종목</span>
|
|
||||||
<strong>{portfolioHoldings.length}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>포트폴리오 평가</span>
|
|
||||||
<strong>{formatNumber(portfolioSummary.total_eval)}</strong>
|
<strong>{formatNumber(portfolioSummary.total_eval)}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>포트폴리오 손익</span>
|
<span>총 손익</span>
|
||||||
<strong
|
<strong
|
||||||
className={`stock-profit ${profitColorClass(
|
className={`stock-profit ${profitColorClass(
|
||||||
toNumeric(portfolioSummary.total_profit)
|
toNumeric(portfolioSummary.total_profit)
|
||||||
@@ -409,21 +420,70 @@ const StockTrade = () => {
|
|||||||
)}
|
)}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
</>
|
<div>
|
||||||
)}
|
<span>보유 종목</span>
|
||||||
|
<strong>{portfolioHoldings.length}</strong>
|
||||||
</div>
|
</div>
|
||||||
{summary.note ? (
|
</div>
|
||||||
|
) : (
|
||||||
|
/* AI balance summary */
|
||||||
|
<div className="stock-status">
|
||||||
|
<div>
|
||||||
|
<span>총 평가금액</span>
|
||||||
|
<strong>{formatNumber(totalEval)}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>예수금</span>
|
||||||
|
<strong>{formatNumber(deposit)}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>보유 종목</span>
|
||||||
|
<strong>{holdings.length}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeTab === TAB_AI && summary.note ? (
|
||||||
<p className="stock-status__note">{summary.note}</p>
|
<p className="stock-status__note">{summary.note}</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* ── Portfolio sections (broker별) ─────────────────────── */}
|
{/* ── Main Tabs ───────────────────────────────────────── */}
|
||||||
|
<div className="stock-main-tabs">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`stock-main-tab ${activeTab === TAB_PORTFOLIO ? 'is-active' : ''}`}
|
||||||
|
onClick={() => setActiveTab(TAB_PORTFOLIO)}
|
||||||
|
>
|
||||||
|
<span className="stock-main-tab__icon">💼</span>
|
||||||
|
<span className="stock-main-tab__label">쟁승토리 계좌</span>
|
||||||
|
{portfolioHoldings.length > 0 && (
|
||||||
|
<span className="stock-main-tab__badge">
|
||||||
|
{portfolioHoldings.length}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`stock-main-tab ${activeTab === TAB_AI ? 'is-active' : ''}`}
|
||||||
|
onClick={() => setActiveTab(TAB_AI)}
|
||||||
|
>
|
||||||
|
<span className="stock-main-tab__icon">🤖</span>
|
||||||
|
<span className="stock-main-tab__label">AI 투자</span>
|
||||||
|
<span className="stock-main-tab__sub">모의투자</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ════════════════════════════════════════════════════════
|
||||||
|
TAB 1: 쟁승토리 계좌
|
||||||
|
════════════════════════════════════════════════════════ */}
|
||||||
|
{activeTab === TAB_PORTFOLIO && (
|
||||||
|
<>
|
||||||
{portfolioError ? (
|
{portfolioError ? (
|
||||||
<p className="stock-error">{portfolioError}</p>
|
<p className="stock-error">{portfolioError}</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* 총 포트폴리오 요약 + 종목 추가 */}
|
{/* 포트폴리오 관리 헤더 + 추가 폼 */}
|
||||||
<section className="stock-panel stock-panel--wide pf-section">
|
<section className="stock-panel stock-panel--wide pf-section">
|
||||||
<div className="stock-panel__head">
|
<div className="stock-panel__head">
|
||||||
<div>
|
<div>
|
||||||
@@ -556,7 +616,7 @@ const StockTrade = () => {
|
|||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Each broker gets a stacked card */}
|
{/* Broker cards stacked */}
|
||||||
{brokerGroups.map(([broker, items]) => {
|
{brokerGroups.map(([broker, items]) => {
|
||||||
const bSummary = getBrokerSummary(items);
|
const bSummary = getBrokerSummary(items);
|
||||||
const color = brokerColors[broker];
|
const color = brokerColors[broker];
|
||||||
@@ -607,7 +667,6 @@ const StockTrade = () => {
|
|||||||
className="stock-holdings__item pf-item"
|
className="stock-holdings__item pf-item"
|
||||||
>
|
>
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
/* Edit mode */
|
|
||||||
<div className="pf-edit-row">
|
<div className="pf-edit-row">
|
||||||
<div className="pf-edit-fields">
|
<div className="pf-edit-fields">
|
||||||
<label>
|
<label>
|
||||||
@@ -656,7 +715,6 @@ const StockTrade = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
/* Normal display */
|
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<p className="stock-holdings__name">
|
<p className="stock-holdings__name">
|
||||||
@@ -704,7 +762,6 @@ const StockTrade = () => {
|
|||||||
: '-'}
|
: '-'}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
{/* action buttons */}
|
|
||||||
<div className="pf-item-actions">
|
<div className="pf-item-actions">
|
||||||
<button
|
<button
|
||||||
className="button ghost small"
|
className="button ghost small"
|
||||||
@@ -752,16 +809,31 @@ const StockTrade = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* ── Existing balance section ─────────────────────────── */}
|
{portfolioLoaded && portfolioHoldings.length === 0 && !portfolioError && (
|
||||||
|
<section className="stock-panel stock-panel--wide">
|
||||||
|
<p className="stock-empty" style={{ textAlign: 'center', padding: 24 }}>
|
||||||
|
등록된 종목이 없습니다. 상단의 <strong>+ 종목 추가</strong> 버튼으로 보유 종목을 등록하세요.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ════════════════════════════════════════════════════════
|
||||||
|
TAB 2: AI 투자 (모의투자)
|
||||||
|
════════════════════════════════════════════════════════ */}
|
||||||
|
{activeTab === TAB_AI && (
|
||||||
|
<>
|
||||||
{balanceError ? <p className="stock-error">{balanceError}</p> : null}
|
{balanceError ? <p className="stock-error">{balanceError}</p> : null}
|
||||||
|
|
||||||
|
{/* AI Balance section */}
|
||||||
<section className="stock-panel stock-panel--wide">
|
<section className="stock-panel stock-panel--wide">
|
||||||
<div className="stock-panel__head">
|
<div className="stock-panel__head">
|
||||||
<div>
|
<div>
|
||||||
<p className="stock-panel__eyebrow">잔고</p>
|
<p className="stock-panel__eyebrow">AI 모의투자</p>
|
||||||
<h3>보유 현황</h3>
|
<h3>보유 현황</h3>
|
||||||
<p className="stock-panel__sub">
|
<p className="stock-panel__sub">
|
||||||
연결 계좌의 실시간 잔고와 보유 종목을 확인합니다.
|
AI가 운용 중인 모의투자 계좌의 잔고와 보유 종목을 확인합니다.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="stock-panel__actions">
|
<div className="stock-panel__actions">
|
||||||
@@ -780,14 +852,8 @@ const StockTrade = () => {
|
|||||||
<div className="stock-balance">
|
<div className="stock-balance">
|
||||||
<div className="stock-balance__summary">
|
<div className="stock-balance__summary">
|
||||||
{[
|
{[
|
||||||
{
|
{ label: '총 평가', value: totalEval },
|
||||||
label: '총 평가',
|
{ label: '예수금', value: deposit },
|
||||||
value: totalEval,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '예수금',
|
|
||||||
value: deposit,
|
|
||||||
},
|
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.label}
|
key={item.label}
|
||||||
@@ -835,9 +901,7 @@ const StockTrade = () => {
|
|||||||
<div className="stock-holdings__metric">
|
<div className="stock-holdings__metric">
|
||||||
<span>현재가</span>
|
<span>현재가</span>
|
||||||
<strong>
|
<strong>
|
||||||
{formatNumber(
|
{formatNumber(getCurrentPrice(item))}
|
||||||
getCurrentPrice(item)
|
|
||||||
)}
|
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<div className="stock-holdings__metric">
|
<div className="stock-holdings__metric">
|
||||||
@@ -866,15 +930,14 @@ const StockTrade = () => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Manual order section ─────────────────────────────── */}
|
{/* Manual order section */}
|
||||||
<section className="stock-panel stock-panel--wide">
|
<section className="stock-panel stock-panel--wide">
|
||||||
<div className="stock-panel__head">
|
<div className="stock-panel__head">
|
||||||
<div>
|
<div>
|
||||||
<p className="stock-panel__eyebrow">수동 주문</p>
|
<p className="stock-panel__eyebrow">수동 주문</p>
|
||||||
<h3>직접 매수/매도</h3>
|
<h3>직접 매수/매도</h3>
|
||||||
<p className="stock-panel__sub">
|
<p className="stock-panel__sub">
|
||||||
종목명 또는 종목코드를 입력하고 매수/매도 주문을
|
종목명 또는 종목코드를 입력하고 매수/매도 주문을 요청합니다.
|
||||||
요청합니다.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -962,6 +1025,8 @@ const StockTrade = () => {
|
|||||||
) : null}
|
) : null}
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* KIS modal */}
|
{/* KIS modal */}
|
||||||
{kisModal ? (
|
{kisModal ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user