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 && ( -
- - - - - - - - {pf.addError &&

{pf.addError}

} -
- )} + {/* 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 ( +
+ + + + + + + + {pf.addError &&

{pf.addError}

} +
+ ); +}; + +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;