# DetailPanel 컴포넌트의 Hotels/여행상품(Theme) UI 처리 분석 보고서 ## 📋 개요 DetailPanel 컴포넌트는 **3가지 상품 타입**을 지원합니다: 1. **Single Product** (결제가능 상품) 2. **Group Product** (그룹상품) 3. **Travel/Theme Product** (여행상품/테마) - `type === "theme"` : ShowProduct (테마상품) - `type === "hotel"` : HotelProduct (호텔상품) --- ## 🏗️ 아키텍처 흐름 ### 1. 데이터 로딩 흐름 (DetailPanel.backup.jsx) ```javascript // panelInfo.type에 따른 데이터 로딩 useEffect(() => { if (panelInfo?.type === "hotel") { // 호텔 상세정보 요청 dispatch(getThemeHotelDetailInfo({ patnrId: panelInfo?.patnrId, curationId: panelInfo?.curationId, })); } if (panelInfo?.type === "theme") { // 테마 상세정보 요청 dispatch(getThemeCurationDetailInfo({ patnrId: panelInfo?.patnrId, curationId: panelInfo?.curationId, bgImgNo: panelInfo?.bgImgNo, })); } }, [panelInfo.type, panelInfo.curationId, ...]); ``` **Redux State 구조:** ```javascript // homeReducer.js const initialState = { themeCurationDetailInfoData: [], // 테마 상품 데이터 themeCurationHotelDetailData: [], // 호텔 상세 데이터 hotelData: {}, // 호텔 통합 정보 productData: {}, // 테마 정보 }; ``` --- ## 🎨 UI 렌더링 로직 ### 2. ThemeProduct 컴포넌트 (라우팅) ```jsx // ThemeProduct.jsx - 타입에 따른 조건부 렌더링 export default function ThemeProduct({ themeType, // "theme" 또는 "hotel" selectedIndex, setSelectedIndex, ... }) { return (
{themeType === "theme" && ( )} {themeType === "hotel" && ( )}
); } ``` --- ## 🏨 호텔 상품 UI 처리 (HotelProduct.jsx) ### 3. HotelProduct 컴포넌트 구조 #### A. 이미지 갤러리 영역 ```jsx ``` - 호텔 이미지들을 인디케이터로 표시 - 선택된 인덱스에 따라 이미지 변경 #### B. 주소/위치 정보 ```jsx ``` #### C. 호텔 정보 카드 영역 ```jsx
{/* 1. 상단 레이어: 로고 + 별점 + 등급 */}
{label}
{/* 2. 제목: [호텔타입] 호텔명 */}
[{hotelInfos[selectedIndex]?.hotelDetailInfo.hotelType}]  {hotelInfos[selectedIndex]?.hotelNm}
{/* 3. 편의시설 그리드 (최대 10개) */}
{amenitiesInfos && amenitiesInfos.map((item) => (

{item.lgAmntNm}

))}
{/* 4. 하단 레이어: 예약 정보 + 가격 + QR코드 */}
{nights}Nights {adultsCount}Adults
{hotelInfos[selectedIndex]?.hotelDetailInfo.roomType}
From

{hotelInfos[selectedIndex]?.hotelDetailInfo.currencySign} {hotelInfos[selectedIndex]?.hotelDetailInfo.price}

