Files
shoptime/com.twin.app.shoptime/THEME_PRODUCT_VISUAL_GUIDE.md
optrader ec5829eebe [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 서비스 레이어 개선
  • 개발 문서 및 가이드 개선
2025-11-22 21:21:32 +09:00

16 KiB
Raw Permalink Blame History

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] ← 현재 상품

선택된 상품 데이터 조립:

showProductInfo = {
  ...productInfo[selectedIndex],        // 모든 상품 정보
  curationId: themeInfo.curationId,     // 테마 ID 추가
  curationNm: themeInfo.curationNm,     // 테마 이름 추가
  expsOrd: `${selectedIndex + 1}`,      // 순번 (1부터)
}

📋 priceInfo 구조

// 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)

{
  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)

{
  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]` 문자열
매진 여부 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 지원: 전자제품 원격 제어 네비게이션 완벽 지원