diff --git a/src/pages/stock/components/PortfolioTab.jsx b/src/pages/stock/components/PortfolioTab.jsx
index 9582799..2412597 100644
--- a/src/pages/stock/components/PortfolioTab.jsx
+++ b/src/pages/stock/components/PortfolioTab.jsx
@@ -1,11 +1,10 @@
import React from 'react';
import Loading from '../../../components/Loading';
-import {
- ResponsiveContainer, AreaChart, Area, XAxis, YAxis,
- Tooltip as ChartTooltip,
-} from 'recharts';
-import { formatNumber, formatPercent, toNumeric, profitColorClass, numFitClass } from '../stockUtils';
+import { formatNumber, formatPercent, toNumeric, profitColorClass } from '../stockUtils';
import PriceSessionBadge from './portfolio/PriceSessionBadge';
+import AddHoldingForm from './portfolio/AddHoldingForm';
+import PortfolioSummary from './portfolio/PortfolioSummary';
+import AssetHistoryChart from './portfolio/AssetHistoryChart';
const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => (
<>
@@ -44,222 +43,13 @@ const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => (
{/* Add form */}
- {pf.addFormOpen && (
-
- )}
+
{/* Portfolio total summary */}
- {pf.portfolioHoldings.length > 0 && (
-
- {[
- { label: '총 매입', value: pf.portfolioSummary.total_buy },
- { label: '총 평가', value: pf.portfolioSummary.total_eval },
- { label: '총 손익', value: pf.portfolioSummary.total_profit, isProfit: true },
- { label: '수익률', value: pf.portfolioSummary.total_profit_rate, isRate: true },
- ].map((s) => {
- const display = s.isRate ? formatPercent(s.value) : formatNumber(s.value);
- const profitCls = s.isProfit || s.isRate
- ? `stock-profit ${profitColorClass(toNumeric(s.value))}`
- : '';
- return (
-
- {s.label}
-
- {display}
-
-
- );
- })}
- {pf.totalCash != null && (() => {
- const display = `${formatNumber(pf.totalCash)}원`;
- return (
-
- 예수금 합계
- {display}
-
- );
- })()}
- {pf.totalAssets != null && (() => {
- const display = `${formatNumber(pf.totalAssets)}원`;
- return (
-
- 총 자산
- {display}
-
- );
- })()}
-
- )}
+
{/* 자산 추이 차트 */}
-
-
-
총 자산 추이
-
- {[
- { label: '7일', value: 7 },
- { label: '30일', value: 30 },
- { label: '90일', value: 90 },
- { label: '전체', value: 0 },
- ].map(({ label, value }) => (
-
- ))}
-
-
-
-
- {asset.assetHistoryLoading ? (
-
-
-
- ) : Array.isArray(asset.assetHistory) && asset.assetHistory.length >= 1 ? (
-
-
-
-
-
-
-
-
- v?.slice(5)}
- tickLine={false}
- axisLine={false}
- interval="preserveStartEnd"
- />
-
- [`${new Intl.NumberFormat('ko-KR').format(v)}원`, '총 자산']}
- />
-
-
-
- ) : (
-
- 저장된 자산 추이 데이터가 없습니다. 📸 스냅샷 버튼으로 오늘 자산을 기록하세요.
-
- )}
-
+
{/* 예수금 패널 */}
diff --git a/src/pages/stock/components/portfolio/AddHoldingForm.jsx b/src/pages/stock/components/portfolio/AddHoldingForm.jsx
new file mode 100644
index 0000000..c8dc18b
--- /dev/null
+++ b/src/pages/stock/components/portfolio/AddHoldingForm.jsx
@@ -0,0 +1,95 @@
+import React from 'react';
+
+const AddHoldingForm = ({ pf }) => {
+ if (!pf.addFormOpen) return null;
+ return (
+
+ );
+};
+
+export default AddHoldingForm;
diff --git a/src/pages/stock/components/portfolio/AssetHistoryChart.jsx b/src/pages/stock/components/portfolio/AssetHistoryChart.jsx
new file mode 100644
index 0000000..e3d49c9
--- /dev/null
+++ b/src/pages/stock/components/portfolio/AssetHistoryChart.jsx
@@ -0,0 +1,94 @@
+import React from 'react';
+import Loading from '../../../../components/Loading';
+import {
+ ResponsiveContainer, AreaChart, Area, XAxis, YAxis,
+ Tooltip as ChartTooltip,
+} from 'recharts';
+
+const AssetHistoryChart = ({ asset, pf, handleSaveSnapshot }) => (
+
+
+
총 자산 추이
+
+ {[
+ { label: '7일', value: 7 },
+ { label: '30일', value: 30 },
+ { label: '90일', value: 90 },
+ { label: '전체', value: 0 },
+ ].map(({ label, value }) => (
+
+ ))}
+
+
+
+
+ {asset.assetHistoryLoading ? (
+
+
+
+ ) : Array.isArray(asset.assetHistory) && asset.assetHistory.length >= 1 ? (
+
+
+
+
+
+
+
+
+ v?.slice(5)}
+ tickLine={false}
+ axisLine={false}
+ interval="preserveStartEnd"
+ />
+
+ [`${new Intl.NumberFormat('ko-KR').format(v)}원`, '총 자산']}
+ />
+
+
+
+ ) : (
+
+ 저장된 자산 추이 데이터가 없습니다. 📸 스냅샷 버튼으로 오늘 자산을 기록하세요.
+
+ )}
+
+);
+
+export default AssetHistoryChart;
diff --git a/src/pages/stock/components/portfolio/PortfolioSummary.jsx b/src/pages/stock/components/portfolio/PortfolioSummary.jsx
new file mode 100644
index 0000000..4d883a4
--- /dev/null
+++ b/src/pages/stock/components/portfolio/PortfolioSummary.jsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import { formatNumber, formatPercent, toNumeric, profitColorClass, numFitClass } from '../../stockUtils';
+
+const PortfolioSummary = ({ pf }) => {
+ if (!(pf.portfolioHoldings.length > 0)) return null;
+ return (
+
+ {[
+ { label: '총 매입', value: pf.portfolioSummary.total_buy },
+ { label: '총 평가', value: pf.portfolioSummary.total_eval },
+ { label: '총 손익', value: pf.portfolioSummary.total_profit, isProfit: true },
+ { label: '수익률', value: pf.portfolioSummary.total_profit_rate, isRate: true },
+ ].map((s) => {
+ const display = s.isRate ? formatPercent(s.value) : formatNumber(s.value);
+ const profitCls = s.isProfit || s.isRate
+ ? `stock-profit ${profitColorClass(toNumeric(s.value))}`
+ : '';
+ return (
+
+ {s.label}
+
+ {display}
+
+
+ );
+ })}
+ {pf.totalCash != null && (() => {
+ const display = `${formatNumber(pf.totalCash)}원`;
+ return (
+
+ 예수금 합계
+ {display}
+
+ );
+ })()}
+ {pf.totalAssets != null && (() => {
+ const display = `${formatNumber(pf.totalAssets)}원`;
+ return (
+
+ 총 자산
+ {display}
+
+ );
+ })()}
+
+ );
+};
+
+export default PortfolioSummary;