stock 실현손익 보여줄 수 있게 화면 구성 추가
This commit is contained in:
@@ -15,6 +15,10 @@ import {
|
||||
getWTI,
|
||||
getAssetHistory,
|
||||
saveAssetSnapshot,
|
||||
getSellHistory,
|
||||
addSellHistory,
|
||||
updateSellHistory,
|
||||
deleteSellHistory,
|
||||
} from '../../api';
|
||||
import Loading from '../../components/Loading';
|
||||
import './Stock.css';
|
||||
@@ -124,6 +128,25 @@ const emptyPortfolioForm = {
|
||||
avg_price: '',
|
||||
};
|
||||
|
||||
/* ── empty sell-history form ─────────────────────────────────────── */
|
||||
|
||||
const toLocalDatetimeValue = (isoStr) => {
|
||||
if (!isoStr) return '';
|
||||
const d = new Date(isoStr);
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
};
|
||||
|
||||
const emptySellForm = () => ({
|
||||
broker: '',
|
||||
ticker: '',
|
||||
name: '',
|
||||
quantity: '',
|
||||
avg_price: '',
|
||||
sell_price: '',
|
||||
sold_at: toLocalDatetimeValue(new Date().toISOString()),
|
||||
});
|
||||
|
||||
/* ── TAB IDs ─────────────────────────────────────────────────────── */
|
||||
|
||||
const TAB_PORTFOLIO = 'portfolio';
|
||||
@@ -163,6 +186,22 @@ const StockTrade = () => {
|
||||
const [sellConfirmId, setSellConfirmId] = useState(null);
|
||||
const [sellLoading, setSellLoading] = useState(false);
|
||||
|
||||
/* 실현손익 내역 */
|
||||
const [sellHistory, setSellHistory] = useState([]);
|
||||
const [sellHistoryLoading, setSellHistoryLoading] = useState(false);
|
||||
const [sellHistoryBroker, setSellHistoryBroker] = useState('ALL');
|
||||
const [sellHistoryPeriod, setSellHistoryPeriod] = useState('3M');
|
||||
|
||||
/* 실현손익 드로어 */
|
||||
const [sellDrawerOpen, setSellDrawerOpen] = useState(false);
|
||||
|
||||
/* 실현손익 수동 추가/수정 폼 */
|
||||
const [sellFormOpen, setSellFormOpen] = useState(false);
|
||||
const [sellEditId, setSellEditId] = useState(null); // null = 추가, number = 수정 중 id
|
||||
const [sellForm, setSellForm] = useState(emptySellForm());
|
||||
const [sellFormSaving, setSellFormSaving] = useState(false);
|
||||
const [sellFormError, setSellFormError] = useState('');
|
||||
|
||||
/* Cash (예수금) form */
|
||||
const [cashForm, setCashForm] = useState({ broker: '', cash: '' });
|
||||
const [cashSaving, setCashSaving] = useState(false);
|
||||
@@ -231,6 +270,18 @@ const StockTrade = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadSellHistory = useCallback(async () => {
|
||||
setSellHistoryLoading(true);
|
||||
try {
|
||||
const data = await getSellHistory();
|
||||
setSellHistory(data?.records ?? (Array.isArray(data) ? data : []));
|
||||
} catch {
|
||||
/* 백엔드 미구현 시 빈 배열 유지 */
|
||||
} finally {
|
||||
setSellHistoryLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadBalance = useCallback(async () => {
|
||||
setBalanceLoading(true);
|
||||
setBalanceError('');
|
||||
@@ -302,12 +353,13 @@ const StockTrade = () => {
|
||||
useEffect(() => {
|
||||
if (activeTab === TAB_PORTFOLIO && !portfolioLoaded) {
|
||||
loadPortfolio();
|
||||
loadSellHistory();
|
||||
} else if (activeTab === TAB_AI && !balanceLoaded) {
|
||||
loadBalance();
|
||||
} else if (activeTab === TAB_REPORT && !portfolioLoaded) {
|
||||
loadPortfolio();
|
||||
}
|
||||
}, [activeTab, portfolioLoaded, balanceLoaded, loadPortfolio, loadBalance]);
|
||||
}, [activeTab, portfolioLoaded, balanceLoaded, loadPortfolio, loadBalance, loadSellHistory]);
|
||||
|
||||
/* 자산 추이: 포트폴리오 탭 첫 진입 또는 기간 변경 시 로드 */
|
||||
useEffect(() => {
|
||||
@@ -495,8 +547,12 @@ const StockTrade = () => {
|
||||
|
||||
const handleSell = async (item) => {
|
||||
const sellPrice = item.current_price ?? item.avg_price;
|
||||
const avgPrice = item.avg_price ?? 0;
|
||||
const qty = item.quantity ?? 0;
|
||||
const saleAmount = sellPrice * qty;
|
||||
const buyAmount = avgPrice * qty;
|
||||
const realizedProfit = saleAmount - buyAmount;
|
||||
const realizedRate = buyAmount > 0 ? (realizedProfit / buyAmount) * 100 : 0;
|
||||
const broker = item.broker ?? '';
|
||||
|
||||
setSellLoading(true);
|
||||
@@ -506,6 +562,29 @@ const StockTrade = () => {
|
||||
const newCash = (existing?.cash ?? 0) + saleAmount;
|
||||
await upsertCash(broker, newCash);
|
||||
await deletePortfolio(item.id);
|
||||
|
||||
// 실현손익 기록 저장 (백엔드)
|
||||
const record = {
|
||||
broker,
|
||||
ticker: item.ticker ?? '',
|
||||
name: item.name ?? item.ticker ?? 'N/A',
|
||||
quantity: qty,
|
||||
avg_price: avgPrice,
|
||||
sell_price: sellPrice,
|
||||
buy_amount: buyAmount,
|
||||
sell_amount: saleAmount,
|
||||
realized_profit: realizedProfit,
|
||||
realized_rate: realizedRate,
|
||||
sold_at: new Date().toISOString(),
|
||||
};
|
||||
try {
|
||||
const saved = await addSellHistory(record);
|
||||
setSellHistory((prev) => [saved ?? record, ...prev]);
|
||||
} catch {
|
||||
/* 백엔드 미구현 시 낙관적 UI 유지 */
|
||||
setSellHistory((prev) => [{ ...record, id: Date.now() }, ...prev]);
|
||||
}
|
||||
|
||||
setSellConfirmId(null);
|
||||
await loadPortfolio();
|
||||
} catch (err) {
|
||||
@@ -515,6 +594,93 @@ const StockTrade = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteSellRecord = async (id) => {
|
||||
setSellHistory((prev) => prev.filter((r) => r.id !== id));
|
||||
try {
|
||||
await deleteSellHistory(id);
|
||||
} catch {
|
||||
/* 실패 시 목록 재로드로 복구 */
|
||||
loadSellHistory();
|
||||
}
|
||||
};
|
||||
|
||||
/* 수동 추가 폼 열기 */
|
||||
const handleSellFormOpen = () => {
|
||||
setSellEditId(null);
|
||||
setSellForm(emptySellForm());
|
||||
setSellFormError('');
|
||||
setSellFormOpen(true);
|
||||
};
|
||||
|
||||
/* 수정 폼 열기 */
|
||||
const handleSellEditStart = (record) => {
|
||||
setSellEditId(record.id);
|
||||
setSellForm({
|
||||
broker: record.broker ?? '',
|
||||
ticker: record.ticker ?? '',
|
||||
name: record.name ?? '',
|
||||
quantity: String(record.quantity ?? ''),
|
||||
avg_price: String(record.avg_price ?? ''),
|
||||
sell_price: String(record.sell_price ?? ''),
|
||||
sold_at: toLocalDatetimeValue(record.sold_at),
|
||||
});
|
||||
setSellFormError('');
|
||||
setSellFormOpen(true);
|
||||
};
|
||||
|
||||
/* 폼 닫기 */
|
||||
const handleSellFormClose = () => {
|
||||
setSellFormOpen(false);
|
||||
setSellEditId(null);
|
||||
setSellFormError('');
|
||||
};
|
||||
|
||||
/* 폼 제출 (추가 or 수정) */
|
||||
const handleSellFormSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setSellFormSaving(true);
|
||||
setSellFormError('');
|
||||
|
||||
const qty = Number(sellForm.quantity);
|
||||
const avgPrice = Number(sellForm.avg_price);
|
||||
const sellPrice = Number(sellForm.sell_price);
|
||||
const buyAmount = avgPrice * qty;
|
||||
const sellAmount = sellPrice * qty;
|
||||
const realizedProfit = sellAmount - buyAmount;
|
||||
const realizedRate = buyAmount > 0 ? (realizedProfit / buyAmount) * 100 : 0;
|
||||
|
||||
const payload = {
|
||||
broker: sellForm.broker.trim(),
|
||||
ticker: sellForm.ticker.trim(),
|
||||
name: sellForm.name.trim(),
|
||||
quantity: qty,
|
||||
avg_price: avgPrice,
|
||||
sell_price: sellPrice,
|
||||
buy_amount: buyAmount,
|
||||
sell_amount: sellAmount,
|
||||
realized_profit: realizedProfit,
|
||||
realized_rate: realizedRate,
|
||||
sold_at: sellForm.sold_at ? new Date(sellForm.sold_at).toISOString() : new Date().toISOString(),
|
||||
};
|
||||
|
||||
try {
|
||||
if (sellEditId != null) {
|
||||
const updated = await updateSellHistory(sellEditId, payload);
|
||||
setSellHistory((prev) =>
|
||||
prev.map((r) => (r.id === sellEditId ? (updated ?? { ...payload, id: sellEditId }) : r))
|
||||
);
|
||||
} else {
|
||||
const saved = await addSellHistory(payload);
|
||||
setSellHistory((prev) => [saved ?? { ...payload, id: Date.now() }, ...prev]);
|
||||
}
|
||||
handleSellFormClose();
|
||||
} catch (err) {
|
||||
setSellFormError(err?.message ?? String(err));
|
||||
} finally {
|
||||
setSellFormSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
/* ── report sort ─────────────────────────────────────────────── */
|
||||
|
||||
const handleReportSort = (field) => {
|
||||
@@ -801,6 +967,37 @@ ${holdingsText}${marketText}
|
||||
});
|
||||
}, [portfolioHoldings, reportSortField, reportSortDir]);
|
||||
|
||||
/* ── derived: 실현손익 필터 ────────────────────────────────────── */
|
||||
|
||||
const sellHistoryBrokers = useMemo(() => {
|
||||
const set = new Set(sellHistory.map((r) => r.broker).filter(Boolean));
|
||||
return ['ALL', ...Array.from(set).sort()];
|
||||
}, [sellHistory]);
|
||||
|
||||
const filteredSellHistory = useMemo(() => {
|
||||
const now = new Date();
|
||||
const periodMs = {
|
||||
'1M': 30 * 86400000,
|
||||
'3M': 90 * 86400000,
|
||||
'6M': 180 * 86400000,
|
||||
'1Y': 365 * 86400000,
|
||||
'ALL': Infinity,
|
||||
}[sellHistoryPeriod] ?? Infinity;
|
||||
return sellHistory.filter((r) => {
|
||||
if (sellHistoryBroker !== 'ALL' && r.broker !== sellHistoryBroker) return false;
|
||||
const diff = now - new Date(r.sold_at);
|
||||
return diff <= periodMs;
|
||||
});
|
||||
}, [sellHistory, sellHistoryBroker, sellHistoryPeriod]);
|
||||
|
||||
const sellHistorySummary = useMemo(() => {
|
||||
const totalProfit = filteredSellHistory.reduce((s, r) => s + (r.realized_profit ?? 0), 0);
|
||||
const totalSell = filteredSellHistory.reduce((s, r) => s + (r.sell_amount ?? 0), 0);
|
||||
const totalBuy = filteredSellHistory.reduce((s, r) => s + (r.buy_amount ?? 0), 0);
|
||||
const rate = totalBuy > 0 ? (totalProfit / totalBuy) * 100 : 0;
|
||||
return { totalProfit, totalSell, totalBuy, rate, count: filteredSellHistory.length };
|
||||
}, [filteredSellHistory]);
|
||||
|
||||
/* ── render ───────────────────────────────────────────────────── */
|
||||
|
||||
return (
|
||||
@@ -1555,6 +1752,8 @@ ${holdingsText}${marketText}
|
||||
</p>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* sell history → 드로어로 이동됨 */}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -2195,6 +2394,321 @@ ${holdingsText}${marketText}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* ── 실현손익 floating 토글 버튼 (마우스 추적) ────────── */}
|
||||
{!sellDrawerOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="sh-floating-toggle"
|
||||
onClick={() => {
|
||||
setSellDrawerOpen(true);
|
||||
loadSellHistory();
|
||||
}}
|
||||
title="실현손익 내역"
|
||||
>
|
||||
<span className="sh-floating-toggle__icon">💹</span>
|
||||
<span className="sh-floating-toggle__label">실현손익</span>
|
||||
{sellHistory.length > 0 && (
|
||||
<span className="sh-floating-toggle__badge">{sellHistory.length}</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* ════════════════════════════════════════════════════════
|
||||
실현손익 드로어
|
||||
════════════════════════════════════════════════════════ */}
|
||||
{sellDrawerOpen && (
|
||||
<div
|
||||
className="sh-backdrop"
|
||||
onClick={() => { setSellDrawerOpen(false); handleSellFormClose(); }}
|
||||
/>
|
||||
)}
|
||||
<aside className={`sh-drawer ${sellDrawerOpen ? 'is-open' : ''}`}>
|
||||
{/* 드로어 헤더 */}
|
||||
<div className="sh-drawer__header">
|
||||
<div>
|
||||
<p className="sh-drawer__eyebrow">실현손익</p>
|
||||
<h3 className="sh-drawer__title">매도 거래 내역</h3>
|
||||
</div>
|
||||
<div className="sh-drawer__header-actions">
|
||||
{sellHistoryLoading && <Loading type="spinner" message="" />}
|
||||
<button
|
||||
className="button ghost small"
|
||||
onClick={loadSellHistory}
|
||||
disabled={sellHistoryLoading}
|
||||
>
|
||||
새로고침
|
||||
</button>
|
||||
<button
|
||||
className="button primary small"
|
||||
onClick={sellFormOpen && sellEditId == null ? handleSellFormClose : handleSellFormOpen}
|
||||
>
|
||||
{sellFormOpen && sellEditId == null ? '취소' : '+ 추가'}
|
||||
</button>
|
||||
<button
|
||||
className="sh-drawer__close"
|
||||
type="button"
|
||||
onClick={() => { setSellDrawerOpen(false); handleSellFormClose(); }}
|
||||
aria-label="닫기"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 수동 추가 / 수정 폼 */}
|
||||
{sellFormOpen && (
|
||||
<form className="sh-form" onSubmit={handleSellFormSubmit}>
|
||||
<div className="sh-form__title">
|
||||
{sellEditId != null ? '거래 내역 수정' : '매도 내역 수동 추가'}
|
||||
</div>
|
||||
<div className="sh-form__grid">
|
||||
<label>
|
||||
증권사
|
||||
<input
|
||||
type="text"
|
||||
value={sellForm.broker}
|
||||
onChange={(e) => setSellForm((p) => ({ ...p, broker: e.target.value }))}
|
||||
placeholder="KB증권"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
종목코드
|
||||
<input
|
||||
type="text"
|
||||
value={sellForm.ticker}
|
||||
onChange={(e) => setSellForm((p) => ({ ...p, ticker: e.target.value }))}
|
||||
placeholder="005930"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
종목명
|
||||
<input
|
||||
type="text"
|
||||
value={sellForm.name}
|
||||
onChange={(e) => setSellForm((p) => ({ ...p, name: e.target.value }))}
|
||||
placeholder="삼성전자"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
수량
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
step={1}
|
||||
value={sellForm.quantity}
|
||||
onChange={(e) => setSellForm((p) => ({ ...p, quantity: e.target.value }))}
|
||||
placeholder="10"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
평균 매입가 (원)
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
step={1}
|
||||
value={sellForm.avg_price}
|
||||
onChange={(e) => setSellForm((p) => ({ ...p, avg_price: e.target.value }))}
|
||||
placeholder="58000"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
매도가 (원)
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
step={1}
|
||||
value={sellForm.sell_price}
|
||||
onChange={(e) => setSellForm((p) => ({ ...p, sell_price: e.target.value }))}
|
||||
placeholder="62000"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="sh-form__datetime">
|
||||
매도 일시
|
||||
<input
|
||||
type="datetime-local"
|
||||
value={sellForm.sold_at}
|
||||
onChange={(e) => setSellForm((p) => ({ ...p, sold_at: e.target.value }))}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{sellForm.quantity && sellForm.avg_price && sellForm.sell_price && (() => {
|
||||
const qty = Number(sellForm.quantity);
|
||||
const buy = Number(sellForm.avg_price) * qty;
|
||||
const sell = Number(sellForm.sell_price) * qty;
|
||||
const profit = sell - buy;
|
||||
const rate = buy > 0 ? (profit / buy) * 100 : 0;
|
||||
return (
|
||||
<div className="sh-form__preview">
|
||||
<span>매도금액 <strong>{formatNumber(Math.round(sell))}원</strong></span>
|
||||
<span>실현손익 <strong className={`stock-profit ${profitColorClass(profit)}`}>{formatNumber(Math.round(profit))}원</strong></span>
|
||||
<span>수익률 <strong className={`stock-profit ${profitColorClass(rate)}`}>{formatPercent(rate)}</strong></span>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<div className="sh-form__actions">
|
||||
<button className="button primary" type="submit" disabled={sellFormSaving}>
|
||||
{sellFormSaving ? '저장 중...' : (sellEditId != null ? '수정 저장' : '추가')}
|
||||
</button>
|
||||
<button className="button ghost" type="button" onClick={handleSellFormClose} disabled={sellFormSaving}>
|
||||
취소
|
||||
</button>
|
||||
{sellFormError && <p className="stock-error">{sellFormError}</p>}
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{/* 필터 바 */}
|
||||
<div className="sell-history__filters">
|
||||
<div className="sell-history__filter-group">
|
||||
<span className="sell-history__filter-label">계좌</span>
|
||||
{sellHistoryBrokers.map((b) => (
|
||||
<button
|
||||
key={b}
|
||||
type="button"
|
||||
className={`sell-history__filter-btn ${sellHistoryBroker === b ? 'is-active' : ''}`}
|
||||
onClick={() => setSellHistoryBroker(b)}
|
||||
>
|
||||
{b === 'ALL' ? '전체' : b}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="sell-history__filter-group">
|
||||
<span className="sell-history__filter-label">기간</span>
|
||||
{[
|
||||
{ label: '1개월', value: '1M' },
|
||||
{ label: '3개월', value: '3M' },
|
||||
{ label: '6개월', value: '6M' },
|
||||
{ label: '1년', value: '1Y' },
|
||||
{ label: '전체', value: 'ALL' },
|
||||
].map(({ label, value }) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
className={`sell-history__filter-btn ${sellHistoryPeriod === value ? 'is-active' : ''}`}
|
||||
onClick={() => setSellHistoryPeriod(value)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 요약 카드 */}
|
||||
{filteredSellHistory.length > 0 && (
|
||||
<div className="sell-history__summary">
|
||||
<div className="sell-history__summary-card">
|
||||
<span>거래 횟수</span>
|
||||
<strong>{sellHistorySummary.count}건</strong>
|
||||
</div>
|
||||
<div className="sell-history__summary-card">
|
||||
<span>총 매도금액</span>
|
||||
<strong>{formatNumber(sellHistorySummary.totalSell)}원</strong>
|
||||
</div>
|
||||
<div className="sell-history__summary-card">
|
||||
<span>실현손익 합계</span>
|
||||
<strong className={`stock-profit ${profitColorClass(sellHistorySummary.totalProfit)}`}>
|
||||
{formatNumber(Math.round(sellHistorySummary.totalProfit))}원
|
||||
</strong>
|
||||
</div>
|
||||
<div className="sell-history__summary-card">
|
||||
<span>평균 수익률</span>
|
||||
<strong className={`stock-profit ${profitColorClass(sellHistorySummary.rate)}`}>
|
||||
{formatPercent(sellHistorySummary.rate)}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 거래 내역 목록 */}
|
||||
{filteredSellHistory.length > 0 ? (
|
||||
<div className="sh-drawer__list">
|
||||
{filteredSellHistory.map((r) => {
|
||||
const profitN = r.realized_profit ?? 0;
|
||||
const rateN = r.realized_rate ?? 0;
|
||||
return (
|
||||
<div key={r.id} className="sh-drawer__item">
|
||||
<div className="sh-drawer__item-top">
|
||||
<div className="sh-drawer__item-name">
|
||||
<span>{r.name}</span>
|
||||
{r.ticker && <code>{r.ticker}</code>}
|
||||
</div>
|
||||
<div className="sh-drawer__item-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="button ghost small"
|
||||
onClick={() => handleSellEditStart(r)}
|
||||
title="수정"
|
||||
>
|
||||
✏️
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="button ghost small pf-btn-danger"
|
||||
onClick={() => handleDeleteSellRecord(r.id)}
|
||||
title="삭제"
|
||||
>
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sh-drawer__item-meta">
|
||||
<span className="sell-history__broker">{r.broker}</span>
|
||||
<span className="sell-history__date">
|
||||
{new Date(r.sold_at).toLocaleString('ko-KR', {
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<div className="sh-drawer__item-metrics">
|
||||
<div>
|
||||
<span>수량</span>
|
||||
<strong>{formatNumber(r.quantity)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>매입가</span>
|
||||
<strong>{formatNumber(r.avg_price)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>매도가</span>
|
||||
<strong>{formatNumber(r.sell_price)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>매도금액</span>
|
||||
<strong>{formatNumber(Math.round(r.sell_amount))}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>실현손익</span>
|
||||
<strong className={`stock-profit ${profitColorClass(profitN)}`}>
|
||||
{formatNumber(Math.round(profitN))}
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>수익률</span>
|
||||
<strong className={`stock-profit ${profitColorClass(rateN)}`}>
|
||||
{formatPercent(rateN)}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="stock-empty sh-drawer__empty">
|
||||
{sellHistory.length === 0
|
||||
? '아직 매도 기록이 없습니다.'
|
||||
: '필터 조건에 맞는 기록이 없습니다.'}
|
||||
</p>
|
||||
)}
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user