[테마 아이템]
- 상품 상세에서 노출되는 테마 아이템 노출에 관련하여 처리. - 에너지 라벨 노출및 테마 아이템 상품 이미지 변경.
This commit is contained in:
@@ -1,21 +1,32 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
|
||||
import arrowDownIcon from '../../../../assets/images/icons/ic-arrow-down.svg';
|
||||
import themeImage1 from '../../../../assets/images/theme/image-1.png';
|
||||
import themeImage2 from '../../../../assets/images/theme/image-2.png';
|
||||
import themeImage3 from '../../../../assets/images/theme/image-3.png';
|
||||
import { sendLogTotalRecommend } from '../../../actions/logActions';
|
||||
import { updatePanel } from '../../../actions/panelActions';
|
||||
import TButton from '../../../components/TButton/TButton';
|
||||
import ThemeItemCard from './ThemeItemCard';
|
||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../utils/Config';
|
||||
import usePriceInfo from '../../../hooks/usePriceInfo';
|
||||
import {
|
||||
LOG_CONTEXT_NAME,
|
||||
LOG_MESSAGE_ID,
|
||||
panel_names,
|
||||
} from '../../../utils/Config';
|
||||
import { $L } from '../../../utils/helperMethods';
|
||||
import css from './ThemeContents.module.less';
|
||||
import { sendLogTotalRecommend } from '../../../actions/logActions';
|
||||
import ThemeItemCard from './ThemeItemCard';
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{
|
||||
@@ -151,20 +162,22 @@ export default function ThemeContents({
|
||||
|
||||
const renderItem = useCallback(
|
||||
(item, index) => {
|
||||
console.log("[Theme] renderItem Item",item);
|
||||
console.log('[Theme] renderItem index', index, 'displayItems size', displayItems?.length);
|
||||
const {
|
||||
prdtId,
|
||||
prdtNm,
|
||||
prdtImgPath,
|
||||
salePrice,
|
||||
originalPrice,
|
||||
patnrLogoPath,
|
||||
imgUrls,
|
||||
// salePrice,
|
||||
// originalPrice,
|
||||
// patnrLogoPath,
|
||||
priceInfo,
|
||||
patncNm,
|
||||
showId,
|
||||
catNm,
|
||||
energyLabels,
|
||||
euEnrgLblInfos,
|
||||
} = item;
|
||||
|
||||
|
||||
const spotlightItemId = `theme-toast-item-${index}`;
|
||||
|
||||
const handleItemClick = () => {
|
||||
@@ -216,10 +229,9 @@ export default function ThemeContents({
|
||||
key={prdtId || `theme-item-${index}`}
|
||||
prdtId={prdtId}
|
||||
prdtNm={prdtNm}
|
||||
prdtImgPath={prdtImgPath}
|
||||
salePrice={salePrice}
|
||||
originalPrice={originalPrice}
|
||||
energyLabels={energyLabels}
|
||||
prdtImgPath={imgUrls[0]}
|
||||
priceInfo={priceInfo}
|
||||
energyLabels={euEnrgLblInfos}
|
||||
onClick={handleItemClick}
|
||||
spotlightId={spotlightItemId}
|
||||
dataSpotlightDefault={index === 0}
|
||||
|
||||
@@ -1,17 +1,48 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
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 css from './ThemeItemCard.module.less';
|
||||
|
||||
const SpottableDiv = Spottable('div');
|
||||
const SpottableTemp = Spottable("div");
|
||||
|
||||
const STRING_CONF = {
|
||||
ENERGY_LOADING: "Loading energy label...",
|
||||
ENERGY_ERROR: "Failed to load energy label",
|
||||
};
|
||||
|
||||
export default function ThemeItemCard({
|
||||
prdtId,
|
||||
prdtNm,
|
||||
prdtImgPath,
|
||||
salePrice,
|
||||
originalPrice,
|
||||
// salePrice,
|
||||
// originalPrice,
|
||||
priceInfo,
|
||||
energyLabels,
|
||||
onClick,
|
||||
onFocus,
|
||||
@@ -21,48 +52,284 @@ export default function ThemeItemCard({
|
||||
dataSpotlightDefault,
|
||||
onFocused,
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
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 handleFocus = (e) => {
|
||||
setIsFocused(true);
|
||||
onFocus?.(e);
|
||||
onFocused?.(true);
|
||||
};
|
||||
|
||||
|
||||
const handleBlur = (e) => {
|
||||
setIsFocused(false);
|
||||
onFocused?.(false);
|
||||
};
|
||||
|
||||
const { originalPrice, discountedPrice, discountRate } =
|
||||
usePriceInfo(priceInfo) || {};
|
||||
|
||||
const mockEnergyLabel = [
|
||||
{
|
||||
"enrgLblExpsOrd": "0",
|
||||
"enrgLblTpCd": "EL_TYPE_05",
|
||||
"enrgLblCd": "MNLC",
|
||||
"enrgClasCd": "A",
|
||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_a.png",
|
||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241113229264&ORIGINAL_NAME_b1_a1=27U511SA EU (E).pdf&FILE_NAME=27U511SA EU (E)[20241113011401634].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241125231113&ORIGINAL_NAME_b1_a1=27U511SA-W.pdf&FILE_NAME=27U511SA-W[20241125231113].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||
},
|
||||
{
|
||||
"enrgLblExpsOrd": "0",
|
||||
"enrgLblTpCd": "EL_TYPE_05",
|
||||
"enrgLblCd": "MNLC",
|
||||
"enrgClasCd": "D",
|
||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||
},
|
||||
{
|
||||
"enrgLblExpsOrd": "0",
|
||||
"enrgLblTpCd": "EL_TYPE_05",
|
||||
"enrgLblCd": "MNLC",
|
||||
"enrgClasCd": "D",
|
||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||
}
|
||||
];
|
||||
|
||||
const setEnactFitZIndex = (zIndexValue) => {
|
||||
const target = document.getElementById("floatLayer");
|
||||
if (target) {
|
||||
const enactFit = target.querySelector(".enact-fit");
|
||||
if (enactFit) {
|
||||
enactFit.style.zIndex = zIndexValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
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));
|
||||
setEnactFitZIndex("10000");
|
||||
setTimeout(() => {
|
||||
Spotlight.focus(SpotlightIds.TPOPUP);
|
||||
}, 250);
|
||||
}
|
||||
},
|
||||
5, // 최대 5회 재시도
|
||||
60000 // 60초 타임아웃
|
||||
)
|
||||
);
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleClosePopup = useCallback(() => {
|
||||
if (convert?.convertedImage && convert.convertedImage.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(convert.convertedImage);
|
||||
}
|
||||
setEnactFitZIndex("100");
|
||||
dispatch(setHidePopup());
|
||||
dispatch(clearConvertedImage());
|
||||
setCurrentPdfUrl(null);
|
||||
|
||||
}, [dispatch, convert?.convertedImage]);
|
||||
|
||||
return (
|
||||
<SpottableDiv
|
||||
className={`${css.itemCard} ${isFocused ? css.focused : ''}`}
|
||||
onClick={onClick}
|
||||
spotlightId={spotlightId}
|
||||
data-spotlight-default={dataSpotlightDefault}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<img src={prdtImgPath} alt={prdtId} className={css.itemImage} />
|
||||
<div className={css.itemInfo}>
|
||||
<div className={css.itemName}>{prdtNm}</div>
|
||||
<div className={css.itemPrice}>
|
||||
<span className={css.salePrice}>{salePrice}</span>
|
||||
<span className={css.originalPrice}>{originalPrice}</span>
|
||||
</div>
|
||||
{energyLabels && energyLabels.length > 0 && (
|
||||
<div className={css.energyLabels}>
|
||||
{energyLabels.map((label, labelIndex) => (
|
||||
<div key={labelIndex} className={css.energyLabel}>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
<>
|
||||
<SpottableDiv
|
||||
className={`${css.itemCard} ${isFocused ? css.focused : ''}`}
|
||||
onClick={onClick}
|
||||
spotlightId={spotlightId}
|
||||
data-spotlight-default={dataSpotlightDefault}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<img src={prdtImgPath} alt={prdtId} className={css.itemImage} />
|
||||
<div className={css.itemInfo}>
|
||||
<div className={css.itemName}>{prdtNm}</div>
|
||||
<div className={css.itemPrice}>
|
||||
<span className={css.salePrice}>{discountedPrice}</span>
|
||||
{discountedPrice !== originalPrice && (
|
||||
<span className={css.originalPrice}>{originalPrice}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SpottableDiv>
|
||||
{/* {energyLabels && energyLabels.length > 0 && (
|
||||
<div className={css.energyLabels}>
|
||||
{energyLabels.map((label, labelIndex) => (
|
||||
<div key={labelIndex} className={css.energyLabel}>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)} */}
|
||||
{mockEnergyLabel && mockEnergyLabel.length > 0 && (
|
||||
<div className={css.energyLabels}>
|
||||
{mockEnergyLabel.map((label, labelIndex) => (
|
||||
<SpottableTemp
|
||||
key={labelIndex}
|
||||
spotlightDisabled={Boolean(!cursorVisible)}
|
||||
onClick={(e) => onEnergyClick(e, label.enrgLblUrl)}
|
||||
aria-label={`Energy Efficiency ${label.enrgGrade || ""}`}
|
||||
>
|
||||
<CustomImage
|
||||
alt={`Energy Label ${label.enrgGrade || labelIndex + 1}`}
|
||||
delay={0}
|
||||
src={label.enrgLblIcnUrl}
|
||||
/>
|
||||
</SpottableTemp>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SpottableDiv>
|
||||
{(() => {
|
||||
// 팝업이 표시되어야 하는 조건 검증
|
||||
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>
|
||||
);
|
||||
})()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,8 +337,9 @@ ThemeItemCard.propTypes = {
|
||||
prdtId: PropTypes.string,
|
||||
prdtNm: PropTypes.string,
|
||||
prdtImgPath: PropTypes.string,
|
||||
salePrice: PropTypes.string,
|
||||
originalPrice: PropTypes.string,
|
||||
// salePrice: PropTypes.string,
|
||||
// originalPrice: PropTypes.string,
|
||||
priceInfo: PropTypes.string,
|
||||
energyLabels: PropTypes.array,
|
||||
onClick: PropTypes.func,
|
||||
onFocus: PropTypes.func,
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&.focused::after,
|
||||
&:focus::after,
|
||||
&:global(.spotlight)::after {
|
||||
|
||||
&:focus::after {
|
||||
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@
|
||||
display: inline-flex;
|
||||
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -80,6 +80,7 @@
|
||||
font-weight: 400;
|
||||
line-height: 25px;
|
||||
word-wrap: break-word;
|
||||
.elip(2);
|
||||
}
|
||||
|
||||
.itemPrice {
|
||||
@@ -88,7 +89,7 @@
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
|
||||
height: 20px;
|
||||
> * {
|
||||
margin-right: 5px;
|
||||
|
||||
@@ -102,8 +103,7 @@
|
||||
color: #C70850;
|
||||
font-size: 20px;
|
||||
font-family: @baseFont;
|
||||
font-weight: 700;
|
||||
line-height: 16.67;
|
||||
font-weight: 700;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
font-family: @baseFont;
|
||||
font-weight: 400;
|
||||
text-decoration: line-through;
|
||||
line-height: 16.67;
|
||||
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,19 @@
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
display: inline-flex;
|
||||
|
||||
> div {
|
||||
width:52px;
|
||||
> img {
|
||||
width:100%;
|
||||
border: 4px solid transparent;
|
||||
}
|
||||
&:focus {
|
||||
> img {
|
||||
border: 4px solid @PRIMARY_COLOR_RED;
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
> * {
|
||||
margin-right: 5px;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user