Files
web-page/docs/superpowers/specs/2026-07-09-fe-module-refactor-realestate-design.md
2026-07-09 22:54:18 +09:00

6.4 KiB

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

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

1. 배경 & 목표

src/pages/realestate/RealEstate.jsx(909줄, /realestate/property 관심 단지 페이지)를 기능 모듈 구조로 동작 보존 분해한다. Subscription과 달리 메인이 데이터 상태(complexes)+CRUD API를 직접 보유하고 하위는 순수 표현 컴포넌트(props)인 "스마트 컨테이너 + 표현" 구조 → 데이터 로직은 hooks/useComplexes.js로 추출(컨벤션: shell은 조합만).

비목표: 동작/스타일 변경, 신규 기능, MapFlyTo/EventItem/CustomTooltip 같은 소형 내부 컴포넌트의 과분해.


2. 현 구조 맵 (RealEstate.jsx 909줄)

  • 상수/데이터: SAMPLE_COMPLEXES(폴백 샘플 4개), STATUS_CONFIG, PRIORITY_LABELS, EMPTY_FORM, TABS(['목록','일정','분석'])
  • 순수 헬퍼: formatDate, formatPrice, getDDays, createMarkerIcon(leaflet L.divIcon)
  • 컴포넌트: MapFlyTo(react-leaflet 훅), ComplexCard, RightPanel(지도+상세, ~183줄, MapFlyTo 사용), ScheduleView(내부 EventItem), PriceAnalysis(차트+표, 내부 CustomTooltip), ComplexModal(폼 모달)
  • 메인 RealEstate: 상태(complexes/selectedComplex/activeTab/filterStatus/showModal/editingComplex) + useEffect 로드 + handleAdd/handleUpdate/handleDelete/handleModalSave + filteredComplexes/stats memo + 헤더/탭/목록·일정·분석 렌더 + 모달.

API: apiGet/apiPost/apiPut/apiDelete/api/realestate/complexes(+/:id).


3. 목표 file map

src/pages/realestate/
├── RealEstate.jsx            # shell/컨테이너 (~150줄): UI 상태(selected/activeTab/filter/modal) + useComplexes 소비 + 조합
├── RealEstate.css            # 변경 없음 (shell에서 import 유지)
├── realEstateUtils.js        # SAMPLE_COMPLEXES·STATUS_CONFIG·PRIORITY_LABELS·EMPTY_FORM·TABS + formatDate·formatPrice·getDDays·createMarkerIcon
├── realEstateUtils.test.js   # 순수헬퍼 유닛 (신규)
├── hooks/
│   └── useComplexes.js       # complexes 상태 + 로드 + add/update/delete (낙관적 UI + API), { complexes, addComplex, updateComplex, deleteComplex } 반환
└── components/
    ├── ComplexCard.jsx       # ({ complex, isSelected, onClick })
    ├── RightPanel.jsx        # ({ complexes, selectedComplex, onSelectComplex, onEdit, onDelete }) — MapFlyTo 내부 유지
    ├── ScheduleView.jsx      # ({ complexes }) — EventItem 내부 중첩 유지
    ├── PriceAnalysis.jsx     # ({ complexes }) — CustomTooltip 내부 중첩 유지
    └── ComplexModal.jsx      # ({ complex, onClose, onSave })

import 경로 규칙: components/hooks → api '../../../api'; components/hooks → utils '../realEstateUtils'; shell → utils './realEstateUtils', hook './hooks/useComplexes', components './components/*', CSS './RealEstate.css'. realEstateUtils.jsleafletL만 import(createMarkerIcon용), api import 없음. RightPanel.jsx는 leaflet/react-leaflet import 필요.


4. useComplexes 훅 (데이터 레이어 추출)

현 메인의 데이터 로직을 그대로 옮김(동작 보존):

  • 상태 complexes(초기값 SAMPLE_COMPLEXES), useEffectGET /api/realestate/complexes 로드(배열 있으면 교체).
  • addComplex(data) — 낙관적 append(id: Date.now()) + POST.
  • updateComplex(data) — 낙관적 map 교체 + PUT /:id.
  • deleteComplex(id) — 낙관적 filter + DELETE /:id. (confirm은 shell에 유지할지 훅에 둘지 — 동작 보존 위해 현 위치(핸들러) 그대로: handleDeleteconfirm은 shell에 남기고 훅은 순수 삭제만 담당. 즉 훅은 deleteComplex(id)가 낙관적 삭제+API, shell이 confirm 후 호출.)
  • 반환: { complexes, addComplex, updateComplex, deleteComplex }. selectedComplex 동기화(삭제/수정 시)는 shell이 담당(UI 상태이므로).

주의: 현 코드의 handleUpdateselectedComplex?.id === data.idsetSelectedComplex(data)도 함. 이는 UI 상태(selectedComplex) 갱신이므로 shell에 유지. 훅은 complexes만 관리. shell의 handleUpdate 래퍼가 updateComplex(data) 호출 + selectedComplex 동기화.


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

  1. realEstateUtils.test.js(신규, 필수) — 순수 헬퍼:
    • formatPrice: 4500→"4,500만원", 0/falsy→"-".
    • getDDays: 오늘=D-Day, 미래 D-N, 과거 D+N, null→null.
    • formatDate: 유효 날짜 한국어 포맷, falsy→"-".
  2. 컴포넌트는 표현 위주 → 이번 범위 스모크 최소화(추출 검증은 build+lint+수동). ComplexCard 스모크(선택) 정도 가능.
  3. 검증 게이트: 각 단계 npm run build + npm run test:run 전체 green(회귀 0) + npm run lint 신규 파일 0 에러. 최종 개발서버 /realestate/property 3탭(목록/일정/분석)·지도 마커·단지 추가/편집/삭제 모달이 리팩토링 전과 동일 동작.

6. 완료 기준

  • RealEstate.jsx가 shell(~150줄) + hooks/useComplexes + realEstateUtils(+test) + components/5로 분해, 동작/스타일 불변.
  • realEstateUtils.test.js 통과, 전체 npm run test:run green.
  • npm run lint·npm run build 통과.
  • /realestate/property 수동 검증 동일 동작(지도·모달·탭·필터).

7. 리스크 / 오픈 이슈

  • 테스트 부재 컴포넌트 리팩토링: 순수 추출 + build/lint/유닛/수동으로 위험 최소화.
  • 훅 추출 시 상태 경계: complexes(데이터)는 훅, selectedComplex/activeTab/filter/modal(UI)은 shell. 현 동작(특히 update 시 selectedComplex 동기화, delete 시 confirm)을 그대로 재현.
  • leaflet/recharts import: RightPanel(leaflet)·PriceAnalysis(recharts)가 각자 필요한 것만 import. createMarkerIcon은 utils에서 L import.
  • 후속 follow-up: RightPanel(~200줄)은 지도/상세 2책임 — 필요 시 후속 분할 여지 기록.
  • 다음 서브프로젝트: PortfolioTab(671, stock/) → InstaCards(1013) → MusicStudio(1936).