From d196b8b49e5fc2f4f42a55a20d9faa7609797cfb Mon Sep 17 00:00:00 2001 From: "junghoon86.park" Date: Mon, 24 Nov 2025 12:39:26 +0900 Subject: [PATCH] =?UTF-8?q?[=ED=85=8C=EB=A7=88=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=ED=85=9C]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상품 상세에서 노출되는 테마 아이템 노출에 관련하여 처리. - 에너지 라벨 노출및 테마 아이템 상품 이미지 변경. --- .../ThemeProduct/ThemeContents.jsx | 42 ++- .../ThemeProduct/ThemeItemCard.jsx | 334 ++++++++++++++++-- .../ThemeProduct/ThemeItemCard.module.less | 30 +- 3 files changed, 349 insertions(+), 57 deletions(-) diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeContents.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeContents.jsx index 08396a6d..156b8b33 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeContents.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeContents.jsx @@ -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} diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeItemCard.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeItemCard.jsx index 8bcec792..8168cb9b 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeItemCard.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeItemCard.jsx @@ -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 ( - - {prdtId} -
-
{prdtNm}
-
- {salePrice} - {originalPrice} -
- {energyLabels && energyLabels.length > 0 && ( -
- {energyLabels.map((label, labelIndex) => ( -
- {label} -
- ))} + <> + + {prdtId} +
+
{prdtNm}
+
+ {discountedPrice} + {discountedPrice !== originalPrice && ( + {originalPrice} + )}
- )} -
-
+ {/* {energyLabels && energyLabels.length > 0 && ( +
+ {energyLabels.map((label, labelIndex) => ( +
+ {label} +
+ ))} +
+ )} */} + {mockEnergyLabel && mockEnergyLabel.length > 0 && ( +
+ {mockEnergyLabel.map((label, labelIndex) => ( + onEnergyClick(e, label.enrgLblUrl)} + aria-label={`Energy Efficiency ${label.enrgGrade || ""}`} + > + + + ))} +
+ )} +
+ + {(() => { + // 팝업이 표시되어야 하는 조건 검증 + 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 +

+
+ )} +
+
+ ); + })()} + ); } @@ -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, diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeItemCard.module.less b/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeItemCard.module.less index 5b43b44d..c868a83f 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeItemCard.module.less +++ b/com.twin.app.shoptime/src/views/DetailPanel/ThemeProduct/ThemeItemCard.module.less @@ -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;