[251122] fix: DetailPanel Theme,Hotels - 1

🕐 커밋 시간: 2025. 11. 22. 21:21:31

📊 변경 통계:
  • 총 파일: 10개
  • 추가: +76줄
  • 삭제: -19줄

📁 추가된 파일:
  + com.twin.app.shoptime/HOTEL_UI_HANDLING_REPORT.md
  + com.twin.app.shoptime/HOTEL_UI_VISUAL_GUIDE.md
  + com.twin.app.shoptime/THEME_PRODUCT_UI_ANALYSIS.md
  + com.twin.app.shoptime/THEME_PRODUCT_VISUAL_GUIDE.md
  + com.twin.app.shoptime/THEME_VS_HOTEL_COMPARISON.md
  + com.twin.app.shoptime/docs/todo/251122-detailpanel-diff.md

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/api/TAxios.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript):
     Added: extractProductMeta()
    🔄 Modified: SpotlightContainerDecorator()
     Deleted: SpotlightContainerDecorator()
  📄 com.twin.app.shoptime/HOTEL_UI_HANDLING_REPORT.md (md파일):
     Added: useEffect(), dispatch(), ThemeProduct(), setLabel(), Map(), forEach(), filter(), findIndex(), setAmenitiesInfos(), setSelectedIndex()
  📄 com.twin.app.shoptime/HOTEL_UI_VISUAL_GUIDE.md (md파일):
     Added: DetailPanel(), getThemeCurationDetailInfo(), getThemeHotelDetailInfo(), getMainCategoryDetail(), THeader(), TBody(), ThemeProduct(), Container(), rating(), TButton(), YouMayLike(), MobileSendPopUp(), selectedIndex(), setSelectedIndex(), ellipsis(), amenitiesBox(), c70850(), handleSMSClick(), dispatch(), clearThemeDetail()
  📄 com.twin.app.shoptime/THEME_PRODUCT_UI_ANALYSIS.md (md파일):
     Added: getThemeCurationDetailInfo(), productData(), Container(), ShowSingleOption(), ShowUnableOption(), useState(), useEffect(), setSelectedImage(), useMemo(), dispatch(), startVideoPlayer(), productDescription(), setTimeout(), useCallback(), isProductSoldOut(), ProductOption(), setSelectedIndex()
  📄 com.twin.app.shoptime/THEME_PRODUCT_VISUAL_GUIDE.md (md파일):
     Added: ShowProduct(), Container(), optionContainer(), ShowSingleOption(), ProductOption(), ShowUnableOption(), UnableOption(), selectedIndex(), setSelectedIndex(), descriptionClick(), setTabLabel(), setDescription(), dispatch(), handleIndicatorOptions(), handleSMSClick(), handleMobileSendPopupOpen(), setImageSelectedIndex(), StarRating(), ProductTag(), SingleOption()
  📄 com.twin.app.shoptime/THEME_VS_HOTEL_COMPARISON.md (md파일):
     Added: Product(), UnableOption(), StarRating(), dispatch(), getThemeCurationDetailInfo(), getThemeHotelDetailInfo()

🔧 주요 변경 내용:
  • API 서비스 레이어 개선
  • 개발 문서 및 가이드 개선
This commit is contained in:
2025-11-22 21:21:32 +09:00
parent ce51902150
commit ec5829eebe
10 changed files with 2514 additions and 19 deletions

View File