{/* 5. CTA 버튼: "SEE MORE" */} {$L("SEE MORE")}
``` --- ## 🎯 데이터 매핑 상세 설명 ### 4. 호텔 데이터 구조 (Redux에서) ```javascript hotelInfos: [ { hotelId: "string", hotelNm: "Hotel Name", hotelImgUrl: "url", imgUrls600: ["url1", "url2", ...], qrcodeUrl: "qr-code-data", hotelDetailInfo: { hotelAddr: "Address", hotelType: "Luxury/Budget/etc", price: "299", currencySign: "$", revwGrd: 4.5, // 평점 nights: 2, adultsCount: 2, roomType: "Double Room", amenities: ["amntId1", "amntId2", ...], imgUrls: ["url1", "url2", ...] } }, // ... 여러 호텔 ] hotelData: { hotelInfo: { curationId: "string", curationNm: "Theme Name", patncNm: "Partner Name", patnrId: "string", patncLogoPath: "url" }, amenities: [ { amntId: "1", lgAmntNm: "Free WiFi", lgAmntImgUrl: "icon-url" }, // ... 편의시설 목록 ] } ``` --- ## ⭐ 별점 등급 매핑 ```javascript useEffect(() => { let label = ""; let rating = hotelInfos[selectedIndex]?.hotelDetailInfo.revwGrd; if (rating !== undefined) { if (rating <= 2.4) label = $L("Fair"); else if (rating >= 2.5 && rating <= 3.4) label = $L("Good"); else if (rating >= 3.5 && rating <= 4.4) label = $L("Very Good"); else if (rating >= 4.5 && rating <= 5) label = $L("Excellent"); } setLabel(label); }, [selectedIndex, hotelInfos]); ``` --- ## 🛏️ 편의시설 처리 로직 ```javascript const getAmenitiesInfo = () => { const matchedData = new Set(); const amenitiesMap = new Map(); // 1. 전체 편의시설을 맵으로 변환 hotelData.amenities.forEach((item) => { amenitiesMap.set(item.amntId, item); }); // 2. 현재 호텔에 포함된 편의시설 필터링 (최대 10개) hotelInfos[selectedIndex]?.hotelDetailInfo.amenities.forEach((amntId) => { if (amenitiesMap.has(amntId) && matchedData.size < 10) { matchedData.add(amenitiesMap.get(amntId)); } }); // 3. 같은 카테고리의 중복 제거 let amenitiesArr = Array.from(matchedData); amenitiesArr = amenitiesArr.filter((item, index, self) => { return index === self.findIndex((t) => t.lgAmntNm === item.lgAmntNm); }); setAmenitiesInfos(amenitiesArr); }; ``` --- ## 📊 스타일링 (HotelProduct.module.less) ### 레이아웃 구성: | 영역 | 크기 | 설명 | |------|------|------| | **themeContainer** | 774px × 930px | 이미지 갤러리 컨테이너 | | **optionContainer** | 1026px × 990px | 호텔 정보 카드 | | **topLayer** | 100% × auto | 로고 + 별점 섹션 | | **title** | 100% × 84px | 호텔명 (2줄 말줄임) | | **amenitiesCotainer** | 846px × 344px | 편의시설 그리드 | | **amenitiesBox** | 138px × 138px | 개별 편의시설 | | **bottomLayer** | auto × auto | 예약정보 + 가격 + QR코드 | | **price** | auto × auto | 가격 표시 (큰 크기, 분홍색) | | **qrcodeContainer** | 192px × 192px | QR코드 영역 | --- ## 🔄 선택 인덱스 관리 ```javascript useEffect(() => { // URL 파라미터로 특정 호텔이 지정된 경우 if (hotelInfos && hotelInfos.length > 0 && panelInfo?.themeHotelId) { for (let i = 0; i < hotelInfos.length; i++) { if (hotelInfos[i].hotelId === panelInfo?.themeHotelId) { setSelectedIndex(i); } } } }, [hotelInfos, panelInfo?.themeHotelId]); ``` --- ## 📱 이미지 길이 업데이트 ```javascript useEffect(() => { if ((hotelInfos && hotelInfos.length > 0) && selectedIndex !== undefined) { if (panelInfo?.type === "hotel") { const imgUrls600 = hotelInfos[selectedIndex]?.imgUrls600 || []; dispatch(getProductImageLength({ imageLength: imgUrls600.length })); } } }, [dispatch, selectedIndex, hotelInfos]); ``` --- ## 🔔 로깅 및 추적 ### A. 상세정보 조회 로그 ```javascript const params = { befPrice: selectedHotelInfo?.hotelInfo?.hotelDetailInfo?.price, curationId: selectedHotelInfo?.curationId, curationNm: selectedHotelInfo?.curationNm, expsOrd: selectedIndex + 1, logTpNo: LOG_TP_NO.PRODUCT.PRODUCT_DETAIL_IMAGE, prdtId: selectedHotelInfo?.hotelInfo?.hotelId, prdtNm: selectedHotelInfo?.hotelInfo?.hotelNm, revwGrd: selectedHotelInfo?.hotelInfo?.hotelDetailInfo?.revwGrd, // ... 더 많은 필드 }; dispatch(sendLogProductDetail(params)); ``` ### B. SMS 보내기 버튼 로그 ```javascript const params = { patncNm: selectedHotelInfo.patncNm, prdtId: selectedHotelInfo.hotelInfo?.hotelDetailInfo?.hotelId, prdtNm: selectedHotelInfo.hotelInfo?.hotelDetailInfo?.hotelNm, shopTpNm: "hotel", // 호텔 타입 마킹 shopByMobileFlag: "Y", price: selectedHotelInfo.hotelInfo?.hotelDetailInfo?.price, // ... }; dispatch(sendLogShopByMobile(params)); ``` --- ## 💡 주요 특징 | 기능 | 구현 | |------|------| | **동적 인덱스 선택** | 화살표 키로 호텔 선택 변경 가능 | | **이미지 갤러리** | ThemeIndicator 컴포넌트로 여러 이미지 표시 | | **평점 표시** | StarRating 컴포넌트로 시각화 | | **편의시설 필터링** | 최대 10개, 중복 제거 | | **가격 표시** | 통화 기호 + 숫자 (분홍색 강조) | | **예약 정보** | 숙박일 수 + 성인 수 자동 포맷팅 | | **QR코드** | 호텔 상세정보 링크 제공 | | **SMS 기능** | "SEE MORE" 버튼으로 SMS 팝업 오픈 | | **Spotlight** | 키보드 네비게이션 지원 | --- ## 🔗 관련 컴포넌트 - **ThemeProduct.jsx** : 라우팅 (theme vs hotel) - **HotelProduct.jsx** : 호텔 UI 렌더링 - **ShowProduct.jsx** : 테마 상품 UI 렌더링 - **ThemeIndicator** : 이미지 갤러리 - **IndicatorOptions** : 주소/위치 표시 - **StarRating** : 별점 표시 - **TQRCode** : QR코드 생성 - **TButton** : 호텔/테마 액션 버튼 --- ## 📝 요약 DetailPanel의 호텔 UI 처리는 **Redux 상태 기반의 선언적 렌더링**으로, 선택된 인덱스(`selectedIndex`)에 따라 해당 호텔의 이미지, 정보, 가격, 편의시설을 동적으로 표시합니다. 모든 상호작용(이미지 변경, 버튼 클릭)은 상세한 로깅을 통해 추적되며, SMS 연동으로 호텔 정보를 공유할 수 있습니다.