[상품 상세 노출 변경에 따른 처리]

- 상품명 노출추가.
 - theadercustom부분에 themetitle부분 처리.
 - qr 크기 조절.(240px -> 190px)
 - 금액 노출부분 하단으로 떨구도록
This commit is contained in:
junghoon86.park
2025-12-01 18:18:13 +09:00
parent 579512402e
commit c522fe2777
11 changed files with 173 additions and 88 deletions

View File

@@ -1,25 +1,46 @@
// src/views/DetailPanel/DetailPanel.new.jsx // src/views/DetailPanel/DetailPanel.new.jsx
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import React, {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useDispatch, useSelector } from 'react-redux'; import {
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight'; import Spotlight from '@enact/spotlight';
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container'; import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
import { getDeviceAdditionInfo } from '../../actions/deviceActions'; import { getDeviceAdditionInfo } from '../../actions/deviceActions';
import { getThemeCurationDetailInfo, updateHomeInfo } from '../../actions/homeActions';
import { getMainCategoryDetail, getMainYouMayLike } from '../../actions/mainActions';
import { finishModalMediaForce } from '../../actions/mediaActions';
import { popPanel, updatePanel } from '../../actions/panelActions';
import { import {
// <<<<<<< HEAD getThemeCurationDetailInfo,
updateHomeInfo,
} from '../../actions/homeActions';
import {
getMainCategoryDetail,
getMainYouMayLike,
} from '../../actions/mainActions';
import { finishModalMediaForce } from '../../actions/mediaActions';
import {
popPanel,
updatePanel,
} from '../../actions/panelActions';
import {
finishVideoPreview, finishVideoPreview,
pauseFullscreenVideo, pauseFullscreenVideo,
resumeFullscreenVideo,
pauseModalVideo, pauseModalVideo,
resumeFullscreenVideo,
resumeModalVideo, resumeModalVideo,
} from '../../actions/playActions'; } from '../../actions/playActions';
import { clearProductDetail, getProductOptionId } from '../../actions/productActions'; import {
clearProductDetail,
getProductOptionId,
} from '../../actions/productActions';
import { clearAllToasts } from '../../actions/toastActions'; import { clearAllToasts } from '../../actions/toastActions';
import TBody from '../../components/TBody/TBody'; import TBody from '../../components/TBody/TBody';
import TPanel from '../../components/TPanel/TPanel'; import TPanel from '../../components/TPanel/TPanel';
@@ -31,6 +52,7 @@ import THeaderCustom from './components/THeaderCustom';
import css from './DetailPanel.module.less'; import css from './DetailPanel.module.less';
import ProductAllSection from './ProductAllSection/ProductAllSection'; import ProductAllSection from './ProductAllSection/ProductAllSection';
import ThemeItemListOverlay from './ThemeItemListOverlay/ThemeItemListOverlay'; import ThemeItemListOverlay from './ThemeItemListOverlay/ThemeItemListOverlay';
// ======= // =======
// changeAppStatus, // changeAppStatus,
// changeLocalSettings, // changeLocalSettings,
@@ -929,12 +951,12 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
} }
}, [themeData, selectedIndex]); }, [themeData, selectedIndex]);
// 타이틀과 aria-label 메모이제이션 (성능 최적화) // 타이틀과 aria-label 메모이제이션 (성능 최적화 // themeTitle과 haederTitle 분리.)
const headerTitle = useMemo( const headerTitle = useMemo(
() => () =>
fp.pipe( fp.pipe(
() => ({ panelPrdtId, productData, panelType, themeData }), () => ({ panelPrdtId, productData }),
({ panelPrdtId, productData, panelType, themeData }) => { ({ panelPrdtId, productData }) => {
const productTitle = fp.pipe( const productTitle = fp.pipe(
() => ({ panelPrdtId, productData }), () => ({ panelPrdtId, productData }),
({ panelPrdtId, productData }) => ({ panelPrdtId, productData }) =>
@@ -943,7 +965,17 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
? fp.pipe(() => productData, fp.get('prdtNm'))() ? fp.pipe(() => productData, fp.get('prdtNm'))()
: null : null
)(); )();
return productTitle || '';
}
)(),
[panelPrdtId, productData]
);
const themeHeaderTitle = useMemo(
() =>
fp.pipe(
() => ({ panelType, themeData }),
({ panelType, themeData }) => {
const themeTitle = fp.pipe( const themeTitle = fp.pipe(
() => ({ panelType, themeData }), () => ({ panelType, themeData }),
({ panelType, themeData }) => ({ panelType, themeData }) =>
@@ -952,12 +984,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
: null : null
)(); )();
return productTitle || themeTitle || ''; return themeTitle || '';
} }
)(), )(),
[panelPrdtId, productData, panelType, themeData] [panelType, themeData]
); );
const ariaLabel = useMemo( const ariaLabel = useMemo(
() => () =>
fp.pipe( fp.pipe(
@@ -1071,6 +1105,9 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
className={css.header} className={css.header}
prdtId={productData?.prdtId} prdtId={productData?.prdtId}
title={headerTitle} title={headerTitle}
themeTitle={themeHeaderTitle}
selectedIndex={selectedIndex}
type={panelInfo?.type === "theme" ? "theme" : null}
onBackButton onBackButton
onClick={onBackClick(false)} onClick={onBackClick(false)}
onBackButtonFocus={onBackButtonFocus} onBackButtonFocus={onBackButtonFocus}
@@ -1079,8 +1116,9 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
onSpotlightLeft={onSpotlightUpTButton} onSpotlightLeft={onSpotlightUpTButton}
marqueeDisabled={false} marqueeDisabled={false}
ariaLabel={ariaLabel} ariaLabel={ariaLabel}
logoImg={productData?.patncLogoPath} logoImg={productData?.patncLogoPath ? productData?.patncLogoPath : themeData?.productInfos[0]?.patncLogoPath}
patnrId={panelPatnrId} patnrId={panelPatnrId}
themeData={themeData}
/> />
<TBody <TBody
className={css.tbody} className={css.tbody}

View File

@@ -1544,7 +1544,10 @@ export default function ProductAllSection({
> >
<div className={css.qrWrapper}> <div className={css.qrWrapper}>
{isShowQRCode ? ( {isShowQRCode ? (
<QRCode productInfo={productData} productType={productType} kind={'detail'} /> <>
{/* <QRCode productInfo={productData} productType={productType} kind={'detail'} /> */}
<QRCode productInfo={productData} productType={productType} />
</>
) : ( ) : (
<div className={css.qrRollingWrap}> <div className={css.qrRollingWrap}>
<div className={css.innerText}> <div className={css.innerText}>

View File

@@ -321,12 +321,12 @@
.qrcode { .qrcode {
> div:first-child { > div:first-child {
// 명시적으로 크기 고정 및 오버플로우 처리 // 명시적으로 크기 고정 및 오버플로우 처리
width: 240px !important; width: 190px !important;
height: 240px !important; height: 190px !important;
max-width: 240px !important; max-width: 190px !important;
max-height: 240px !important; max-height: 190px !important;
min-width: 240px !important; min-width: 190px !important;
min-height: 240px !important; min-height: 190px !important;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
@@ -346,8 +346,8 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
text-align: center; text-align: center;
width: 240px; width: 190px;
height: 240px; height: 190px;
background: #fff; background: #fff;
border: 1px solid #fff; border: 1px solid #fff;
.innerText { .innerText {
@@ -355,7 +355,7 @@
padding: 0 20px; padding: 0 20px;
h3 { h3 {
word-break: break-word; word-break: break-word;
font-size: 36px; font-size: 30px;
font-weight: bold; font-weight: bold;
color: @PRIMARY_COLOR_RED; color: @PRIMARY_COLOR_RED;
& + p { & + p {
@@ -363,7 +363,7 @@
} }
} }
p { p {
font-size: 24px; font-size: 18px;
font-weight: bold; font-weight: bold;
line-height: 1.17; line-height: 1.17;
color: @COLOR_GRAY05; color: @COLOR_GRAY05;

View File

@@ -11,13 +11,13 @@
} }
&.detailQrcode { &.detailQrcode {
> div:first-child { > div:first-child {
width: 240px; width: 190px;
height: 240px; height: 190px;
} }
} }
.tooltip { .tooltip {
margin-top: 10px; margin-top: 10px;
width: 240px; width: 190px;
height: 60px; height: 60px;
background: #000; background: #000;
color: #fff; color: #fff;
@@ -39,7 +39,7 @@
position: relative; position: relative;
width: 100%; width: 100%;
height: 44px; height: 44px;
font-size: 20px; font-size: 16px;
line-height: 22px; line-height: 22px;
letter-spacing: -1px; letter-spacing: -1px;
display: flex; display: flex;

View File

@@ -2,10 +2,12 @@
@import "../../../style/utils.module.less"; @import "../../../style/utils.module.less";
.container { .container {
margin-bottom: 10px;
// .size(@w:100%,@h:100%); // .size(@w:100%,@h:100%);
.size(@w:100%,@h:334px); .size(@w:100%,@h:370px);
.productInfoWrapper { .productInfoWrapper {
.flex(@justifyCenter:flex-start,@alignCenter:flex-start); .flex(@justifyCenter:flex-start,@alignCenter:flex-start);
flex-wrap: wrap;
// margin: 54px 0 10px 0; // margin: 54px 0 10px 0;
margin: 20px 0 10px 0; margin: 20px 0 10px 0;
// 고정 높이로 인해 QR 영역과 하단 버튼 영역 사이에 과도한 여백이 생김 // 고정 높이로 인해 QR 영역과 하단 버튼 영역 사이에 과도한 여백이 생김

View File

@@ -171,48 +171,53 @@ export default function ProductPriceDisplay({ productType, productInfo }) {
<> <>
{productType && productInfo && ( {productType && productInfo && (
/* <div> */ /* <div> */
<div style={{ margin: "0 10px 0 0", width: "380px" }}> <>
{/* shop by mobile (구매불가) 상품 price render */} <div className={css.productNm}>
{(productType === "shopByMobile" || isThemeShopByMobile) && ( {productInfo.prdtNm}
<ShopByMobilePriceDisplay
priceData={productInfo}
priceInfo={priceInfo}
isOriginalPriceEmpty={isOriginalPriceEmpty}
isDiscountedPriceEmpty={isDiscountedPriceEmpty}
isDiscounted={isDiscounted}
/>
)}
{/* buy now (결제 가능) 상품 price render */}
{(productType === "buyNow" || isThemeBuyNow) && (
<BuyNowPriceDisplay
priceData={productInfo}
priceInfo={priceInfo}
isOriginalPriceEmpty={isOriginalPriceEmpty}
isDiscountedPriceEmpty={isDiscountedPriceEmpty}
isDiscounted={isDiscounted}
/>
)}
<div className={css.enrgLbImgBox}>
{euEnrgLblInfos && (countryCode === "GB" || countryCode === "DE") && 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>
</div> <div style={{ margin: "0 10px 0 0", width: "380px" }}>
{/* shop by mobile (구매불가) 상품 price render */}
{(productType === "shopByMobile" || isThemeShopByMobile) && (
<ShopByMobilePriceDisplay
priceData={productInfo}
priceInfo={priceInfo}
isOriginalPriceEmpty={isOriginalPriceEmpty}
isDiscountedPriceEmpty={isDiscountedPriceEmpty}
isDiscounted={isDiscounted}
/>
)}
{/* buy now (결제 가능) 상품 price render */}
{(productType === "buyNow" || isThemeBuyNow) && (
<BuyNowPriceDisplay
priceData={productInfo}
priceInfo={priceInfo}
isOriginalPriceEmpty={isOriginalPriceEmpty}
isDiscountedPriceEmpty={isDiscountedPriceEmpty}
isDiscounted={isDiscounted}
/>
)}
<div className={css.enrgLbImgBox}>
{euEnrgLblInfos && (countryCode === "GB" || countryCode === "DE") && 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>
</>
)} )}
{(() => { {(() => {
// 팝업이 표시되어야 하는 조건 검증 // 팝업이 표시되어야 하는 조건 검증

View File

@@ -184,3 +184,13 @@
height: auto; height: auto;
object-fit: contain; // 비율 유지하면서 컨테이너에 맞춤 object-fit: contain; // 비율 유지하면서 컨테이너에 맞춤
} }
.productNm {
width: 100%;
font-weight: bold;
font-size: 36px;
color: @COLOR_WHITE;
flex:none;
.elip(2);
margin-bottom: 20px;
}

View File

@@ -123,14 +123,16 @@ export default function ShopByMobilePriceDisplay({
<span className={css.price}> <span className={css.price}>
{isDiscountedPriceEmpty ? offerInfo : discountedPrice} {isDiscountedPriceEmpty ? offerInfo : discountedPrice}
</span> </span>
{isDiscounted && (
<span className={css.discountedPrc}>
{originalPrice && isOriginalPriceEmpty
? offerInfo
: originalPrice}
</span>
)}
</div> </div>
{isDiscounted && (
<div className={css.btmLayer2}>
<span className={css.discountedPrc}>
{originalPrice && isOriginalPriceEmpty
? offerInfo
: originalPrice}
</span>
</div>
)}
</div> </div>
); );
} else if (TYPE_CASE.case3) { } else if (TYPE_CASE.case3) {
@@ -150,14 +152,17 @@ export default function ShopByMobilePriceDisplay({
<span className={css.price}> <span className={css.price}>
{isDiscountedPriceEmpty ? offerInfo : discountedPrice} {isDiscountedPriceEmpty ? offerInfo : discountedPrice}
</span> </span>
{isDiscounted && (
<span className={css.discountedPrc}>
{originalPrice && isOriginalPriceEmpty
? offerInfo
: originalPrice}
</span>
)}
</div> </div>
{isDiscounted && (
<div className={css.btmLayer2}>
<span className={css.discountedPrc}>
{originalPrice && isOriginalPriceEmpty
? offerInfo
: originalPrice}
</span>
</div>
)}
{/* 할부 */} {/* 할부 */}
</div> </div>
); );
@@ -172,7 +177,7 @@ export default function ShopByMobilePriceDisplay({
)} )}
<span className={css.name}>{$L("Shop Time Price")}</span> <span className={css.name}>{$L("Shop Time Price")}</span>
</div> </div>
<div className={css.btmLayer}> <div className={css.btmLayer2}>
<span className={css.price}>{discountedPrice}</span> <span className={css.price}>{discountedPrice}</span>
{discountedPrice !== originalPrice && ( {discountedPrice !== originalPrice && (
<span className={css.discountedPrc}> <span className={css.discountedPrc}>

View File

@@ -40,6 +40,11 @@
display: flex; display: flex;
align-items: center; align-items: center;
} }
.btmLayer2 {
margin: 5px 0;
display: flex;
align-items: center;
}
.price { .price {
font-weight: bold; font-weight: bold;
font-size: 52px; font-size: 52px;

View File

@@ -24,6 +24,9 @@ const SpottableComponent = Spottable("button");
export default function THeaderCustom({ export default function THeaderCustom({
prdtId, prdtId,
title, title,
type,
themeTitle,
selectedIndex,
className, className,
onBackButton, onBackButton,
onSpotlightUp, onSpotlightUp,
@@ -36,15 +39,18 @@ export default function THeaderCustom({
kind, kind,
logoImg, logoImg,
patnrId, patnrId,
themeData,
...rest ...rest
}) { }) {
const convertedTitle = useMemo(() => { const convertedTitle = useMemo(() => {
if (title && typeof title === "string") { if (title && typeof title === "string") {
const cleanedTitle = title.replace(/(\r\n|\n)/g, ""); const cleanedTitle = title.replace(/(\r\n|\n)/g, "");
return $L(marqueeDisabled ? title : cleanedTitle); return $L(marqueeDisabled ? title : cleanedTitle);
} else if(type === "theme") {
return themeData?.productInfos[selectedIndex].prdtNm;
} }
return ""; return "";
}, [marqueeDisabled, title]); }, [marqueeDisabled, title, selectedIndex, themeData, type]);
const _onClick = useCallback( const _onClick = useCallback(
(e) => { (e) => {
@@ -87,6 +93,9 @@ export default function THeaderCustom({
role="button" role="button"
/> />
)} )}
{type === "theme" && themeTitle && (
<span className={css.themeTitle} dangerouslySetInnerHTML={{ __html: themeTitle }} />
)}
{kind ? ( {kind ? (
"" ""
) : ( ) : (

View File

@@ -54,3 +54,11 @@
margin-right: 10px; // 파트너사 로고 후 10px gap margin-right: 10px; // 파트너사 로고 후 10px gap
border-radius: 100%; border-radius: 100%;
} }
.themeTitle {
font-size: 25px;
font-weight: 600;
color: #eaeaea;
width: max-content;
margin-right: 20px;
}