Files
web-page/docs/superpowers/specs/2026-07-10-fe-module-refactor-portfoliotab-design.md
2026-07-10 10:08:39 +09:00

6.2 KiB

PortfolioTab.jsx 분해 리팩토링 — 설계 (FE 모듈 sweep 서브프로젝트 3)

  • 작성일: 2026-07-10
  • 역할/저장소: FE (web-ui)
  • 범위: FE 대형 컴포넌트 분해 sweep의 3번째 서브프로젝트. 컨벤션은 확립됨(web-ui/CLAUDE.md "기능 모듈 구조") — 적용만.
  • 성격: behavior-preserving 순수 추출. 로직/JSX/스타일 변경 없음.

1. 배경 & 목표

src/pages/stock/components/PortfolioTab.jsx(671줄)는 StockTrade의 포트폴리오 탭 컴포넌트로, 로컬 상태 없이 pf/asset 훅을 props로 받는 단일 대형 표현 컴포넌트다. 논리 블록(추가폼/총자산요약/자산추이차트/예수금패널/브로커섹션/보유행/NXT뱃지)을 stock/components/portfolio/ 하위 표현 컴포넌트로 동작 보존 분해한다.

비목표: 동작/스타일 변경, prop shape 정제(개별 prop으로 좁히기는 후속), HoldingRow 내부 상태 추가분할.


2. 현 구조 맵 (PortfolioTab.jsx 671줄)

  • formatPriceTime(순수, 9-14), PriceSessionBadge(16-27) — NXT 뱃지
  • PortfolioTab({ pf, asset, handleSell, handleSaveSnapshot }):
    • 에러(31-33)
    • 관리 section(36-282): 헤더(refresh/추가 토글) + 추가 폼(66-153) + 총자산 요약 카드(155-196) + 자산 추이 차트(198-281)
    • 예수금 패널(285-409): 예수금 테이블(inline edit/delete) + 예수금 추가 폼
    • 브로커 섹션 반복(412-659): pf.brokerGroups.map → 브로커별 section + items.map보유 행(447-654, view/edit/매도확인/삭제확인 4상태)
    • 빈 상태(661-667)

의존: Loading(src/components), recharts(AreaChart 등), stockUtils(formatNumber/formatPercent/toNumeric/profitColorClass/numFitClass).


3. 목표 file map

src/pages/stock/components/
├── PortfolioTab.jsx              # shell (~70줄): 에러 + 관리 section(헤더 inline + AddHoldingForm + PortfolioSummary + AssetHistoryChart) + CashPanel + brokerGroups.map(BrokerSection) + 빈상태
└── portfolio/
    ├── portfolioUtils.js         # formatPriceTime (순수)
    ├── portfolioUtils.test.js    # (신규)
    ├── PriceSessionBadge.jsx     # ({ session, asOf })
    ├── AddHoldingForm.jsx        # ({ pf })
    ├── PortfolioSummary.jsx      # ({ pf })
    ├── AssetHistoryChart.jsx     # ({ asset, pf, handleSaveSnapshot })
    ├── CashPanel.jsx             # ({ pf })
    ├── BrokerSection.jsx         # ({ broker, items, pf, handleSell }) — HoldingRow 반복
    └── HoldingRow.jsx            # ({ item, pf, handleSell })

props 스레딩(behavior-preserving): 각 하위에 pf(훅 객체 전체)/asset/핸들러를 그대로 전달. 컴포넌트 본문은 현 pf.xxx/asset.xxx 참조를 그대로 사용 → prop shape·동작 불변.

import 경로 규칙:

  • portfolio/*.jsx → stockUtils '../../stockUtils'; Loading '../../../../components/Loading'; portfolioUtils './portfolioUtils'.
  • HoldingRow.jsxPriceSessionBadge './PriceSessionBadge'.
  • PortfolioTab.jsx(stock/components/) → 하위 './portfolio/*'. (기존 Loading '../../../components/Loading', stockUtils '../stockUtils'는 shell이 계속 쓰는 것만 유지.)

각 컴포넌트가 실제 사용하는 심볼만 import(빌드/lint가 누락·미사용 검출).


4. 컴포넌트별 책임 (분해 후 검증 관점)

  • PriceSessionBadge(~20줄): session이 NXT_AFTER/NXT_PRE일 때만 뱃지. formatPriceTime(utils) 사용.
  • AddHoldingForm(~90줄): pf.addFormOpen일 때 폼. pf.addForm/pf.setAddForm/pf.handleAddSubmit/pf.addLoading/pf.addError.
  • PortfolioSummary(~45줄): pf.portfolioHoldings.length>0일 때 총매입/평가/손익/수익률 + 예수금/총자산 카드.
  • AssetHistoryChart(~85줄): asset.assetHistory* + asset.setAssetHistoryDays + handleSaveSnapshot + pf.totalAssets. recharts AreaChart.
  • CashPanel(~120줄): pf.cashList 테이블(inline edit: pf.cashEditing*/pf.handleCashInline*, delete: pf.handleCashDelete) + 예수금 추가 폼(pf.cashForm/pf.handleCashSave/…).
  • BrokerSection(~55줄): pf.getBrokerSummary/pf.brokerColors/pf.cashList + items.map(HoldingRow).
  • HoldingRow(~200줄): 개별 종목. view/edit(pf.editingId/pf.editForm/pf.handleEditSave…)/매도확인(pf.sellConfirmId/handleSell/pf.sellLoading)/삭제확인(pf.deleteConfirmId/pf.handleDelete). PriceSessionBadge 사용.

5. 테스트 (안전망 — 현재 PortfolioTab 테스트 0)

  1. portfolioUtils.test.js(신규, 필수)formatPriceTime: 유효 ISO → "HH:MM"(제로패드), falsy/invalid → "". (현 구현 동작 고정.)
  2. PriceSessionBadge.test.jsx(신규, 스모크) — session='NXT_AFTER'→"NXT" 렌더, 'NXT_PRE'→"NXT 프리" 렌더, 그 외(예: undefined/'REGULAR')→렌더 없음(null).
  3. 나머지 표현 컴포넌트는 props 스레딩 위주 → 이번 범위 스모크 최소화(추출 검증은 build+lint+수동).
  4. 검증 게이트: 각 단계 npm run build + npm run test:run 전체 green(회귀 0) + npm run lint 신규/수정 파일 0 에러. 최종 개발서버 /stock/trade 포트폴리오 탭에서 추가/요약/차트/예수금(인라인 편집·삭제)/브로커/보유행(편집·매도확인·삭제확인)이 리팩토링 전과 동일 동작.

6. 완료 기준

  • PortfolioTab.jsx가 shell(~70줄) + portfolio/ 7컴포넌트 + portfolioUtils(+test)로 분해, 동작/스타일 불변.
  • portfolioUtils.test.js·PriceSessionBadge.test.jsx 통과, 전체 npm run test:run green.
  • npm run lint·npm run build 통과.
  • /stock/trade 포트폴리오 탭 수동 검증 동일 동작.

7. 리스크 / 오픈 이슈

  • 테스트 부재 컴포넌트 리팩토링: 순수 추출 + build/lint/유닛/스모크/수동으로 위험 최소화.
  • prop drilling: pf 전체 전달은 behavior-preserving 우선(정제는 후속). 각 컴포넌트가 쓰는 pf.*만 실제 참조.
  • HoldingRow ~200줄: 단일 책임(보유 1행). 내부 edit/매도/삭제 분할은 후속 follow-up.
  • 다음 서브프로젝트: InstaCards(1013) → MusicStudio(1936).