[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:
437
com.twin.app.shoptime/THEME_PRODUCT_VISUAL_GUIDE.md
Normal file
437
com.twin.app.shoptime/THEME_PRODUCT_VISUAL_GUIDE.md
Normal 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 지원**: 전자제품 원격 제어 네비게이션 완벽 지원
|
||||
Reference in New Issue
Block a user