# DetailPanel 호텔/여행상품 UI 처리 - 시각적 가이드 ## 🔄 데이터 흐름 다이어그램 ``` ┌─────────────────────────────────────────────────────────────────┐ │ DetailPanel (Main) │ └────────────────┬────────────────────────────────────────────────┘ │ ┌────────┴─────────┐ │ panelInfo.type │ └────────┬─────────┘ │ ┌───────────┼───────────┐ │ │ │ "theme" "hotel" "product" │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌────────┐ ┌─────────┐ │ShowPrdt │ │HotelPdt│ │SinglePdt│ └────┬────┘ └────┬───┘ └────┬────┘ │ │ │ ▼ ▼ ▼ Redux Actions Redux Actions Redux Actions ┌─────────────────────────────────────────┐ │ getThemeCurationDetailInfo() [THEME] │ │ getThemeHotelDetailInfo() [HOTEL] │ │ getMainCategoryDetail() [PRODUCT] │ └──────────────┬──────────────────────────┘ │ ▼ ┌────────────────────┐ │ Redux Reducer │ │ │ │ - productData │ │ - hotelData │ │ - hotelInfos[] │ │ - themeInfos[] │ └────────────────────┘ │ ▼ ┌────────────────────┐ │ UI Components │ │ │ │ - ThemeIndicator │ │ - HotelProduct │ │ - ShowProduct │ └────────────────────┘ ``` --- ## 🏨 HotelProduct UI 구조 ``` ┌──────────────────────────────────────────────────────────────────┐ │ HotelProduct Component │ └──────────────────┬───────────────────────────────────────────────┘ │ ┌──────────┴──────────┐ │ │ ▼ ▼ ┌─────────────┐ ┌──────────────────┐ │ThemeIndicator │ │OptionContainer │ ├─────────────┤ ├──────────────────┤ │ 이미지 갤러리 │ │ 호텔 정보 카드 │ │ 774×930px │ │ 1026×990px │ │ │ │ │ │ ┌─────────┐ │ │ ┌────────────┐ │ │ │ 이미지1 │ │ │ │ 로고 + 별점 │ │ │ │ │ │ │ ├────────────┤ │ │ │ ▲ ▼ │ │ │ │ 호텔명 │ │ │ │ [•••] │ │ │ │ [타입]호텔 │ │ │ │ │ │ │ ├────────────┤ │ │ │ ◀ ───► │ │ │ │ 편의시설 │ │ │ │ 이미지2 │ │ │ │ [아이콘] │ │ │ │ 이미지3 │ │ │ │ [아이콘] │ │ │ └─────────┘ │ │ ├────────────┤ │ │ │ │ │ 예약정보 │ │ └─────────────┘ │ │ N Nights │ │ │ │ M Adults │ │ │ │ Room Type │ │ │ ├────────────┤ │ │ │ From │ │ │ │ $299 ◀─────┼──┼→ QR Code │ │ (분홍색) │ │ 160×160px │ ├────────────┤ │ │ │[SEE MORE]btn│ │ │ └────────────┘ │ └──────────────────┘ ``` --- ## 📊 컴포넌트 계층도 (DetailPanel) ``` DetailPanel ├── TPanel │ ├── THeader (제목: 호텔명/테마명) │ └── TBody (스크롤 가능 영역) │ └── ThemeProduct (조건부 렌더링) │ ├── ShowProduct │ │ ├── Container (Spotlight) │ │ │ ├── ThemeIndicator │ │ │ └── IndicatorOptions │ │ └── optionContainer │ │ ├── ShowSingleOption │ │ └── ShowUnableOption │ │ │ └── HotelProduct ◀── 호텔 전용 │ ├── Container (Spotlight) │ │ ├── ThemeIndicator │ │ └── IndicatorOptions │ └── optionContainer │ ├── topLayer │ │ ├── img (로고) │ │ └── rating (별점) │ ├── title │ ├── amenitiesCotainer │ │ └── amenitiesBox[] (반복) │ ├── bottomLayer │ │ ├── 예약정보 │ │ ├── 가격 │ │ └── QR코드 │ └── TButton (SEE MORE) │ ├── YouMayLike (조건부) └── MobileSendPopUp (조건부) ``` --- ## 🎯 선택 인덱스 상태 관리 ```javascript HotelProduct │ ├─ selectedIndex (props) │ └─ 현재 선택된 호텔의 배열 인덱스 │ ├─ hotelInfos[selectedIndex] → 호텔 데이터 │ ├─ hotelInfos[selectedIndex].hotelNm → 호텔명 │ ├─ hotelInfos[selectedIndex].hotelDetailInfo.price → 가격 │ └─ hotelInfos[selectedIndex].hotelDetailInfo.amenities → 편의시설 ID 배열 │ └─ setSelectedIndex (callback) └─ ThemeIndicator 화살표 클릭 시 호출 ├─ UP / LEFT → selectedIndex - 1 └─ DOWN / RIGHT → selectedIndex + 1 ``` --- ## 💾 Redux 상태 구조 (호텔 데이터) ```javascript state.home = { // 호텔 목록 (배열) themeCurationHotelDetailData: [ { hotelId: "HOTEL001", hotelNm: "Luxury Hotel", hotelImgUrl: "http://...", imgUrls600: ["url1", "url2", "url3"], qrcodeUrl: "qrcode-data", hotelDetailInfo: { hotelAddr: "123 Main St, NYC", hotelType: "5-star", price: "299.99", currencySign: "$", revwGrd: 4.8, // 평점 nights: 2, // 숙박일 수 adultsCount: 2, // 성인 수 roomType: "Deluxe Double Room", amenities: ["AMN001", "AMN002", "AMN005"], // 편의시설 ID imgUrls: ["url1", "url2", "url3"] } }, { /* 다음 호텔 */ } ], // 호텔 통합 정보 hotelData: { hotelInfo: { curationId: "CURATION001", curationNm: "Dubai Vacation", patncNm: "Travel Partner", patnrId: "PARTNER001", patncLogoPath: "http://logo-url" }, amenities: [ { amntId: "AMN001", lgAmntNm: "Free WiFi", lgAmntImgUrl: "http://icon-wifi.png" }, { amntId: "AMN002", lgAmntNm: "Swimming Pool", lgAmntImgUrl: "http://icon-pool.png" }, { amntId: "AMN005", lgAmntNm: "Spa", lgAmntImgUrl: "http://icon-spa.png" } // ... 더 많은 편의시설 ] } } ``` --- ## 🎨 스타일 적용 흐름 ``` HotelProduct.module.less │ ├─ .themeContainer │ └─ ThemeIndicator 감싸기 │ ├─ width: 774px │ └─ height: 930px │ ├─ .optionContainer │ └─ 호텔 정보 카드 │ ├─ width: 1026px │ ├─ height: 990px │ ├─ padding: 30px 120px 60px 60px │ └─ background: #f9f9f9 │ ├─ .topLayer │ └─ display: flex │ ├─ justify-content: space-between │ └─ img (로고 42×42px) │ rating (별점 + 텍스트) │ ├─ .title │ ├─ font-size: 36px │ ├─ font-weight: bold │ ├─ overflow: ellipsis (2줄 말줄임) │ └─ color: #333 │ ├─ .amenitiesCotainer │ ├─ width: 846px │ ├─ height: 344px │ ├─ display: flex │ ├─ flex-wrap: wrap │ ├─ background: #f2f2f2 │ └─ border: 1px solid #dadada │ │ │ └─ .amenitiesBox (반복) │ ├─ width: 138px │ ├─ height: 138px │ ├─ background: white │ ├─ border-radius: 6px │ ├─ display: flex (column) │ └─ img: 36×32px, p: 18px font │ ├─ .bottomLayer │ ├─ display: flex │ ├─ justify-content: space-between │ │ │ ├─ 좌측 (예약정보 + 가격) │ │ ├─ .today: 24px, bold, #222 │ │ ├─ .roomType: 24px, #808080 │ │ └─ .price │ │ ├─ title: 36px, #222 │ │ └─ value: 44px, bold, #c70850 (분홍색) │ │ │ └─ 우측 (QR코드) │ └─ .qrcodeContainer │ ├─ width: 192px │ ├─ height: 192px │ ├─ box-shadow: 0px 3px 6px rgba(0,0,0,0.1) │ └─ border: 1px solid #dadada │ └─ .tbutton ├─ size: extra ├─ background: 테마 색상 └─ margin-top: auto ``` --- ## 🔄 이벤트 흐름 ``` 사용자 입력 │ ├─ 화살표 키 (▲▼◄►) │ └─ Spotlight 포커스 변경 │ └─ setSelectedIndex() 호출 │ └─ hotelInfos[newIndex] 데이터 로드 │ └─ UI 업데이트 (이미지, 가격, 편의시설) │ └─ [SEE MORE] 버튼 클릭 └─ handleSMSClick() ├─ 로깅 (sendLogTotalRecommend, sendLogShopByMobile) └─ MobileSendPopUp 오픈 └─ SMS로 호텔 정보 전송 ``` --- ## 📱 반응형 고려사항 ### 이미지 크기 - **thumbnailUrls**: `imgUrls600[]` (600px 기준) - **갤러리**: 774×930px 고정 - **카드**: 1026×990px 고정 - **QR코드**: 160×160px 표시용 ### 텍스트 보호 ```javascript // 호텔명 2줄 말줄임 .title { .elip(@clamp:2); // max 2 lines } // 방 타입 3줄 말줄임 .roomType { .elip(@clamp:3); // max 3 lines } ``` --- ## ⚠️ 주의사항 1. **QR 코드 이슈**: 호텔 QR 코드가 서버에서 다르게 내려와 에러 발생 ```javascript // TODO: 해결되면 주석제거 ``` 2. **편의시설 필터링**: - 최대 10개로 제한 - 같은 카테고리명 중복 제거 3. **평점 없음 처리**: ```javascript if (rating !== undefined) { // 평점 등급 처리 } ``` 4. **이미지 없음 처리**: ```javascript const imgUrls600 = hotelInfos[selectedIndex]?.imgUrls600 || []; ``` --- ## 📊 상태 관리 체크리스트 - ✅ 호텔 데이터 로드 (useEffect with panelInfo.type) - ✅ 선택 인덱스 초기화 (panelInfo?.themeHotelId 기반) - ✅ 이미지 길이 업데이트 (이미지 스와이프 관련) - ✅ 편의시설 정렬 (최대 10개, 중복 제거) - ✅ 평점 등급 계산 - ✅ SMS 로깅 (shopByMobileFlag: "Y", shopTpNm: "hotel") - ✅ 상세 정보 로깅 (logTpNo: PRODUCT.PRODUCT_DETAIL_IMAGE) - ✅ cleanup (clearThemeDetail, clearProductDetail) --- ## 🔗 관련 액션 (Redux Actions) ```javascript // src/actions/homeActions.js // 호텔 데이터 조회 getThemeHotelDetailInfo({ patnrId, curationId }) → dispatch(GET_THEME_HOTEL_DETAIL_INFO, hotelData) // 테마 데이터 조회 getThemeCurationDetailInfo({ patnrId, curationId, bgImgNo }) → dispatch(GET_THEME_CURATION_DETAIL_INFO, themeData) // 상세 정보 초기화 clearThemeDetail() → 호텔/테마 데이터 리셋 ``` --- ## 📝 필드 매핑 요약 | UI 요소 | 데이터 경로 | 설명 | |---------|-----------|------| | 로고 | `hotelData.hotelInfo.patncLogoPath` | 파트너 로고 | | 별점 | `hotelInfos[i].hotelDetailInfo.revwGrd` | 0~5 평점 | | 별점 등급 | 계산됨 (2.5~3.4=Good 등) | 동적 매핑 | | 호텔명 | `hotelInfos[i].hotelNm` | 호텔 이름 | | 호텔 타입 | `hotelInfos[i].hotelDetailInfo.hotelType` | 5-Star, Luxury 등 | | 이미지 | `hotelInfos[i].hotelDetailInfo.imgUrls[]` | 갤러리 이미지 | | 주소 | `hotelInfos[i].hotelDetailInfo.hotelAddr` | 지도 위치 | | 편의시설 | `hotelInfos[i].hotelDetailInfo.amenities[]` | ID 배열 → 매핑 | | 숙박일 | `hotelInfos[i].hotelDetailInfo.nights` | N Nights | | 성인 수 | `hotelInfos[i].hotelDetailInfo.adultsCount` | M Adults | | 방 타입 | `hotelInfos[i].hotelDetailInfo.roomType` | Deluxe Double Room | | 가격 | `hotelInfos[i].hotelDetailInfo.price` | 숫자 (문자열) | | 통화 | `hotelInfos[i].hotelDetailInfo.currencySign` | $ / € / ¥ | | QR 코드 | `hotelInfos[i].qrcodeUrl` | QR 데이터 |