[에너지 라벨 노출 관련 작업]

- 상세 가격하단 노출 처리 및 스타일 수정
 - youmayalsolike 부분에 노출  처리 및 스타일 수정
 - 검색결과 아이템 부분에 노출 처리.
This commit is contained in:
junghoon86.park
2025-11-11 19:28:32 +09:00
parent 69659f301e
commit 1b902b69ad
5 changed files with 345 additions and 13 deletions

View File

@@ -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 (
<TItemCard
<TItemCardNew
key={prdtId}
contextName={LOG_CONTEXT_NAME.YOUMAYLIKE}
messageId={LOG_MESSAGE_ID.CONTENTCLICK}
@@ -186,6 +207,7 @@ export default function YouMayAlsoLike({
onClick={handleItemClick}
label={index * 1 + 1 + ' of ' + newYoumaylikeProductData.length}
lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos}
/>
);
})}

View File

@@ -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);

View File

@@ -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}
/>
)}
<div className={css.enrgLbImgBox}>
{euEnrgLblInfos && euEnrgLblInfos.map((item, index)=>{
return(
<SpottableComponent
key={index}
spotlightDisabled={Boolean(!cursorVisible)}
onClick={(e) => onEnergyClick(e, item.enrgLblUrl)}
aria-label={`Energy Efficiency ${item.enrgGrade || ""}`}
className={css.enrgLbImg}
>
<CustomImage
alt={`Energy Label ${item.enrgGrade || index + 1}`}
delay={0}
src={item.enrgLblIcnUrl}
/>
</SpottableComponent>
)
})}
</div>
</div>
)}
{(() => {
// 팝업이 표시되어야 하는 조건 검증
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 (
<TPopUp
kind="energyPopup"
title={$L("Energy Efficiency")}
hasText
open={popupVisible}
hasButton
button1Text={$L("CLOSE")}
onClose={handleClosePopup}
>
<div className={css.energyPopupContent}>
{convert ? (
<>
<div className={css.energyImagesContainer}>
{convert.convertedImage ? (
<img
alt="Energy Label"
src={convert.convertedImage}
className={css.energyImage}
/>
) : convert.error ? (
<div>
<p>{$L(STRING_CONF.ENERGY_ERROR)}</p>
<p style={{ fontSize: "0.8em", marginTop: "10px" }}>
{convert.error?.message || String(convert.error)}
</p>
</div>
) : convert.isConverting ? (
<div>
<p>{$L(STRING_CONF.ENERGY_LOADING)}</p>
<p
style={{
fontSize: "0.8em",
marginTop: "10px",
color: "#999",
}}
>
Converting PDF to image... (attempt in progress)
</p>
</div>
) : (
<div>
<p>{$L(STRING_CONF.ENERGY_ERROR)}</p>
<p
style={{
fontSize: "0.8em",
marginTop: "10px",
color: "#999",
}}
>
Unknown state - no image or error
</p>
</div>
)}
</div>
</>
) : (
<div>
<p>{$L(STRING_CONF.ENERGY_ERROR)}</p>
<p
style={{
fontSize: "0.8em",
marginTop: "10px",
color: "#999",
}}
>
Convert reducer state not found
</p>
</div>
)}
</div>
</TPopUp>
);
})()}
</>
);
}

View File

@@ -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; // 비율 유지하면서 컨테이너에 맞춤
}

View File

@@ -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 }) => {
<>
<TScroller className={css.container} spotlightId={SpotlightIds.SEARCH_ITEM}>
{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}
/>
);
})}