diff --git a/src/pages/stock/components/PortfolioTab.jsx b/src/pages/stock/components/PortfolioTab.jsx index 9acc7de..9665e68 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 { 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'; import CashPanel from './portfolio/CashPanel'; +import BrokerSection from './portfolio/BrokerSection'; const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => ( <> @@ -57,254 +56,9 @@ const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => ( {/* Broker cards stacked */} - {pf.brokerGroups.map(([broker, items]) => { - const bSummary = pf.getBrokerSummary(items); - const color = pf.brokerColors[broker]; - return ( -
-
-
-

- {broker} -

-

{broker} 보유 현황

-

- {items.length}종목 · 총 매입{' '} - {formatNumber(bSummary.totalBuy)} · 평가{' '} - {formatNumber(bSummary.totalEval)} · 손익{' '} - - {formatNumber(bSummary.totalProfit)} ( - {formatPercent(bSummary.totalProfitRate)}) - - {(() => { - const bc = pf.cashList.find((c) => c.broker === broker); - return bc ? ( - - 예수금 {formatNumber(bc.cash)}원 - - ) : null; - })()} -

-
-
-
- {items.map((item) => { - const profitAmt = item.profit_amount; - const profitRate = item.profit_rate; - const profitAmtN = toNumeric(profitAmt); - const profitRateN = toNumeric(profitRate); - const isEditing = pf.editingId === item.id; - const isDeleting = pf.deleteConfirmId === item.id; - const isSelling = pf.sellConfirmId === item.id; - const sellPrice = item.current_price ?? item.avg_price; - const saleAmount = sellPrice != null ? sellPrice * (item.quantity ?? 0) : null; - - return ( -
- {isEditing ? ( -
-
- - - -
-
- - -
-
- ) : ( - <> -
-

- {item.name ?? item.ticker ?? 'N/A'} -

- - {item.ticker ?? ''} - -
-
- 수량 - {formatNumber(item.quantity)} -
-
- 평균단가 - {formatNumber(item.avg_price)} -
-
- 매입가 - {formatNumber(item.purchase_price ?? item.avg_price)} -
-
- 현재가 - - {item.current_price != null - ? formatNumber(item.current_price) - : '조회 실패'} - - -
-
- 평가금액 - - {item.current_price != null && item.quantity != null - ? formatNumber(item.current_price * item.quantity) - : '-'} - -
-
- 수익률 - - {profitRate != null ? formatPercent(profitRate) : '-'} - -
-
- 평가손익 - - {profitAmt != null ? formatNumber(profitAmt) : '-'} - -
-
- {!isSelling && !isDeleting && ( - - )} - {isSelling ? ( -
- - {item.current_price == null && ( - 현재가 미조회 — 매입가 기준 - )} - {saleAmount != null - ? `${formatNumber(saleAmount)}원 매도 후 예수금 반영` - : '매도 처리'} - - - -
- ) : isDeleting ? ( - <> - - - - ) : ( - <> - - - - )} -
- - )} -
- ); - })} -
-
- ); - })} + {pf.brokerGroups.map(([broker, items]) => ( + + ))} {pf.portfolioLoaded && pf.portfolioHoldings.length === 0 && !pf.portfolioError && (
diff --git a/src/pages/stock/components/portfolio/BrokerSection.jsx b/src/pages/stock/components/portfolio/BrokerSection.jsx new file mode 100644 index 0000000..68fa62b --- /dev/null +++ b/src/pages/stock/components/portfolio/BrokerSection.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { formatNumber, formatPercent, profitColorClass } from '../../stockUtils'; +import HoldingRow from './HoldingRow'; + +const BrokerSection = ({ broker, items, pf, handleSell }) => { + const bSummary = pf.getBrokerSummary(items); + const color = pf.brokerColors[broker]; + return ( +
+
+
+

+ {broker} +

+

{broker} 보유 현황

+

+ {items.length}종목 · 총 매입{' '} + {formatNumber(bSummary.totalBuy)} · 평가{' '} + {formatNumber(bSummary.totalEval)} · 손익{' '} + + {formatNumber(bSummary.totalProfit)} ( + {formatPercent(bSummary.totalProfitRate)}) + + {(() => { + const bc = pf.cashList.find((c) => c.broker === broker); + return bc ? ( + + 예수금 {formatNumber(bc.cash)}원 + + ) : null; + })()} +

+
+
+
+ {items.map((item) => ( + + ))} +
+
+ ); +}; + +export default BrokerSection; diff --git a/src/pages/stock/components/portfolio/HoldingRow.jsx b/src/pages/stock/components/portfolio/HoldingRow.jsx new file mode 100644 index 0000000..40e8432 --- /dev/null +++ b/src/pages/stock/components/portfolio/HoldingRow.jsx @@ -0,0 +1,215 @@ +import React from 'react'; +import { formatNumber, formatPercent, toNumeric, profitColorClass } from '../../stockUtils'; +import PriceSessionBadge from './PriceSessionBadge'; + +const HoldingRow = ({ item, pf, handleSell }) => { + const profitAmt = item.profit_amount; + const profitRate = item.profit_rate; + const profitAmtN = toNumeric(profitAmt); + const profitRateN = toNumeric(profitRate); + const isEditing = pf.editingId === item.id; + const isDeleting = pf.deleteConfirmId === item.id; + const isSelling = pf.sellConfirmId === item.id; + const sellPrice = item.current_price ?? item.avg_price; + const saleAmount = sellPrice != null ? sellPrice * (item.quantity ?? 0) : null; + + return ( +
+ {isEditing ? ( +
+
+ + + +
+
+ + +
+
+ ) : ( + <> +
+

+ {item.name ?? item.ticker ?? 'N/A'} +

+ + {item.ticker ?? ''} + +
+
+ 수량 + {formatNumber(item.quantity)} +
+
+ 평균단가 + {formatNumber(item.avg_price)} +
+
+ 매입가 + {formatNumber(item.purchase_price ?? item.avg_price)} +
+
+ 현재가 + + {item.current_price != null + ? formatNumber(item.current_price) + : '조회 실패'} + + +
+
+ 평가금액 + + {item.current_price != null && item.quantity != null + ? formatNumber(item.current_price * item.quantity) + : '-'} + +
+
+ 수익률 + + {profitRate != null ? formatPercent(profitRate) : '-'} + +
+
+ 평가손익 + + {profitAmt != null ? formatNumber(profitAmt) : '-'} + +
+
+ {!isSelling && !isDeleting && ( + + )} + {isSelling ? ( +
+ + {item.current_price == null && ( + 현재가 미조회 — 매입가 기준 + )} + {saleAmount != null + ? `${formatNumber(saleAmount)}원 매도 후 예수금 반영` + : '매도 처리'} + + + +
+ ) : isDeleting ? ( + <> + + + + ) : ( + <> + + + + )} +
+ + )} +
+ ); +}; + +export default HoldingRow;