diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx index f741cabf..3ba91ef3 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx @@ -9,30 +9,21 @@ import React, { import classNames from 'classnames'; // import { throttle } from 'lodash'; import { PropTypes } from 'prop-types'; -import { - useDispatch, - useSelector, -} from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import Spotlight from '@enact/spotlight'; /* eslint-disable react/jsx-no-bind */ // src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx -import SpotlightContainerDecorator - from '@enact/spotlight/SpotlightContainerDecorator'; +import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import Spottable from '@enact/spotlight/Spottable'; import couponImg from '../../../../assets/images/icons/coupon.png'; import arrowDownIcon from '../../../../assets/images/icons/ic-arrow-down.svg'; // import Spottable from '@enact/spotlight/Spottable'; //image -import arrowDown - from '../../../../assets/images/icons/ic_arrow_down_3x_new.png'; -import indicatorDefaultImage - from '../../../../assets/images/img-thumb-empty-144@3x.png'; -import { - setHidePopup, - setShowPopup, -} from '../../../actions/commonActions.js'; +import arrowDown from '../../../../assets/images/icons/ic_arrow_down_3x_new.png'; +import indicatorDefaultImage from '../../../../assets/images/img-thumb-empty-144@3x.png'; +import { setHidePopup, setShowPopup } from '../../../actions/commonActions.js'; import { getProductCouponDownload, getProductCouponSearch, @@ -62,8 +53,7 @@ import CustomImage from '../../../components/CustomImage/CustomImage.jsx'; // ProductInfoSection imports import TButton, { TYPES } from '../../../components/TButton/TButton'; import TPopUp from '../../../components/TPopUp/TPopUp.jsx'; -import TVirtualGridList - from '../../../components/TVirtualGridList/TVirtualGridList.jsx'; +import TVirtualGridList from '../../../components/TVirtualGridList/TVirtualGridList.jsx'; import useReviews from '../../../hooks/useReviews/useReviews'; import useScrollTo from '../../../hooks/useScrollTo'; import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig'; @@ -89,10 +79,7 @@ import { tap, when, } from '../../../utils/fp'; -import { - $L, - formatGMTString, -} from '../../../utils/helperMethods'; +import { $L, formatGMTString } from '../../../utils/helperMethods'; import { SpotlightIds } from '../../../utils/SpotlightIds'; import ShowUserReviews from '../../UserReview/ShowUserReviews'; // import CustomScrollbar from '../components/CustomScrollbar/CustomScrollbar'; @@ -103,21 +90,14 @@ import StarRating from '../components/StarRating'; // ProductContentSection imports import TScrollerDetail from '../components/TScroller/TScrollerDetail'; import DetailPanelSkeleton from '../DetailPanelSkeleton/DetailPanelSkeleton'; -import ProductDescription - from '../ProductContentSection/ProductDescription/ProductDescription'; -import ProductDetail - from '../ProductContentSection/ProductDetail/ProductDetail.new'; -import { - ProductVideoV2, -} from '../ProductContentSection/ProductVideo/ProductVideo.v2.jsx'; -import ProductVideo - from '../ProductContentSection/ProductVideo/ProductVideo.v3'; -import SeeMoreProducts - from '../ProductContentSection/SeeMoreProducts/SeeMoreProducts'; +import ProductDescription from '../ProductContentSection/ProductDescription/ProductDescription'; +import ProductDetail from '../ProductContentSection/ProductDetail/ProductDetail.new'; +import { ProductVideoV2 } from '../ProductContentSection/ProductVideo/ProductVideo.v2.jsx'; +import ProductVideo from '../ProductContentSection/ProductVideo/ProductVideo.v3'; +import SeeMoreProducts from '../ProductContentSection/SeeMoreProducts/SeeMoreProducts'; import UserReviews from '../ProductContentSection/UserReviews/UserReviews'; // import ViewAllReviewsButton from '../ProductContentSection/UserReviews/ViewAllReviewsButton'; -import YouMayAlsoLike - from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike'; +import YouMayAlsoLike from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike'; import QRCode from '../ProductInfoSection/QRCode/QRCode'; import ProductOverview from '../ProductOverview/ProductOverview'; // CSS imports @@ -166,7 +146,8 @@ const ShopByMobileContainer = SpotlightContainerDecorator( spotlightDirection: 'horizontal', enterTo: 'last-focused', restrict: 'self-only', - defaultElement: SpotlightIds?.DETAIL_SHOPBYMOBILE || 'detail_shop_by_mobile', + defaultElement: + SpotlightIds?.DETAIL_SHOPBYMOBILE || 'detail_shop_by_mobile', }, 'div' ); @@ -258,17 +239,27 @@ export default function ProductAllSection({ const dispatch = useDispatch(); // Redux 상태 - const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion); + const webOSVersion = useSelector( + (state) => state.common.appStatus.webOSVersion + ); const groupInfos = useSelector((state) => state.product.groupInfo); const nowMenu = useSelector((state) => state.common.menu.nowMenu); // YouMayLike 데이터는 API 응답 시간이 걸리므로 직접 구독 const youmaylikeData = useSelector((state) => state.main.youmaylikeData); //coupon - const { partnerCoupon } = useSelector((state) => state.coupon.productCouponSearchData); - const { userNumber } = useSelector((state) => state.common.appStatus.loginUserData); - const { popupVisible, activePopup } = useSelector((state) => state.common.popup); - const cursorVisible = useSelector((state) => state.common.appStatus.cursorVisible); + const { partnerCoupon } = useSelector( + (state) => state.coupon.productCouponSearchData + ); + const { userNumber } = useSelector( + (state) => state.common.appStatus.loginUserData + ); + const { popupVisible, activePopup } = useSelector( + (state) => state.common.popup + ); + const cursorVisible = useSelector( + (state) => state.common.appStatus.cursorVisible + ); // 🆕 [251210] patnrId=21 카테고리 그룹 데이터 const brandShopByShowCategoryGroups = useSelector( (state) => state.brand.brandShopByShowCategoryGroups @@ -293,12 +284,15 @@ export default function ProductAllSection({ // 출처 정보 통합 (향후 확장성 대비) // YouMayLike 상품이 아닐 경우 fromPanel을 초기화하여 오기 방지 - const fromPanel = useMemo(() => ({ - fromYouMayLike: panelInfo?.fromPanel?.fromYouMayLike || false, - // 향후 다른 출처 플래그들 추가 가능 - // fromRecommendation: panelInfo?.fromPanel?.fromRecommendation || false, - // fromSearch: panelInfo?.fromPanel?.fromSearch || false, - }), [panelInfo?.fromPanel?.fromYouMayLike]); + const fromPanel = useMemo( + () => ({ + fromYouMayLike: panelInfo?.fromPanel?.fromYouMayLike || false, + // 향후 다른 출처 플래그들 추가 가능 + // fromRecommendation: panelInfo?.fromPanel?.fromRecommendation || false, + // fromSearch: panelInfo?.fromPanel?.fromSearch || false, + }), + [panelInfo?.fromPanel?.fromYouMayLike] + ); //구매 하단 토스트 노출 확인을 위한 용도 const [openToast, setOpenToast] = useState(false); @@ -327,21 +321,18 @@ export default function ProductAllSection({ //topbutton const handleTopButtonClick = useCallback(() => { const container = scrollContainerRef?.current; - if (!container) return; - if (typeof container.scrollTo === 'function') { - scrollTop({ y: 0, animate: true }); - setTimeout(()=>{ - if(hasVideo){ - Spotlight.focus("product-video-player"); - } else { - Spotlight.focus("product-detail-container-0"); - } - },100); - } - }, [ - scrollTop, - hasVideo - ]); + if (!container) return; + if (typeof container.scrollTo === 'function') { + scrollTop({ y: 0, animate: true }); + setTimeout(() => { + if (hasVideo) { + Spotlight.focus('product-video-player'); + } else { + Spotlight.focus('product-detail-container-0'); + } + }, 100); + } + }, [scrollTop, hasVideo]); const fetchCouponData = useCallback(() => { dispatch( @@ -403,7 +394,9 @@ export default function ProductAllSection({ const handleCouponTotDownload = useCallback(() => { if (selectedCoupon && userNumber) { - const couponCodesArray = couponCodes.split(',').map((code) => parseInt(code.trim())); + const couponCodesArray = couponCodes + .split(',') + .map((code) => parseInt(code.trim())); setDownloadCouponArr((prevArr) => [...prevArr, ...couponCodesArray]); dispatch( @@ -474,7 +467,9 @@ export default function ProductAllSection({ return; } setDownloadCouponArr((prevArr) => [...prevArr, cpnSno]); - dispatch(getProductCouponDownload({ mbrNo: userNumber, cpnSno: cpnSno })); + dispatch( + getProductCouponDownload({ mbrNo: userNumber, cpnSno: cpnSno }) + ); dispatch(showToast({ message: 'Your coupon download is complete.' })); }; @@ -489,7 +484,13 @@ export default function ProductAllSection({ >
{shptmDcTpCd === 'CPN00401' && ( @@ -505,7 +506,9 @@ export default function ProductAllSection({
- {$L('Purchase over ${cpnAplyMinPurcAmt} (up to ${cpnAplyMaxDcAmt} off)') + {$L( + 'Purchase over ${cpnAplyMinPurcAmt} (up to ${cpnAplyMaxDcAmt} off)' + ) .replace('{cpnAplyMinPurcAmt}', cpnAplyMinPurcAmt) .replace('{cpnAplyMaxDcAmt}', cpnAplyMaxDcAmt)} @@ -518,7 +521,9 @@ export default function ProductAllSection({
0 && downloadCouponArr.includes(cpnSno) && css.disable, + downloadCouponArr.length > 0 && + downloadCouponArr.includes(cpnSno) && + css.disable, duplDwldYn === 'N' && downloadYn === 'Y' && css.disable, !downloadCouponArr.includes(cpnSno) && index === selectedCouponIndex && @@ -527,7 +532,9 @@ export default function ProductAllSection({ )} aria-label="Download Button" > - {downloadCouponArr.length > 0 && downloadCouponArr.includes(cpnSno) && duplDwldYn === 'N' + {downloadCouponArr.length > 0 && + downloadCouponArr.includes(cpnSno) && + duplDwldYn === 'N' ? $L('DOWNLOAD COMPLETED') : $L('DOWNLOAD')}
@@ -566,13 +573,20 @@ export default function ProductAllSection({ const [activeButton, setActiveButton] = useState(null); const productData = useMemo( - () => getProductData(productType, themeProductInfo, themeProducts, selectedIndex, productInfo), + () => + getProductData( + productType, + themeProductInfo, + themeProducts, + selectedIndex, + productInfo + ), [productType, themeProductInfo, themeProducts, selectedIndex, productInfo] ); // 🆕 [251211] patnrId=21인 경우 QR 데이터 확인 useEffect(() => { - if (productData?.patnrId === 21 || productData?.patnrId === "21") { + if (productData?.patnrId === 21 || productData?.patnrId === '21') { console.log('[QR-Data] patnrId=21 QR 데이터 확인:', { patnrId: productData?.patnrId, prdtId: productData?.prdtId, @@ -629,7 +643,9 @@ export default function ProductAllSection({ if (webOSVersion < '6.0') { return ( productData?.pmtSuptYn === 'N' || - (productData?.pmtSuptYn === 'Y' && productData?.grPrdtProcYn === 'N' && panelInfo?.prdtId) + (productData?.pmtSuptYn === 'Y' && + productData?.grPrdtProcYn === 'N' && + panelInfo?.prdtId) ); } @@ -650,7 +666,10 @@ export default function ProductAllSection({ // 여행/테마 상품 - DetailPanel.backup.jsx와 동일한 로직 const isTravelProductVisible = useMemo(() => { - return panelInfo?.curationId && (panelInfo?.type === 'theme' || panelInfo?.type === 'hotel'); + return ( + panelInfo?.curationId && + (panelInfo?.type === 'theme' || panelInfo?.type === 'hotel') + ); }, [panelInfo]); // useReviews Hook 사용 - 모든 리뷰 관련 로직을 담당 @@ -722,13 +741,13 @@ export default function ProductAllSection({ if (productData && Object.keys(productData).length > 0) { // sendLogDetail - 제품 상세 버튼 클릭 로깅 (Source와 동일) const detailLogParams = { - curationId: productData?.curationId ?? "", - curationNm: productData?.curationNm ?? "", - inDt: "", - linkTpCd: panelInfo?.linkTpCd ?? "", + curationId: productData?.curationId ?? '', + curationNm: productData?.curationNm ?? '', + inDt: '', + linkTpCd: panelInfo?.linkTpCd ?? '', logTpNo: LOG_TP_NO.DETAIL.DETAIL_BUTTON_CLICK, - patncNm: productData?.patncNm ?? "", - patnrId: productData?.patnrId ?? "", + patncNm: productData?.patncNm ?? '', + patnrId: productData?.patnrId ?? '', }; dispatch(sendLogDetail(detailLogParams)); @@ -745,14 +764,22 @@ export default function ProductAllSection({ menuType = Config.LOG_MENU.DETAIL_PAGE_PRODUCT_DETAIL; } - dispatch(sendLogTotalRecommend({ - menu: menuType, - buttonTitle: "DESCRIPTION", - contextName: LOG_CONTEXT_NAME.DETAILPAGE, - messageId: LOG_MESSAGE_ID.BUTTONCLICK, - })); + dispatch( + sendLogTotalRecommend({ + menu: menuType, + buttonTitle: 'DESCRIPTION', + contextName: LOG_CONTEXT_NAME.DETAILPAGE, + messageId: LOG_MESSAGE_ID.BUTTONCLICK, + }) + ); } - }, [productData, panelInfo, isBillingProductVisible, isGroupProductVisible, isTravelProductVisible]); + }, [ + productData, + panelInfo, + isBillingProductVisible, + isGroupProductVisible, + isTravelProductVisible, + ]); // sendLogGNB 로깅 - Source의 DetailPanel 컴포넌트들과 동일한 패턴 useEffect(() => { @@ -774,9 +801,11 @@ export default function ProductAllSection({ } // YouMayLike에서 상품 선택 시 메뉴 변경 (Source의 isYouMayLikeOpened와 동일 패턴) - const menu = (fromPanel?.fromYouMayLike !== undefined && fromPanel?.fromYouMayLike === true) - ? `${baseMenu}/${Config.LOG_MENU.DETAIL_PAGE_YOU_MAY_LIKE}` - : baseMenu; + const menu = + fromPanel?.fromYouMayLike !== undefined && + fromPanel?.fromYouMayLike === true + ? `${baseMenu}/${Config.LOG_MENU.DETAIL_PAGE_YOU_MAY_LIKE}` + : baseMenu; const logKey = `${productData?.patnrId || ''}-${productData?.prdtId || ''}`; if (lastGnbLogKeyRef.current === logKey) { @@ -798,15 +827,17 @@ export default function ProductAllSection({ // sendLogGNB 전송 후 플래그 초기화 (1회 사용 후 비활성화) useEffect(() => { if (fromPanel?.fromYouMayLike === true) { - dispatch(updatePanel({ - name: panel_names.DETAIL_PANEL, - panelInfo: { - ...panelInfo, - fromPanel: { - fromYouMayLike: false // 플래그 초기화 - } - } - })); + dispatch( + updatePanel({ + name: panel_names.DETAIL_PANEL, + panelInfo: { + ...panelInfo, + fromPanel: { + fromYouMayLike: false, // 플래그 초기화 + }, + }, + }) + ); } }, [fromPanel?.fromYouMayLike, dispatch, panelInfo]); @@ -819,10 +850,10 @@ export default function ProductAllSection({ const logTpNo = isTravelProductVisible ? Config.LOG_TP_NO.PRODUCT.TRAVEL_DETAIL : isGroupProductVisible - ? Config.LOG_TP_NO.PRODUCT.GROUP_DETAIL - : isBillingProductVisible - ? Config.LOG_TP_NO.PRODUCT.BILLING_PRODUCT_DETAIL - : Config.LOG_TP_NO.PRODUCT.PRODUCT_DETAIL; + ? Config.LOG_TP_NO.PRODUCT.GROUP_DETAIL + : isBillingProductVisible + ? Config.LOG_TP_NO.PRODUCT.BILLING_PRODUCT_DETAIL + : Config.LOG_TP_NO.PRODUCT.PRODUCT_DETAIL; const logKey = `${productData?.patnrId || ''}-${productData?.prdtId || ''}-${panelInfo?.linkTpCd || ''}-${logTpNo}`; if (lastProductDetailLogKeyRef.current === logKey) { @@ -831,24 +862,24 @@ export default function ProductAllSection({ lastProductDetailLogKeyRef.current = logKey; const params = { - befPrice: productData?.priceInfo?.split("|")[0], - curationId: productData?.curationId ?? "", - curationNm: productData?.curationNm ?? "", + befPrice: productData?.priceInfo?.split('|')[0], + curationId: productData?.curationId ?? '', + curationNm: productData?.curationNm ?? '', entryMenu: entryMenuRef.current, - expsOrd: "1", + expsOrd: '1', inDt: formatGMTString(new Date()), - lastPrice: productData?.priceInfo?.split("|")[1], - lgCatCd: productData?.catCd ?? "", - lgCatNm: productData?.catNm ?? "", - linkTpCd: panelInfo?.linkTpCd ?? "", + lastPrice: productData?.priceInfo?.split('|')[1], + lgCatCd: productData?.catCd ?? '', + lgCatNm: productData?.catNm ?? '', + linkTpCd: panelInfo?.linkTpCd ?? '', logTpNo, - patncNm: productData?.patncNm ?? "", - patnrId: productData?.patnrId ?? "", - prdtId: productData?.prdtId ?? "", - prdtNm: productData?.prdtNm ?? "", - revwGrd: productData?.revwGrd ?? "", - rewdAplyFlag: productData.priceInfo?.split("|")[2], - tsvFlag: productData?.todaySpclFlag ?? "", + patncNm: productData?.patncNm ?? '', + patnrId: productData?.patnrId ?? '', + prdtId: productData?.prdtId ?? '', + prdtNm: productData?.prdtNm ?? '', + revwGrd: productData?.revwGrd ?? '', + rewdAplyFlag: productData.priceInfo?.split('|')[2], + tsvFlag: productData?.todaySpclFlag ?? '', }; dispatch(sendLogProductDetail(params)); @@ -885,13 +916,22 @@ export default function ProductAllSection({ // 🚀 SingleOption.jsx의 sendLogTotalRecommend 로직 추가 if (productData && Object.keys(productData).length > 0) { - const { priceInfo, patncNm, prdtId, prdtNm, brndNm, catNm, showId, showNm } = productData; - const regularPrice = priceInfo?.split("|")[0]; - const discountPrice = priceInfo?.split("|")[1]; - const discountRate = priceInfo?.split("|")[4]; + const { + priceInfo, + patncNm, + prdtId, + prdtNm, + brndNm, + catNm, + showId, + showNm, + } = productData; + const regularPrice = priceInfo?.split('|')[0]; + const discountPrice = priceInfo?.split('|')[1]; + const discountRate = priceInfo?.split('|')[4]; // Option 정보는 현재 선택된 옵션이 없으므로 기본값 사용 - const prodOptCval = ""; // 실제로는 선택된 옵션 값이 들어가야 함 + const prodOptCval = ''; // 실제로는 선택된 옵션 값이 들어가야 함 dispatch( sendLogTotalRecommend({ @@ -906,8 +946,8 @@ export default function ProductAllSection({ category: catNm, contextName: Config.LOG_CONTEXT_NAME.DETAILPAGE, messageId: Config.LOG_MESSAGE_ID.BUY_NOW, - showId: showId ?? "", - showNm: showNm ?? "", + showId: showId ?? '', + showNm: showNm ?? '', }) ); } @@ -947,23 +987,29 @@ export default function ProductAllSection({ ); //닫히도록 - const handleCloseToast = useCallback((e) => { - // 팝업이 열려있으면 닫지 않음 - if (popupVisible) { - return; // 팝업이 활성이면 무시 - } - dispatch(clearAllToasts()); - setOpenToast(false); - }, [dispatch, popupVisible]); + const handleCloseToast = useCallback( + (e) => { + // 팝업이 열려있으면 닫지 않음 + if (popupVisible) { + return; // 팝업이 활성이면 무시 + } + dispatch(clearAllToasts()); + setOpenToast(false); + }, + [dispatch, popupVisible] + ); - const handleFocus = useCallback((e)=>{ - // 팝업이 열려있으면 닫지 않음 - if (popupVisible && cursorVisible) { - return; // 팝업이 활성이면 무시 - } - dispatch(clearAllToasts()); - setOpenToast(false); - },[dispatch, popupVisible, cursorVisible]) + const handleFocus = useCallback( + (e) => { + // 팝업이 열려있으면 닫지 않음 + if (popupVisible && cursorVisible) { + return; // 팝업이 활성이면 무시 + } + dispatch(clearAllToasts()); + setOpenToast(false); + }, + [dispatch, popupVisible, cursorVisible] + ); // 스크롤 컨테이너의 클릭 이벤트 추적용 로깅 const handleScrollContainerClick = useCallback((e) => { @@ -1010,7 +1056,10 @@ export default function ProductAllSection({ // TODO: 장바구니 추가 로직 구현 }, []); - const { revwGrd, orderPhnNo } = useMemo(() => extractProductMeta(productInfo), [productInfo]); + const { revwGrd, orderPhnNo } = useMemo( + () => extractProductMeta(productInfo), + [productInfo] + ); const [favoriteOverride, setFavoriteOverride] = useState(null); const favoriteFlag = useMemo( @@ -1019,7 +1068,8 @@ export default function ProductAllSection({ ); const [mobileSendPopupOpen, setMobileSendPopupOpen] = useState(false); - const [isShowUserReviewsFocused, setIsShowUserReviewsFocused] = useState(false); + const [isShowUserReviewsFocused, setIsShowUserReviewsFocused] = + useState(false); // 🆕 [251210] patnrId=21 SEE MORE PRODUCTS 버튼 표시 여부 const [hasSeeMoreProducts, setHasSeeMoreProducts] = useState(false); const [seeMoreProductsData, setSeeMoreProductsData] = useState([]); @@ -1047,9 +1097,9 @@ export default function ProductAllSection({ // User Reviews 스크롤 핸들러 추가 const handleUserReviewsClick = useCallback(() => { scrollToSection('scroll-marker-user-reviews'); - setTimeout(()=>{ - Spotlight.focus("user-reviews-container"); - },100) + setTimeout(() => { + Spotlight.focus('user-reviews-container'); + }, 100); }, [scrollToSection]); // ProductVideo V1 전용 - MediaPanel minimize 포함 @@ -1125,7 +1175,10 @@ export default function ProductAllSection({ document.addEventListener('detailpanel-scroll-reset', handleScrollReset); return () => { - document.removeEventListener('detailpanel-scroll-reset', handleScrollReset); + document.removeEventListener( + 'detailpanel-scroll-reset', + handleScrollReset + ); }; }, []); const productDetailRef = useRef(null); //높이값 변경때문 @@ -1150,7 +1203,11 @@ export default function ProductAllSection({ } // 이미지들 추가 - if (productData && productData.imgUrls600 && productData.imgUrls600.length > 0) { + if ( + productData && + productData.imgUrls600 && + productData.imgUrls600.length > 0 + ) { productData.imgUrls600.forEach((image, imgIndex) => { items.push({ type: 'image', @@ -1170,7 +1227,9 @@ export default function ProductAllSection({ // renderItems에 Video가 존재하는지 확인하는 boolean 상태 const hasVideo = useMemo(() => { return ( - renderItems && renderItems.length > 0 && renderItems.some((item) => item.type === 'video') + renderItems && + renderItems.length > 0 && + renderItems.some((item) => item.type === 'video') ); }, [renderItems]); @@ -1182,10 +1241,11 @@ export default function ProductAllSection({ const handleShopByMobileOpen = useCallback(() => { // sendLogShopByMobile - Source와 동일한 로깅 추가 if (productData && Object.keys(productData).length > 0) { - const { priceInfo, patncNm, patnrId, prdtId, prdtNm, brndNm, catNm } = productData; - const regularPrice = priceInfo?.split("|")[0]; - const discountPrice = priceInfo?.split("|")[1]; - const discountRate = priceInfo?.split("|")[4]; + const { priceInfo, patncNm, patnrId, prdtId, prdtNm, brndNm, catNm } = + productData; + const regularPrice = priceInfo?.split('|')[0]; + const discountPrice = priceInfo?.split('|')[1]; + const discountRate = priceInfo?.split('|')[4]; const logParams = { prdtId, @@ -1233,7 +1293,8 @@ export default function ProductAllSection({ }, [promotions, isBillingProductVisible, shopByMobileId, stackOrder]); const firstCouponId = useMemo( - () => (promotions && promotions.length > 0 ? 'detail-coupon-button-0' : null), + () => + promotions && promotions.length > 0 ? 'detail-coupon-button-0' : null, [promotions] ); @@ -1242,34 +1303,37 @@ export default function ProductAllSection({ [] ); - const handleThemeItemButtonClick = useCallback((e) => { - e.stopPropagation(); - dispatch( - showToast({ - id: 'theme-contents-toast', - message: '', - type: 'themeContents', - duration: 0, // 수동으로 닫을 때까지 유지 - position: 'bottom-center', - // ThemeContents 관련 props - themeItems: themeProducts, - setSelectedIndex, - videoVerticalVisible: false, - currentVideoShowId: null, - tabIndex: 0, - handleItemFocus: () => {}, - tabTitle: ['THEME'], - panelInfo, - direction: 'horizontal', - version: 2, - // onToastClose: () => { - // setTimeout(() => { - // Spotlight.focus('theme-open-button'); - // }, 100); - // }, - }) - ); - }, [dispatch, themeProducts, setSelectedIndex, panelInfo]); + const handleThemeItemButtonClick = useCallback( + (e) => { + e.stopPropagation(); + dispatch( + showToast({ + id: 'theme-contents-toast', + message: '', + type: 'themeContents', + duration: 0, // 수동으로 닫을 때까지 유지 + position: 'bottom-center', + // ThemeContents 관련 props + themeItems: themeProducts, + setSelectedIndex, + videoVerticalVisible: false, + currentVideoShowId: null, + tabIndex: 0, + handleItemFocus: () => {}, + tabTitle: ['THEME'], + panelInfo, + direction: 'horizontal', + version: 2, + // onToastClose: () => { + // setTimeout(() => { + // Spotlight.focus('theme-open-button'); + // }, 100); + // }, + }) + ); + }, + [dispatch, themeProducts, setSelectedIndex, panelInfo] + ); const handleProductDetailsClick = useCallback(() => { dispatch(minimizeModalMedia()); @@ -1277,17 +1341,17 @@ export default function ProductAllSection({ // Source의 handleIndicatorOptions와 동일한 로깅 기능 추가 handleIndicatorOptions(); - setTimeout(()=>{ - Spotlight.focus("product-description-content") - },100); + setTimeout(() => { + Spotlight.focus('product-description-content'); + }, 100); }, [scrollToSection, dispatch, handleIndicatorOptions]); const handleYouMayAlsoLikeClick = useCallback(() => { dispatch(minimizeModalMedia()); scrollToSection('scroll-marker-you-may-also-like'); - setTimeout(()=>{ - Spotlight.focus("detail_youMayAlsoLike_area") - },100); + setTimeout(() => { + Spotlight.focus('detail_youMayAlsoLike_area'); + }, 100); }, [scrollToSection, dispatch]); const handleSeeMoreProductsClick = useCallback(() => { @@ -1295,16 +1359,21 @@ export default function ProductAllSection({ // 버튼 클릭 시 스크롤만 처리 (데이터는 useEffect에서 처리) scrollToSection('scroll-marker-see-more-products'); setTimeout(() => { - Spotlight.focus("detail_seeMoreProducts_area"); + Spotlight.focus('detail_seeMoreProducts_area'); }, 100); }, [scrollToSection]); // 🆕 [251210] patnrId=21인 경우 그룹 상품 데이터 미리 처리 useEffect(() => { - if (panelInfo?.patnrId === 21 || panelInfo?.patnrId === "21") { - console.log('[SEE MORE PRODUCTS] patnrId=21 detected - processing group data on panel mount'); + if (panelInfo?.patnrId === 21 || panelInfo?.patnrId === '21') { + console.log( + '[SEE MORE PRODUCTS] patnrId=21 detected - processing group data on panel mount' + ); console.log('[SEE MORE PRODUCTS] panelInfo:', panelInfo); - console.log('[SEE MORE PRODUCTS] brandShopByShowCategoryGroups:', brandShopByShowCategoryGroups); + console.log( + '[SEE MORE PRODUCTS] brandShopByShowCategoryGroups:', + brandShopByShowCategoryGroups + ); const patnrIdString = String(panelInfo.patnrId); const currentPrdtId = panelInfo.prdtId; @@ -1323,16 +1392,25 @@ export default function ProductAllSection({ // 모든 contsId에서 현재 상품이 속한 그룹 찾기 for (const contsId in categoryGroups) { const contsInfo = categoryGroups[contsId]; - console.log(`[SEE MORE PRODUCTS] Checking contsId: ${contsId}, contsNm: ${contsInfo.contsNm}`); + console.log( + `[SEE MORE PRODUCTS] Checking contsId: ${contsId}, contsNm: ${contsInfo.contsNm}` + ); if (contsInfo.brandShopByShowClctInfos) { for (const group of contsInfo.brandShopByShowClctInfos) { - console.log(`[SEE MORE PRODUCTS] Checking group: ${group.clctNm} (${group.clctId})`); + console.log( + `[SEE MORE PRODUCTS] Checking group: ${group.clctNm} (${group.clctId})` + ); if (group.brandProductInfos) { - const foundProduct = group.brandProductInfos.find(p => p.prdtId === currentPrdtId); + const foundProduct = group.brandProductInfos.find( + (p) => p.prdtId === currentPrdtId + ); if (foundProduct) { - console.log('[SEE MORE PRODUCTS] 🎯 Found current product:', foundProduct); + console.log( + '[SEE MORE PRODUCTS] 🎯 Found current product:', + foundProduct + ); foundGroup = group; foundConts = contsInfo; break; @@ -1351,22 +1429,28 @@ export default function ProductAllSection({ console.log(' - Group ID:', foundGroup.clctId); // 현재 상품을 제외한 다른 상품들 확인 - const otherProducts = foundGroup.brandProductInfos.filter(p => p.prdtId !== currentPrdtId); + const otherProducts = foundGroup.brandProductInfos.filter( + (p) => p.prdtId !== currentPrdtId + ); console.log(' - Other products count:', otherProducts.length); if (otherProducts.length > 0) { // 다른 상품이 있을 때만 버튼 표시 shouldShowButton = true; - console.log('[SEE MORE PRODUCTS] 📦 Showing button - group has other products:'); + console.log( + '[SEE MORE PRODUCTS] 📦 Showing button - group has other products:' + ); otherProducts.forEach((product, index) => { - console.log(` ${index + 1}. ${product.prdtNm} (${product.prdtId})`); + console.log( + ` ${index + 1}. ${product.prdtNm} (${product.prdtId})` + ); console.log(` - Price: ${product.priceInfo}`); console.log(` - Brand: ${product.brndNm || 'N/A'}`); }); // 🆕 SeeMoreProducts 컴포넌트를 위한 데이터 변환 - const formattedProducts = otherProducts.map(product => ({ + const formattedProducts = otherProducts.map((product) => ({ prdtId: product.prdtId, prdtNm: product.prdtNm, priceInfo: product.priceInfo, @@ -1382,14 +1466,20 @@ export default function ProductAllSection({ // YouMayAlsoLike 데이터 형식으로 맞추기 setSeeMoreProductsData(formattedProducts); } else { - console.log('[SEE MORE PRODUCTS] ❌ No other products in group - hiding button'); + console.log( + '[SEE MORE PRODUCTS] ❌ No other products in group - hiding button' + ); setSeeMoreProductsData([]); } } else { - console.log('[SEE MORE PRODUCTS] ❌ No group found for current product - hiding button'); + console.log( + '[SEE MORE PRODUCTS] ❌ No group found for current product - hiding button' + ); } } else { - console.log('[SEE MORE PRODUCTS] ❌ No category groups found for patnrId 21 - hiding button'); + console.log( + '[SEE MORE PRODUCTS] ❌ No category groups found for patnrId 21 - hiding button' + ); } // 버튼 표시 여부 상태 설정 @@ -1417,9 +1507,7 @@ export default function ProductAllSection({ const mediaMinimizedRef = useRef(false); const getTotalContentHeight = useCallback(() => { const measuredHeight = - contentHeightRef.current || - scrollContainerRef.current?.scrollHeight || - 0; + contentHeightRef.current || scrollContainerRef.current?.scrollHeight || 0; return measuredHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0); }, []); @@ -1452,8 +1540,9 @@ export default function ProductAllSection({ const prevScrollTop = prevScrollTopRef.current; scrollPositionRef.current = currentScrollTop; - contentHeightRef.current = e?.scrollHeight || contentHeightRef.current || 0; - + contentHeightRef.current = + e?.scrollHeight || contentHeightRef.current || 0; + // 기존 bottom 체크 로직 유지 const totalHeight = getTotalContentHeight(); if (totalHeight) { @@ -1510,7 +1599,13 @@ export default function ProductAllSection({ } // v2: onScrollStop에서 처리 (기존 로직 유지) }, - [isBottom, productVideoVersion, isVideoPlaying, dispatch, getTotalContentHeight] + [ + isBottom, + productVideoVersion, + isVideoPlaying, + dispatch, + getTotalContentHeight, + ] ); // 스크롤 멈추었을 때만 호출 (성능 최적화) @@ -1518,7 +1613,8 @@ export default function ProductAllSection({ (e) => { const currentScrollTop = e.scrollTop; scrollPositionRef.current = currentScrollTop; - contentHeightRef.current = e?.scrollHeight || contentHeightRef.current || 0; + contentHeightRef.current = + e?.scrollHeight || contentHeightRef.current || 0; const totalHeight = getTotalContentHeight(); if (totalHeight) { const isAtBottom = currentScrollTop + 944 >= totalHeight; @@ -1583,7 +1679,9 @@ export default function ProductAllSection({ // 초기 로딩 후 Skeleton 숨기기 useEffect(() => { const hasDataReady = - productType === 'theme' ? hasThemeContents && !!productData : !!productData; + productType === 'theme' + ? hasThemeContents && !!productData + : !!productData; if (productType && hasDataReady && isInitialLoading) { const timer = setTimeout(() => { @@ -1693,7 +1791,11 @@ export default function ProductAllSection({ } return ( - + {/* Left Margin Section - 60px */}
@@ -1726,8 +1828,11 @@ export default function ProductAllSection({
{isShowQRCode ? ( <> - {/* */} - + {/* */} + ) : (
@@ -1763,7 +1868,9 @@ export default function ProductAllSection({ return (
-
SPECIAL PROMOTION
+
+ SPECIAL PROMOTION +
Coupon only applicable to this product!
@@ -1815,7 +1922,9 @@ export default function ProductAllSection({ className={css.shopByMobileButton} onClick={handleShopByMobileOpen} > -
{$L('SHOP BY MOBILE')}
+
+ {$L('SHOP BY MOBILE')} +
{panelInfo && (
@@ -1835,7 +1944,9 @@ export default function ProductAllSection({
{orderPhnNo && ( <> -
{$L('Call to Order')}
+
+ {$L('Call to Order')} +
@@ -1881,30 +1992,32 @@ export default function ProductAllSection({ )} - {(panelInfo?.patnrId !== 21 || panelInfo?.patnrId !== "21") && hasYouMayAlsoLike && ( - - {$L('YOU MAY ALSO LIKE')} - - )} + {(panelInfo?.patnrId !== 21 || panelInfo?.patnrId !== '21') && + hasYouMayAlsoLike && ( + + {$L('YOU MAY ALSO LIKE')} + + )} {/* 🆕 [251210] patnrId=21인 경우 SEE MORE PRODUCTS 버튼 */} - {(panelInfo?.patnrId === 21 || panelInfo?.patnrId === "21") && hasSeeMoreProducts && ( - - {$L('SEE MORE PRODUCTS')} - - )} + {(panelInfo?.patnrId === 21 || panelInfo?.patnrId === '21') && + hasSeeMoreProducts && ( + + {$L('SEE MORE PRODUCTS')} + + )} {/* YouMayLike 버튼 렌더링 상태 로그 */} {/* {(() => { console.log('[YouMayLike] 버튼 렌더링 체크:', { @@ -1935,10 +2048,13 @@ export default function ProductAllSection({ }} >
{$L('THEME ITEM')}
- + - )} + )} handleButtonFocus('product')} - onBlur={handleButtonBlur} + onBlur={handleButtonBlur} > {/* 비디오가 있으면 먼저 렌더링 (productVideoVersion이 3이 아닐 때만) */} - {hasVideo && renderItems[0].type === 'video' && productVideoVersion !== 3 && ( - <> - {productVideoVersion === 1 ? ( - setIsVideoPlaying(true)} - onScrollToImages={handleScrollToImagesV1} - onFocus={() => {}} - data-spotlight-id="product-video-player-container" - disclaimer={productData.disclaimer} - /> - ) : ( - - )} -
- - )} + {hasVideo && + renderItems[0].type === 'video' && + productVideoVersion !== 3 && ( + <> + {productVideoVersion === 1 ? ( + setIsVideoPlaying(true)} + onScrollToImages={handleScrollToImagesV1} + onFocus={() => {}} + data-spotlight-id="product-video-player-container" + disclaimer={productData.disclaimer} + /> + ) : ( + + )} +
+ + )} {/* 이미지들만 렌더링 (비디오 제외) */} {renderItems.length > 0 @@ -2028,7 +2149,10 @@ export default function ProductAllSection({ )) : !hasVideo && }
-
+
{/* 리뷰 데이터가 완전할 때만 UserReviews 섹션 표시 */} -
+
{isReviewDataComplete && (
-
- {(panelInfo?.patnrId !== 21 || panelInfo?.patnrId !== "21") && hasYouMayAlsoLike && ( +
+ {panelInfo?.patnrId !== '21' && hasYouMayAlsoLike && (
{/* {(() => { console.log('[YouMayLike] YouMayAlsoLike 컴포넌트 렌더링:', { @@ -2090,10 +2220,16 @@ export default function ProductAllSection({
{/* 🆕 [251210] patnrId=21인 경우 SEE MORE PRODUCTS 섹션 */} - {(panelInfo?.patnrId === 21 || panelInfo?.patnrId === "21") && hasSeeMoreProducts && ( + {panelInfo?.patnrId === '21' && hasSeeMoreProducts && (
-
-
+
+
)} - -
+ +
@@ -2127,7 +2263,7 @@ export default function ProductAllSection({ SCROLL DOWN

- )} + )}
{/* COUPON POPUP */} {activePopup === Config.ACTIVE_POPUP.couponPopup && ( @@ -2156,7 +2292,9 @@ export default function ProductAllSection({ /> )} -
{`1/${selectedCoupon?.length}`}
+
{`1/${selectedCoupon?.length}`}
)}