# 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 지원**: 전자제품 원격 제어 네비게이션 완벽 지원