@@ -0,0 +1,437 @@
# Theme Product UI - 시각적 구조 가이드
## 📊 ShowProduct 렌더링 구조
```
ShowProduct (Main Component)
├─ Container (Spotlight)
│ └─ spotlight-IndicatorContainer
│ │
│ ├─ ThemeIndicator
│ │ ├─ [메인 이미지 영역] 834×930px
│ │ │ ├─ 비디오 플레이 버튼 (선택사항)
│ │ │ ├─ 메인 이미지
│ │ │ └─ [매진] 배지 (soldoutFlag="Y"일 때)
│ │ │
│ │ └─ [썸네일 스크롤] (TVirtualGridList)
│ │ ├─ 이미지 높이: 152px 고정
│ │ ├─ 상하 스크롤 네비게이션
│ │ └─ 현재 선택 강조 표시
│ │
│ └─ IndicatorOptions
│ ├─ 로고 + ID (상단)
│ ├─ 상품명 (제목)
│ ├─ 평점 + 태그
│ ├─ 버튼 영역
│ │ ├─ [DESCRIPTION] 버튼
│ │ ├─ [RETURNS & EXCHANGES] 버튼
│ │ └─ [SHOP BY MOBILE] 버튼
│ ├─ QR 코드 (160×160px)
│ └─ 설명 팝업 (조건부)
└─ optionContainer (하단)
├─ ShowSingleOption (결제가능 상품)
│ └─ ProductOption (래퍼)
│ └─ SingleOption
│ ├─ 옵션 선택 (좌석, 이용일 등)
│ ├─ 수량 선택
│ ├─ 가격 표시
│ └─ [구매] 버튼
└─ ShowUnableOption (결제불가 상품)
└─ ProductOption (래퍼)
└─ UnableOption
├─ 구매 불가 메시지
├─ SMS 공유 옵션
└─ [SHOP BY MOBILE] 버튼
```
---
## 🖼️ ThemeIndicator 상세 구조
```
ThemeIndicator Container
├─ [메인 디스플레이 영역]
│ │
│ ├─ 비디오 지원 여부 감지
│ │ ├─ YES → [비디오 플레이 버튼]
│ │ │ (아래 이미지들이 [1]부터 시작)
│ │ │
│ │ └─ NO → [이미지 [0]부터 표시]
│ │
│ └─ 선택된 이미지/비디오
│ └─ imageSelectedIndex 기반 렌더링
├─ [썸네일 스크롤 영역]
│ │
│ └─ TVirtualGridList
│ ├─ 항목 높이: 152px (scaleH 적용)
│ ├─ 아이템:
│ │ ├─ [0] - 비디오 (있을 경우)
│ │ ├─ [1] - 이미지 1
│ │ ├─ [2] - 이미지 2
│ │ └─ [n] - 이미지 n
│ │
│ ├─ 상하 화살표 네비게이션
│ └─ 현재 선택 (selectedIndex) 강조
└─ [화살표 버튼] (선택사항)
├─ UP/LEFT → selectedIndex - 1
└─ DOWN/RIGHT → selectedIndex + 1
```
---
## 📐 레이아웃 치수
```
┌────────────────────────────────────────────────┐
│ DetailPanel 레이아웃 │
├────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ │ │ │ │
│ │ ThemeIndicator │ │ IndicatorOptions │ │
│ │ 834 × 930px │ │ 정보 + 버튼 │ │
│ │ │ │ │ │
│ │ ┌─────────────┐ │ ├──────────────────┤ │
│ │ │메인 이미지 │ │ │ 로고 + ID │ │
│ │ │ 600×600px │ │ │ 상품명 │ │
│ │ │ │ │ │ 평점 + 태그 │ │
│ │ └─────────────┘ │ │ │ │
│ │ │ │ [설명][교환] │ │
│ │ ┌─────────────┐ │ │ [SMS] │ │
│ │ │ 썸네일 │ │ │ │ │
│ │ │ 150×150px │ │ │ QR코드 160×160 │ │
│ │ │ (반복) │ │ │ │ │
│ │ └─────────────┘ │ └──────────────────┘ │
│ │ │ │
│ └─────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ ShowSingleOption / ShowUnableOption │ │
│ │ (ProductOption으로 감싸짐) │ │
│ │ │ │
│ │ ProductOption (헤더) │ │
│ │ ├─ 로고 아이콘 │ │
│ │ ├─ 상품명 │ │
│ │ ├─ 평점 + 태그 │ │
│ │ └───────────────────────────────── │ │
│ │ │ │
│ │ SingleOption / UnableOption (본체) │ │
│ │ ├─ [옵션 선택 / 구매불가 메시지] │ │
│ │ ├─ 수량 / SMS 공유 │ │
│ │ ├─ 가격 │ │
│ │ └─ [구매 / SEE MORE] 버튼 │ │
│ │ │ │
│ └──────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────┘
```
---
## 🔄 데이터 흐름 (Theme Product)
```
Redux State
├─ state.home.themeCurationDetailInfoData
│ └─ Array of Products
│ ├─ [0] { prdtId, prdtNm, imgUrls600[], priceInfo, ... }
│ ├─ [1] { ... }
│ └─ [n] { ... }
├─ state.home.productData
│ └─ { themeInfo[0] { curationId, curationNm, ... } }
└─ selectedIndex (상태 변수)
└─ 0, 1, 2, ... n
└─ productInfo[selectedIndex] ← 현재 상품
```
**선택된 상품 데이터 조립:**
```javascript
showProductInfo = {
...productInfo[selectedIndex], // 모든 상품 정보
curationId: themeInfo.curationId, // 테마 ID 추가
curationNm: themeInfo.curationNm, // 테마 이름 추가
expsOrd: `${selectedIndex + 1}`, // 순번 (1부터)
}
```
---
## 📋 priceInfo 구조
```javascript
// priceInfo = "299|199|Y|discount10|10%"
┌──────────────────────────────────────────────────────┐
원가 할인가 보상적용 할인코드 할인율
$299 $199 Y discount10 10%
└──────────────────────────────────────────────────────┘
[0] [1] [2] [3] [4]
// 파싱:
split("|")[0] befPrice (원가)
split("|")[1] lastPrice (할인가)
split("|")[2] rewdAplyFlag (보상적용여부)
split("|")[3] 할인코드 (일반적으로 로그에는 미사용)
split("|")[4] discountRate (할인율%)
```
---
## 🎬 비디오/이미지 선택 로직
### Scenario A: 비디오 O + 이미지 3개
```
서버 응답:
imgUrls600 = [
"play-button.png", // [0] 비디오 플레이 아이콘
"image1.png", // [1] 첫 번째 이미지
"image2.png", // [2] 두 번째 이미지
"image3.png" // [3] 세 번째 이미지
]
UI 표시:
- imageSelectedIndex = 0 → [비디오 플레이] 표시 + 자동 재생
- imageSelectedIndex = 1 → imgUrls600[0] = image1 표시
- imageSelectedIndex = 2 → imgUrls600[1] = image2 표시
- imageSelectedIndex = 3 → imgUrls600[2] = image3 표시
```
### Scenario B: 비디오 X + 이미지 3개
```
서버 응답:
imgUrls600 = [
"image1.png",
"image2.png",
"image3.png"
]
UI 표시:
- imageSelectedIndex = 0 → imgUrls600[0] = image1 표시
- imageSelectedIndex = 1 → imgUrls600[1] = image2 표시
- imageSelectedIndex = 2 → imgUrls600[2] = image3 표시
```
---
## 🎛️ 조건부 렌더링 조건
```
결제 가능 상품 (ShowSingleOption)
├─ pmtSuptYn === "Y"
└─ webOSVersion >= "6.0"
└─ YES → 구매 옵션 + 수량 + 버튼
└─ NO → 다음 조건 확인
결제 불가능 상품 (ShowUnableOption)
├─ pmtSuptYn === "N" OR
└─ webOSVersion < "6.0"
└─ YES → "구매불가" 메시지 + SMS 공유
```
---
## 🔔 이벤트 흐름
```
사용자 입력
├─ 화살표 키 (▲▼)
│ └─ setSelectedIndex(newIndex) 호출
│ └─ showProductInfo 리메모이제이션
│ └─ useEffect 발동
│ ├─ 이미지 변경 (imageSelectedIndex 업데이트)
│ ├─ 가격 업데이트
│ ├─ 옵션 업데이트
│ └─ 로그 전송 (1초 후)
├─ [설명] 버튼
│ └─ descriptionClick(label, description)
│ ├─ setTabLabel([label])
│ ├─ setDescription(description)
│ ├─ dispatch(setShowPopup(descriptionPopup))
│ ├─ handleIndicatorOptions() 호출
│ └─ 로그 전송
├─ [SMS] 버튼
│ └─ handleSMSClick()
│ ├─ dispatch(setShowPopup(smsPopup))
│ ├─ handleMobileSendPopupOpen() 호출
│ └─ 로그 전송
└─ 이미지 클릭 (썸네일)
└─ setImageSelectedIndex(index)
└─ 해당 이미지 확대 표시
```
---
## 📍 ProductOption 래퍼 역할
```
ProductOption (공통 헤더)
├─ contentHeader
│ │
│ ├─ topLayer
│ │ ├─ 파트너 로고 (CustomImage)
│ │ └─ 상품 ID (expsPrdtNo)
│ │
│ ├─ title
│ │ └─ 상품명 (prdtNm)
│ │ (HTML 태그 제거 후 표시)
│ │
│ └─ bottomLayer
│ ├─ StarRating (revwGrd)
│ └─ ProductTag (상품 태그)
└─ children
└─ SingleOption 또는 UnableOption
(본체 컴포넌트)
```
---
## 🏪 SingleOption vs UnableOption
### SingleOption (결제 가능)
```
┌─────────────────────────────────┐
│ ProductOption (공통 헤더) │
├─────────────────────────────────┤
│ │
│ ┌─ 옵션 선택 영역 │
│ │ ├─ 이용일자 선택 │
│ │ ├─ 좌석/등급 선택 │
│ │ └─ 기타 옵션 │
│ │ │
│ ├─ 수량 선택 │
│ │ ├─ [] 1 [+] │
│ │ └─ 수량: 1 │
│ │ │
│ ├─ 가격 영역 │
│ │ ├─ 원가: $299 │
│ │ └─ 할인가: $199 │
│ │ │
│ └─ [구매] 버튼 │
│ └─ spotlight: "buy_Btn" │
│ │
└─────────────────────────────────┘
```
### UnableOption (결제 불가)
```
┌─────────────────────────────────┐
│ ProductOption (공통 헤더) │
├─────────────────────────────────┤
│ │
│ ┌─ 상태 메시지 │
│ │ └─ "결제 불가능합니다" │
│ │ (또는 버전 미지원) │
│ │ │
│ ├─ 대체 옵션 │
│ │ ├─ 가격 표시 │
│ │ └─ SMS 공유 옵션 │
│ │ │
│ └─ [SHOP BY MOBILE] 버튼 │
│ └─ SMS로 정보 전송 │
│ smsTpCd: "APP00204" │
│ │
└─────────────────────────────────┘
```
---
## 📊 로깅 데이터 매핑
### 상품 상세 로그 (productDetailImage)
```javascript
{
expsOrd: "1", // 상품 순번 (1부터)
prdtId: "PROD001",
prdtNm: "Opera Show",
catCd: "CATE001",
catNm: "Performance",
befPrice: "299", // 원가
lastPrice: "199", // 할인가
rewdAplyFlag: "Y", // 보상 적용
revwGrd: 4.5, // 평점
tsvFlag: "Y", // 오늘의 특가
logTpNo: 2301, // PRODUCT.PRODUCT_DETAIL_IMAGE
}
```
### SMS 전송 로그 (shopByMobile)
```javascript
{
shopTpNm: "product", // 상품 타입
shopByMobileFlag: "Y", // SMS 전송 플래그
prdtId: "PROD001",
prdtNm: "Opera Show",
showId: "SHOW001", // 쇼 ID (테마 전용)
showNm: "Opera Night", // 쇼 이름 (테마 전용)
patncNm: "Broadway Partners",
price: "199",
logTpNo: 1401, // SHOP_BY_MOBILE.SHOP_BY_MOBILE
}
```
---
## 🔗 상태 관리 체크리스트
- ✅ 상품 목록 로드 (getThemeCurationDetailInfo)
- ✅ 선택 인덱스 관리 (selectedIndex)
- ✅ 이미지 선택 관리 (imageSelectedIndex)
- ✅ 비디오 자동 재생 (autoPlaying)
- ✅ 매진 상태 확인 (soldoutFlag)
- ✅ 결제 가능 여부 (pmtSuptYn + webOSVersion)
- ✅ 설명 팝업 상태 (popupVisible, activePopup)
- ✅ 로그 전송 (1초 딜레이)
- ✅ Spotlight 포커스 관리
---
## 📝 필드 매핑 테이블
| UI 요소 | 데이터 경로 | 타입 | 설명 |
|---------|-----------|------|------|
| 메인 이미지 | `imgUrls600[imageSelectedIndex]` | URL | 선택된 이미지 표시 |
| 썸네일 | `imgUrls600[]` | 배열 | 스크롤 갤러리 |
| 상품명 | `prdtNm` | 문자열 | HTML 태그 제거 후 표시 |
| 파트너 로고 | `patncLogoPath` | URL | 폴백: 기본 로고 |
| 평점 | `revwGrd` | 숫자 | 0~5 |
| 상품 ID | `expsPrdtNo` | 문자열 | 선택사항 |
| 원가 | `priceInfo.split(\"|\"[0]` | 숫자 | 할인 전 가격 |
| 할인가 | `priceInfo.split(\"|\"[1]` | 숫자 | 최종 가격 |
| 할인율 | `priceInfo.split(\"|\"[4]` | 문자열 | "10%" 형식 |
| 매진 여부 | `soldoutFlag` | "Y"/"N" | UI 배지 표시 |
| 결제 가능 | `pmtSuptYn` | "Y"/"N" | 조건부 렌더링 |
| 비디오 URL | `prdtMediaUrl` | URL | 선택사항 |
| 비디오 자막 | `prdtMediaSubtitlUrl` | URL | 선택사항 |
| QR 코드 | `qrcodeUrl` | 데이터 | 상품 공유용 |
| 쇼 ID | `showId` | 문자열 | 테마 상품 고유 |
| 카테고리 | `catCd` / `catNm` | 문자열 | 분류 정보 |
---
## 🎓 주요 특징
1. **멀티 이미지 갤러리**: 비디오 + 이미지 혼합 지원
2. **자동 비디오 재생**: 조건 만족 시 자동으로 재생 (launchedFromPlayer 제외)
3. **스마트 인덱싱**: 비디오 유무에 따라 이미지 인덱스 자동 조정
4. **조건부 구매 UI**: 결제 여부 + OS 버전에 따른 분기
5. **상세 로깅**: 노출, 선택, 클릭 모두 추적 (매장 분석용)
6. **SMS 공유**: 구매불가 상품도 정보 공유 가능
7. **Spotlight 지원**: 전자제품 원격 제어 네비게이션 완벽 지원