diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.jsx index 7c263bf4..3b6540a2 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.jsx @@ -1,20 +1,40 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { + useDispatch, + useSelector, +} from 'react-redux'; import { Job } from '@enact/core/util'; -import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; +import SpotlightContainerDecorator + from '@enact/spotlight/SpotlightContainerDecorator'; import Spottable from '@enact/spotlight/Spottable'; import { clearThemeDetail } from '../../../../actions/homeActions'; -import { popPanel, pushPanel, updatePanel } from '../../../../actions/panelActions'; +import { + popPanel, + pushPanel, + updatePanel, +} from '../../../../actions/panelActions'; import { finishVideoPreview } from '../../../../actions/playActions'; import THeader from '../../../../components/THeader/THeader'; -import TItemCard from '../../../../components/TItemCard/TItemCard'; -import TVerticalPagenator from '../../../../components/TVerticalPagenator/TVerticalPagenator'; -import TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList'; +import TItemCardNew from '../../../../components/TItemCard/TItemCard.new'; +import TVerticalPagenator + from '../../../../components/TVerticalPagenator/TVerticalPagenator'; +import TVirtualGridList + from '../../../../components/TVirtualGridList/TVirtualGridList'; import useScrollTo from '../../../../hooks/useScrollTo'; -import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../../utils/Config'; +import { + LOG_CONTEXT_NAME, + LOG_MESSAGE_ID, + panel_names, +} from '../../../../utils/Config'; import { $L } from '../../../../utils/helperMethods'; import css from './YouMayAlsoLike.module.less'; @@ -136,6 +156,7 @@ export default function YouMayAlsoLike({ patncNm, brndNm, lgCatCd, + euEnrgLblInfos, } = product; const handleItemClick = () => { @@ -166,7 +187,7 @@ export default function YouMayAlsoLike({ cursorOpen.current.stop(); }; return ( - ); })} diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.module.less b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.module.less index 0906deeb..af050ff0 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.module.less +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.module.less @@ -81,7 +81,7 @@ > div:nth-child(2) { margin-top: 15px; /* desc wrapper */ - > div > h3 { + > div > div > h3 { /* title */ color: rgba(234, 234, 234, 1); .size(@w:100%,@h:64px); diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductOverview/ProductPriceDisplay/ProductPriceDisplay.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductOverview/ProductPriceDisplay/ProductPriceDisplay.jsx index 24f242b5..0ac0a91a 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductOverview/ProductPriceDisplay/ProductPriceDisplay.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductOverview/ProductPriceDisplay/ProductPriceDisplay.jsx @@ -1,19 +1,59 @@ import React, { useCallback, useMemo, + useState, } from 'react'; import classNames from 'classnames'; -import { useSelector } from 'react-redux'; +import { + useDispatch, + useSelector, +} from 'react-redux'; +import Spotlight from '@enact/spotlight'; +import Spottable from '@enact/spotlight/Spottable'; + +import { + setHidePopup, + setShowPopup, +} from '../../../../actions/commonActions'; +import { + clearConvertedImage, + convertPdfToImage, +} from '../../../../actions/convertActions'; +import CustomImage from '../../../../components/CustomImage/CustomImage'; +import TPopUp from '../../../../components/TPopUp/TPopUp'; import usePriceInfo from '../../../../hooks/usePriceInfo'; +import * as Config from '../../../../utils/Config'; import { $L } from '../../../../utils/helperMethods'; +import { SpotlightIds } from '../../../../utils/SpotlightIds'; import BuyNowPriceDisplay from './BuyNowPriceDisplay/BuyNowPriceDisplay'; import css from './ProductPriceDisplay.module.less'; import ShopByMobilePriceDisplay from './ShopByMobilePriceDisplay/ShopByMobilePriceDisplay'; +const SpottableComponent = Spottable("div"); + +const STRING_CONF = { + ENERGY_LOADING: "Loading energy label...", + ENERGY_ERROR: "Failed to load energy label", +}; + export default function ProductPriceDisplay({ productType, productInfo }) { + const dispatch = useDispatch(); + + const [currentPdfUrl, setCurrentPdfUrl] = useState(null); + + const cursorVisible = useSelector( + (state) => state.common.appStatus.cursorVisible + ); + const { activePopup, popupVisible } = useSelector( + (state) => state.common.popup + ); + + const convert = useSelector((state) => state.convert); + + const webOSVersion = useSelector( (state) => state.common.appStatus.webOSVersion ); @@ -27,6 +67,7 @@ export default function ProductPriceDisplay({ productType, productInfo }) { patnrName, installmentMonths, orderPhnNo, + euEnrgLblInfos, } = productInfo; const { @@ -65,6 +106,67 @@ export default function ProductPriceDisplay({ productType, productInfo }) { ); }, [productType, productInfo?.pmtSuptYn, webOSVersion]); + const handleClosePopup = useCallback(() => { + if (convert?.convertedImage && convert.convertedImage.startsWith("blob:")) { + URL.revokeObjectURL(convert.convertedImage); + } + + dispatch(setHidePopup()); + dispatch(clearConvertedImage()); + setCurrentPdfUrl(null); + }, [dispatch, convert?.convertedImage]); + + const onEnergyClick = useCallback( + (e, pdfUrl) => { + e.stopPropagation(); + setCurrentPdfUrl(pdfUrl); + + // PNG 이미지는 직접 표시 + if (pdfUrl.endsWith(".png")) { + // console.log(`📸 [EnergyLabel] Displaying PNG directly:`, pdfUrl); + dispatch({ + type: "CONVERT_PDF_TO_IMAGE_SUCCESS", + payload: { pdfUrl, imageUrl: pdfUrl }, + }); + dispatch(setShowPopup(Config.ACTIVE_POPUP.energyPopup)); + setTimeout(() => { + Spotlight.focus(SpotlightIds.TPOPUP); + }, 250); + return; + } + + // PDF 변환 시작 (최대 5회 재시도, 60초 타임아웃) + // console.log(`📄 [EnergyLabel] Starting PDF conversion:`, pdfUrl); + dispatch( + convertPdfToImage( + pdfUrl, + (error, imageUrl) => { + if (error) { + console.error( + "[EnergyLabel] 최종 변환 실패:", + error.message || error + ); + // 실패해도 팝업은 열어서 에러 메시지 표시 + dispatch(setShowPopup(Config.ACTIVE_POPUP.energyPopup)); + setTimeout(() => { + Spotlight.focus(SpotlightIds.TPOPUP); + }, 250); + } else { + console.log(`[EnergyLabel] PDF 변환 완료, 팝업 표시`); + dispatch(setShowPopup(Config.ACTIVE_POPUP.energyPopup)); + setTimeout(() => { + Spotlight.focus(SpotlightIds.TPOPUP); + }, 250); + } + }, + 5, // 최대 5회 재시도 + 60000 // 60초 타임아웃 + ) + ); + }, + [dispatch] + ); + return ( <> {productType && productInfo && ( @@ -91,8 +193,125 @@ export default function ProductPriceDisplay({ productType, productInfo }) { isDiscounted={isDiscounted} /> )} +
+ {euEnrgLblInfos && euEnrgLblInfos.map((item, index)=>{ + return( + onEnergyClick(e, item.enrgLblUrl)} + aria-label={`Energy Efficiency ${item.enrgGrade || ""}`} + className={css.enrgLbImg} + > + + + ) + })} +
)} + {(() => { + // 팝업이 표시되어야 하는 조건 검증 + const isEnergyPopup = activePopup === Config.ACTIVE_POPUP.energyPopup; + const hasPdfUrl = !!currentPdfUrl; + const shouldShowPopup = isEnergyPopup && hasPdfUrl; + + if (!shouldShowPopup) { + // console.log('[EnergyLabel] Popup not showing:', { + // isEnergyPopup, + // hasPdfUrl, + // popupVisible, + // }); + return null; + } + + // console.log('[EnergyLabel] Rendering popup:', { + // popupVisible, + // activePopup, + // currentPdfUrl, + // isConverting: convert?.isConverting, + // hasImage: !!convert?.convertedImage, + // hasError: !!convert?.error, + // }); + + return ( + +
+ {convert ? ( + <> +
+ {convert.convertedImage ? ( + Energy Label + ) : convert.error ? ( +
+

{$L(STRING_CONF.ENERGY_ERROR)}

+

+ {convert.error?.message || String(convert.error)} +

+
+ ) : convert.isConverting ? ( +
+

{$L(STRING_CONF.ENERGY_LOADING)}

+

+ Converting PDF to image... (attempt in progress) +

+
+ ) : ( +
+

{$L(STRING_CONF.ENERGY_ERROR)}

+

+ Unknown state - no image or error +

+
+ )} +
+ + ) : ( +
+

{$L(STRING_CONF.ENERGY_ERROR)}

+

+ Convert reducer state not found +

+
+ )} +
+
+ ); + })()} ); } diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductOverview/ProductPriceDisplay/ProductPriceDisplay.module.less b/com.twin.app.shoptime/src/views/DetailPanel/ProductOverview/ProductPriceDisplay/ProductPriceDisplay.module.less index ad2fc607..f608d8ae 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductOverview/ProductPriceDisplay/ProductPriceDisplay.module.less +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductOverview/ProductPriceDisplay/ProductPriceDisplay.module.less @@ -99,3 +99,90 @@ } } } + +.enrgLbImgBox { + display:flex; + justify-content: flex-start; + align-items: center; + .enrgLbImg { + width:62px; + height:38px; + border:4px solid transparent; + &:focus { + border: 4px solid @PRIMARY_COLOR_RED; + box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5); + border-radius: 12px; + box-sizing: border-box; + } + > img { + width:100%; + } + } +} + +.popupContainer { + .header { + .size(@w: 780px , @h: 102px); + .flex(@display: flex, @justifyCenter: center, @alignCenter: center, @direction: row); + background-color: #e7ebef; + + > h3 { + font-size: 36px; + color: #222222; + font-weight: bold; + } + } + + .qrcodeContainer { + padding: 30px 0; + display: flex; + flex-direction: column; + align-items: center; + + .qrcode { + .size(@w: 360px , @h: 360px); + background-color: #ffffff; + border-radius: 12px; + box-shadow: 0 0 0 1px #dadada inset; + margin-bottom: 41px; + } + + > h3 { + display: flex; + text-align: center; + word-break: break-word; + line-height: 1.27; + } + + .popupBtn { + .size(@w: 300px , @h: 78px); + margin-top: 38px; + } + } +} + +// 🔽 에너지 라벨 팝업 스타일 (1920x1080 TV 화면 기준) +.energyPopupContent { + width: 100%; + max-height: 800px; // 팝업 타이틀/버튼 영역 제외한 콘텐츠 최대 높이 + overflow-y: auto; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.energyImagesContainer { + width: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.energyImage { + max-width: 100%; + max-height: 750px; // 1080px - 타이틀(~120px) - 버튼(~120px) - 여백(~90px) + width: auto; + height: auto; + object-fit: contain; // 비율 유지하면서 컨테이너에 맞춤 +} diff --git a/com.twin.app.shoptime/src/views/SearchPanel/SearchResultsNew/ItemCard.jsx b/com.twin.app.shoptime/src/views/SearchPanel/SearchResultsNew/ItemCard.jsx index 27319243..2a8aeb77 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/SearchResultsNew/ItemCard.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/SearchResultsNew/ItemCard.jsx @@ -4,7 +4,10 @@ import { useDispatch } from 'react-redux'; import Spotlight from '@enact/spotlight'; -import { pushPanel, updatePanel } from '../../../actions/panelActions'; +import { + pushPanel, + updatePanel, +} from '../../../actions/panelActions'; import { navigateToDetailPanel } from '../../../actions/panelNavigationActions'; import TItemCardNew from '../../../components/TItemCard/TItemCard.new'; import TScroller from '../../../components/TScroller/TScroller'; @@ -62,7 +65,7 @@ const ItemCard = ({ onClick, itemInfo, searchQuery }) => { <> {itemInfo.map((item, index) => { - const { thumbnail, title, dcPrice, price, soldout, contentId } = item; + const { thumbnail, title, dcPrice, price, soldout, contentId,euEnrgLblInfos } = item; const tokens = contentId && contentId.split('_'); const patnrId = tokens?.[4] || ''; const prdtId = tokens?.[5] || ''; @@ -83,6 +86,7 @@ const ItemCard = ({ onClick, itemInfo, searchQuery }) => { {...(index === 0 ? { 'data-spotlight-up': 'searchtabContainer' } : {})} label={index * 1 + 1 + ' of ' + itemInfo.length + 1} lastLabel=" go to detail, button" + euEnrgLblInfos={euEnrgLblInfos} /> ); })}