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 ? (
+

+ ) : 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}
/>
);
})}