[상품상세] 스타일 수정
1. buy now, add to cart 버튼 크기문제 수정요청. (수정완료) 2. description, 배송문구 부분 포커스 시에도 product detail로 포커스 표시 요청.(수정완료) 3. Review Section "+ View More" 버튼 클릭이후 노출되는 이미지 팝업 리뷰 부분 이미지 수정요청.(수정완료) 4. user review 팝업에서 노출되는 세줄 아이콘 제거 요청.(수정완료) 5. review 팝업에서 컨텐츠 박스 이상 노출되는 텍스트 처리요청.(히든처리) 6. review 패널 오픈시에 필터 stars에서 s 제거요청.(수정완료) 7. review 패널 오픈시에 필터 margin값 변경요청.(변경 완료) 8. 가격과 할인값이 둘다 1000불 이상일때 가격노출부분 텍스트 크기 수정요청.(60->52px) 9. qr code 노출부분에서 시간이 지날시 텍스트 이미지 노출요청.(노출 처리) 10. 상단 타이틀 marquee변경 요청(처리완료) 11. 타이틀 상품명에 상품아이디 노출요청.(처리는 했지만 노출부분 확인필요) 12. shop by mobile 눌렀을때 노출되는 화면에서 디자인 맞추기.(디자인 맞춰둠)
This commit is contained in:
@@ -5,16 +5,16 @@
|
|||||||
.titleHead {
|
.titleHead {
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
background: #E7EBEF;
|
background: #e7ebef;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start; // center → flex-start
|
justify-content: flex-start; // center → flex-start
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.titleHead__text {
|
.titleHead__text {
|
||||||
text-align: left; // center → left
|
text-align: left; // center → left
|
||||||
color: black;
|
color: black;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-family: 'LG Smart UI';
|
font-family: "LG Smart UI";
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 42px;
|
line-height: 42px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
@@ -25,18 +25,19 @@
|
|||||||
.headerTopRow {
|
.headerTopRow {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 5px; // TV 호환: gap 대신 margin 사용
|
margin-bottom: 5px; // TV 호환: gap 대신 margin 사용
|
||||||
|
|
||||||
.headerTopRow__brandLogo {
|
.headerTopRow__brandLogo {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
margin-right: 15px; // TV 호환: gap 대신 margin 사용
|
margin-right: 15px; // TV 호환: gap 대신 margin 사용
|
||||||
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headerTopRow__productId {
|
.headerTopRow__productId {
|
||||||
color: #808080;
|
color: #808080;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-family: 'LG Smart UI';
|
font-family: "LG Smart UI";
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
@@ -53,7 +54,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
background-color: @COLOR_SKYBLUE;
|
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -62,18 +63,24 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
> .container__header__textBox {
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-left: 15px;
|
||||||
|
width: 885px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
> .container__header__textBoxOnly {
|
> .container__header__textBoxOnly {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .container__header__productImg {
|
> .container__header__productImg {
|
||||||
margin: 30px 0px 0 30px; // 60px → 30px로 변경
|
margin: 30px 0px 0 30px; // 60px → 30px로 변경
|
||||||
|
|
||||||
> .container__header__productImg__img {
|
> .container__header__productImg__img {
|
||||||
.border-solid(@size:1px,@color:#cccccc);
|
.border-solid(@size:1px,@color:#cccccc);
|
||||||
width: 100px; // 132px → 100px (Figma 크기)
|
width: 100px; // 132px → 100px (Figma 크기)
|
||||||
height: 100px; // 132px → 100px (Figma 크기)
|
height: 100px; // 132px → 100px (Figma 크기)
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +100,7 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: @COLOR_GRAY06;
|
color: @COLOR_GRAY06;
|
||||||
padding: 10px 30px 0 30px; // 60px → 30px로 변경
|
padding: 10px 30px 0 0px; // 60px → 30px로 변경
|
||||||
text-align: left;
|
text-align: left;
|
||||||
.elip(@clamp:1);
|
.elip(@clamp:1);
|
||||||
}
|
}
|
||||||
@@ -101,10 +108,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 60px;
|
line-height: 60px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 10px 30px 10px 30px; // 60px → 30px로 변경
|
padding: 10px 30px 10px 30px; // 60px → 30px로 변경
|
||||||
}
|
}
|
||||||
.container__header__price {
|
.container__header__price {
|
||||||
padding: 0 30px 20px 30px; // 60px → 30px로 변경
|
padding: 0 30px 20px 30px; // 60px → 30px로 변경
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: @PRIMARY_COLOR_RED;
|
color: @PRIMARY_COLOR_RED;
|
||||||
@@ -152,7 +159,7 @@
|
|||||||
width: 345.5px; // 고정 너비
|
width: 345.5px; // 고정 너비
|
||||||
height: 412px; // PhoneInputSection과 동일한 높이
|
height: 412px; // PhoneInputSection과 동일한 높이
|
||||||
padding: 10px 30px 0 30px; // 위 10px, 좌우 30px, 아래 0
|
padding: 10px 30px 0 30px; // 위 10px, 좌우 30px, 아래 0
|
||||||
background: #F8F8F8;
|
background: #f8f8f8;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@@ -164,7 +171,7 @@
|
|||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
color: #808080;
|
color: #808080;
|
||||||
font-size: 20px; // 피그마 스펙대로 복원
|
font-size: 20px; // 피그마 스펙대로 복원
|
||||||
font-family: 'LG Smart UI';
|
font-family: "LG Smart UI";
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 24px; // 피그마 스펙대로 복원
|
line-height: 24px; // 피그마 스펙대로 복원
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
|||||||
@@ -66,6 +66,10 @@
|
|||||||
&.full {
|
&.full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
&.detail_small {
|
||||||
|
min-width: 312px;
|
||||||
|
max-width: 312px;
|
||||||
|
}
|
||||||
&.basic {
|
&.basic {
|
||||||
&.disabled {
|
&.disabled {
|
||||||
background-color: #7a808d;
|
background-color: #7a808d;
|
||||||
|
|||||||
@@ -629,7 +629,13 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
({ panelPrdtId, productData }) =>
|
({ panelPrdtId, productData }) =>
|
||||||
fp.isNotNil(panelPrdtId) &&
|
fp.isNotNil(panelPrdtId) &&
|
||||||
fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)()
|
fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)()
|
||||||
? fp.pipe(() => productData, fp.get("prdtNm"))()
|
? // ? fp.pipe(() => productData, fp.get("prdtNm"))()
|
||||||
|
// `${fp.get("prdtId")(productData)} ${fp.get("prdtNm")(productData)}`
|
||||||
|
fp.pipe(
|
||||||
|
() => productData,
|
||||||
|
(data) =>
|
||||||
|
`${fp.get("prdtId")(data)} ${fp.get("prdtNm")(data)}`
|
||||||
|
)()
|
||||||
: null
|
: null
|
||||||
)();
|
)();
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
z-index: 1; // 배경 컴포넌트(z-index: 0) 위에 표시
|
z-index: 1; // 배경 컴포넌트(z-index: 0) 위에 표시
|
||||||
background: transparent !important; // 투명 배경으로 설정하여 뒤의 배경 컴포넌트가 보이도록
|
background: transparent !important; // 투명 배경으로 설정하여 뒤의 배경 컴포넌트가 보이도록
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
// 하위 요소들도 투명 배경 (detailPanelWrap 스코프 내에서만 적용)
|
// 하위 요소들도 투명 배경 (detailPanelWrap 스코프 내에서만 적용)
|
||||||
> * {
|
> * {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import { PropTypes } from 'prop-types';
|
import { PropTypes } from 'prop-types';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
/* eslint-disable react/jsx-no-bind */
|
/* eslint-disable react/jsx-no-bind */
|
||||||
// src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
|
// src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
|
||||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
import SpotlightContainerDecorator
|
||||||
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
//image
|
//image
|
||||||
import arrowDown from '../../../../assets/images/icons/ic_arrow_down_3x_new.png';
|
import arrowDown
|
||||||
|
from '../../../../assets/images/icons/ic_arrow_down_3x_new.png';
|
||||||
|
import indicatorDefaultImage
|
||||||
|
from '../../../../assets/images/img-thumb-empty-144@3x.png';
|
||||||
import { pushPanel } from '../../../actions/panelActions';
|
import { pushPanel } from '../../../actions/panelActions';
|
||||||
import { resetShowAllReviews } from '../../../actions/productActions';
|
import { resetShowAllReviews } from '../../../actions/productActions';
|
||||||
import { showToast } from '../../../actions/toastActions';
|
import { showToast } from '../../../actions/toastActions';
|
||||||
@@ -46,13 +59,16 @@ import ProductTag from '../components/ProductTag';
|
|||||||
import StarRating from '../components/StarRating';
|
import StarRating from '../components/StarRating';
|
||||||
// ProductContentSection imports
|
// ProductContentSection imports
|
||||||
import TScrollerDetail from '../components/TScroller/TScrollerDetail';
|
import TScrollerDetail from '../components/TScroller/TScrollerDetail';
|
||||||
import ProductDescription from '../ProductContentSection/ProductDescription/ProductDescription';
|
import ProductDescription
|
||||||
import ProductDetail from '../ProductContentSection/ProductDetail/ProductDetail.new';
|
from '../ProductContentSection/ProductDescription/ProductDescription';
|
||||||
import UserReviews from '../ProductContentSection/UserReviews/UserReviews';
|
import ProductDetail
|
||||||
import ViewAllReviewsButton from '../ProductContentSection/UserReviews/ViewAllReviewsButton';
|
from '../ProductContentSection/ProductDetail/ProductDetail.new';
|
||||||
import YouMayAlsoLike from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
|
|
||||||
import ProductVideo from '../ProductContentSection/ProductVideo/ProductVideo';
|
import ProductVideo from '../ProductContentSection/ProductVideo/ProductVideo';
|
||||||
import indicatorDefaultImage from '../../../../assets/images/img-thumb-empty-144@3x.png';
|
import UserReviews from '../ProductContentSection/UserReviews/UserReviews';
|
||||||
|
import ViewAllReviewsButton
|
||||||
|
from '../ProductContentSection/UserReviews/ViewAllReviewsButton';
|
||||||
|
import YouMayAlsoLike
|
||||||
|
from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
|
||||||
import QRCode from '../ProductInfoSection/QRCode/QRCode';
|
import QRCode from '../ProductInfoSection/QRCode/QRCode';
|
||||||
import ProductOverview from '../ProductOverview/ProductOverview';
|
import ProductOverview from '../ProductOverview/ProductOverview';
|
||||||
// CSS imports
|
// CSS imports
|
||||||
@@ -62,42 +78,45 @@ import css from './ProductAllSection.module.less';
|
|||||||
|
|
||||||
const Container = SpotlightContainerDecorator(
|
const Container = SpotlightContainerDecorator(
|
||||||
{
|
{
|
||||||
enterTo: 'last-focused',
|
enterTo: "last-focused",
|
||||||
preserveld: true,
|
preserveld: true,
|
||||||
leaveFor: { right: 'content-scroller-container' },
|
leaveFor: { right: "content-scroller-container" },
|
||||||
spotlightDirection: 'vertical',
|
spotlightDirection: "vertical",
|
||||||
},
|
},
|
||||||
'div'
|
"div"
|
||||||
);
|
);
|
||||||
|
|
||||||
const ContentContainer = SpotlightContainerDecorator(
|
const ContentContainer = SpotlightContainerDecorator(
|
||||||
{
|
{
|
||||||
enterTo: 'default-element',
|
enterTo: "default-element",
|
||||||
preserveld: true,
|
preserveld: true,
|
||||||
leaveFor: {
|
leaveFor: {
|
||||||
left: 'spotlight-product-info-section-container',
|
left: "spotlight-product-info-section-container",
|
||||||
},
|
},
|
||||||
restrict: 'none',
|
restrict: "none",
|
||||||
spotlightDirection: 'vertical',
|
spotlightDirection: "vertical",
|
||||||
},
|
},
|
||||||
'div'
|
"div"
|
||||||
);
|
);
|
||||||
|
|
||||||
const HorizontalContainer = SpotlightContainerDecorator(
|
const HorizontalContainer = SpotlightContainerDecorator(
|
||||||
{
|
{
|
||||||
enterTo: 'last-focused',
|
enterTo: "last-focused",
|
||||||
preserveld: true,
|
preserveld: true,
|
||||||
defaultElement: 'spotlight-product-info-section-container',
|
defaultElement: "spotlight-product-info-section-container",
|
||||||
spotlightDirection: 'horizontal',
|
spotlightDirection: "horizontal",
|
||||||
},
|
},
|
||||||
'div'
|
"div"
|
||||||
);
|
);
|
||||||
|
|
||||||
// FP: Pure function to determine product data based on typeP
|
// FP: Pure function to determine product data based on typeP
|
||||||
const getProductData = curry((productType, themeProductInfo, productInfo) =>
|
const getProductData = curry((productType, themeProductInfo, productInfo) =>
|
||||||
pipe(
|
pipe(
|
||||||
when(
|
when(
|
||||||
() => isVal(productType) && productType === 'theme' && isVal(themeProductInfo),
|
() =>
|
||||||
|
isVal(productType) &&
|
||||||
|
productType === "theme" &&
|
||||||
|
isVal(themeProductInfo),
|
||||||
() => themeProductInfo
|
() => themeProductInfo
|
||||||
),
|
),
|
||||||
defaultTo(productInfo),
|
defaultTo(productInfo),
|
||||||
@@ -113,17 +132,17 @@ const deriveFavoriteFlag = curry((favoriteOverride, productData) => {
|
|||||||
return favoriteOverride;
|
return favoriteOverride;
|
||||||
}
|
}
|
||||||
// 그렇지 않으면 productData의 favorYn 값을 사용 (기본값 'N')
|
// 그렇지 않으면 productData의 favorYn 값을 사용 (기본값 'N')
|
||||||
return pipe(get('favorYn'), defaultTo('N'))(productData);
|
return pipe(get("favorYn"), defaultTo("N"))(productData);
|
||||||
});
|
});
|
||||||
|
|
||||||
// FP: Pure function to extract review grade and order phone
|
// FP: Pure function to extract review grade and order phone
|
||||||
const extractProductMeta = (productInfo) => ({
|
const extractProductMeta = (productInfo) => ({
|
||||||
revwGrd: get('revwGrd', productInfo),
|
revwGrd: get("revwGrd", productInfo),
|
||||||
orderPhnNo: get('orderPhnNo', productInfo),
|
orderPhnNo: get("orderPhnNo", productInfo),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경
|
// 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경
|
||||||
const SpottableComponent = Spottable('div');
|
const SpottableComponent = Spottable("div");
|
||||||
|
|
||||||
const LayoutSample = ({ onClick }) => (
|
const LayoutSample = ({ onClick }) => (
|
||||||
<SpottableComponent
|
<SpottableComponent
|
||||||
@@ -152,7 +171,9 @@ export default function ProductAllSection({
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
// Redux 상태
|
// Redux 상태
|
||||||
const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion);
|
const webOSVersion = useSelector(
|
||||||
|
(state) => state.common.appStatus.webOSVersion
|
||||||
|
);
|
||||||
const groupInfos = useSelector((state) => state.product.groupInfo);
|
const groupInfos = useSelector((state) => state.product.groupInfo);
|
||||||
|
|
||||||
// YouMayLike 데이터는 API 응답 시간이 걸리므로 직접 구독
|
// YouMayLike 데이터는 API 응답 시간이 걸리므로 직접 구독
|
||||||
@@ -162,6 +183,33 @@ export default function ProductAllSection({
|
|||||||
//하단부분까지 갔을때 체크용
|
//하단부분까지 갔을때 체크용
|
||||||
const [documentHeight, setDocumentHeight] = useState(0);
|
const [documentHeight, setDocumentHeight] = useState(0);
|
||||||
const [isBottom, setIsBottom] = useState(false);
|
const [isBottom, setIsBottom] = useState(false);
|
||||||
|
|
||||||
|
//qr코드 노출용
|
||||||
|
const [isShowQRCode, setIsShowQRCode] = useState(true);
|
||||||
|
const timerRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const toggleQRCode = () => {
|
||||||
|
if (isShowQRCode) {
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
setIsShowQRCode(false);
|
||||||
|
}, 10000);
|
||||||
|
} else {
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
setIsShowQRCode(true);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleQRCode();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isShowQRCode]);
|
||||||
|
|
||||||
//버튼 active 표시용
|
//버튼 active 표시용
|
||||||
|
|
||||||
const [activeProductBtn, setActiveProductBtn] = useState(false);
|
const [activeProductBtn, setActiveProductBtn] = useState(false);
|
||||||
@@ -176,20 +224,20 @@ export default function ProductAllSection({
|
|||||||
// 단품(결제 가능 상품) - DetailPanel.backup.jsx와 동일한 로직
|
// 단품(결제 가능 상품) - DetailPanel.backup.jsx와 동일한 로직
|
||||||
const isBillingProductVisible = useMemo(() => {
|
const isBillingProductVisible = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
productData?.pmtSuptYn === 'Y' &&
|
productData?.pmtSuptYn === "Y" &&
|
||||||
productData?.grPrdtProcYn === 'N' &&
|
productData?.grPrdtProcYn === "N" &&
|
||||||
panelInfo?.prdtId &&
|
panelInfo?.prdtId &&
|
||||||
webOSVersion >= '6.0'
|
webOSVersion >= "6.0"
|
||||||
);
|
);
|
||||||
}, [productData, webOSVersion, panelInfo?.prdtId]);
|
}, [productData, webOSVersion, panelInfo?.prdtId]);
|
||||||
|
|
||||||
// 구매 불가 상품 - DetailPanel.backup.jsx와 동일한 로직
|
// 구매 불가 상품 - DetailPanel.backup.jsx와 동일한 로직
|
||||||
const isUnavailableProductVisible = useMemo(() => {
|
const isUnavailableProductVisible = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
productData?.pmtSuptYn === 'N' ||
|
productData?.pmtSuptYn === "N" ||
|
||||||
(productData?.pmtSuptYn === 'Y' &&
|
(productData?.pmtSuptYn === "Y" &&
|
||||||
productData?.grPrdtProcYn === 'N' &&
|
productData?.grPrdtProcYn === "N" &&
|
||||||
webOSVersion < '6.0' &&
|
webOSVersion < "6.0" &&
|
||||||
panelInfo?.prdtId)
|
panelInfo?.prdtId)
|
||||||
);
|
);
|
||||||
}, [productData, webOSVersion, panelInfo?.prdtId]);
|
}, [productData, webOSVersion, panelInfo?.prdtId]);
|
||||||
@@ -197,8 +245,8 @@ export default function ProductAllSection({
|
|||||||
// 그룹 상품 - DetailPanel.backup.jsx와 동일한 로직
|
// 그룹 상품 - DetailPanel.backup.jsx와 동일한 로직
|
||||||
const isGroupProductVisible = useMemo(() => {
|
const isGroupProductVisible = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
productData?.pmtSuptYn === 'Y' &&
|
productData?.pmtSuptYn === "Y" &&
|
||||||
productData?.grPrdtProcYn === 'Y' &&
|
productData?.grPrdtProcYn === "Y" &&
|
||||||
groupInfos &&
|
groupInfos &&
|
||||||
groupInfos.length > 0
|
groupInfos.length > 0
|
||||||
);
|
);
|
||||||
@@ -206,7 +254,10 @@ export default function ProductAllSection({
|
|||||||
|
|
||||||
// 여행/테마 상품 - DetailPanel.backup.jsx와 동일한 로직
|
// 여행/테마 상품 - DetailPanel.backup.jsx와 동일한 로직
|
||||||
const isTravelProductVisible = useMemo(() => {
|
const isTravelProductVisible = useMemo(() => {
|
||||||
return panelInfo?.curationId && (panelInfo?.type === 'theme' || panelInfo?.type === 'hotel');
|
return (
|
||||||
|
panelInfo?.curationId &&
|
||||||
|
(panelInfo?.type === "theme" || panelInfo?.type === "hotel")
|
||||||
|
);
|
||||||
}, [panelInfo]);
|
}, [panelInfo]);
|
||||||
|
|
||||||
// useReviews Hook 사용 - 모든 리뷰 관련 로직을 담당
|
// useReviews Hook 사용 - 모든 리뷰 관련 로직을 담당
|
||||||
@@ -232,7 +283,9 @@ export default function ProductAllSection({
|
|||||||
|
|
||||||
// ProductAllSection 마운트 시 showAllReviews 초기화
|
// ProductAllSection 마운트 시 showAllReviews 초기화
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('[ProductAllSection] Component mounted - resetting showAllReviews to false');
|
console.log(
|
||||||
|
"[ProductAllSection] Component mounted - resetting showAllReviews to false"
|
||||||
|
);
|
||||||
dispatch(resetShowAllReviews());
|
dispatch(resetShowAllReviews());
|
||||||
}, []); // 빈 dependency array = 마운트 시에만 실행
|
}, []); // 빈 dependency array = 마운트 시에만 실행
|
||||||
|
|
||||||
@@ -254,9 +307,9 @@ export default function ProductAllSection({
|
|||||||
(productData.imgUrls600 && productData.imgUrls600[0]) ||
|
(productData.imgUrls600 && productData.imgUrls600[0]) ||
|
||||||
(productData.imgUrls && productData.imgUrls[0]) ||
|
(productData.imgUrls && productData.imgUrls[0]) ||
|
||||||
productData.thumbnailUrl ||
|
productData.thumbnailUrl ||
|
||||||
'https://placehold.co/150x150',
|
"https://placehold.co/150x150",
|
||||||
brandLogo: productData.patncLogoPath || 'https://placehold.co/50x50',
|
brandLogo: productData.patncLogoPath || "https://placehold.co/50x50",
|
||||||
productName: productData.prdtNm || '상품명 정보가 없습니다',
|
productName: productData.prdtNm || "상품명 정보가 없습니다",
|
||||||
avgRating: stats.averageRating || 5,
|
avgRating: stats.averageRating || 5,
|
||||||
reviewCount: stats.totalReviews || 0,
|
reviewCount: stats.totalReviews || 0,
|
||||||
},
|
},
|
||||||
@@ -266,32 +319,33 @@ export default function ProductAllSection({
|
|||||||
|
|
||||||
// BUY NOW 버튼 클릭 핸들러 - Toast로 BuyOption 표시
|
// BUY NOW 버튼 클릭 핸들러 - Toast로 BuyOption 표시
|
||||||
const handleBuyNowClick = useCallback(() => {
|
const handleBuyNowClick = useCallback(() => {
|
||||||
console.log('[BuyNow] Buy Now button clicked');
|
console.log("[BuyNow] Buy Now button clicked");
|
||||||
dispatch(
|
dispatch(
|
||||||
showToast({
|
showToast({
|
||||||
message: '',
|
message: "",
|
||||||
type: 'buyOption',
|
type: "buyOption",
|
||||||
duration: 0,
|
duration: 0,
|
||||||
position: 'bottom-center',
|
position: "bottom-center",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
// ADD TO CART 버튼 클릭 핸들러
|
// ADD TO CART 버튼 클릭 핸들러
|
||||||
const handleAddToCartClick = useCallback(() => {
|
const handleAddToCartClick = useCallback(() => {
|
||||||
console.log('[AddToCart] Add To Cart button clicked');
|
console.log("[AddToCart] Add To Cart button clicked");
|
||||||
// TODO: 장바구니 추가 로직 구현
|
// TODO: 장바구니 추가 로직 구현
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 디버깅: 실제 이미지 및 동영상 데이터 확인
|
// 디버깅: 실제 이미지 및 동영상 데이터 확인
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('[ProductId] ProductAllSection productData check:', {
|
console.log("[ProductId] ProductAllSection productData check:", {
|
||||||
hasProductData: !!productData,
|
hasProductData: !!productData,
|
||||||
productDataPrdtId: productData && productData.prdtId,
|
productDataPrdtId: productData && productData.prdtId,
|
||||||
imgUrls600: productData && productData.imgUrls600,
|
imgUrls600: productData && productData.imgUrls600,
|
||||||
imgUrls600Length: productData && productData.imgUrls600 && productData.imgUrls600.length,
|
imgUrls600Length:
|
||||||
|
productData && productData.imgUrls600 && productData.imgUrls600.length,
|
||||||
imgUrls600Type: Array.isArray(productData && productData.imgUrls600)
|
imgUrls600Type: Array.isArray(productData && productData.imgUrls600)
|
||||||
? 'array'
|
? "array"
|
||||||
: typeof (productData && productData.imgUrls600),
|
: typeof (productData && productData.imgUrls600),
|
||||||
// 동영상 관련 정보 추가
|
// 동영상 관련 정보 추가
|
||||||
prdtMediaUrl: productData && productData.prdtMediaUrl,
|
prdtMediaUrl: productData && productData.prdtMediaUrl,
|
||||||
@@ -303,7 +357,10 @@ export default function ProductAllSection({
|
|||||||
});
|
});
|
||||||
}, [productData, renderItems]);
|
}, [productData, renderItems]);
|
||||||
|
|
||||||
const { revwGrd, orderPhnNo } = useMemo(() => extractProductMeta(productInfo), [productInfo]);
|
const { revwGrd, orderPhnNo } = useMemo(
|
||||||
|
() => extractProductMeta(productInfo),
|
||||||
|
[productInfo]
|
||||||
|
);
|
||||||
|
|
||||||
// FP: derive favorite flag from props with local override, avoid non-I/O useEffect
|
// FP: derive favorite flag from props with local override, avoid non-I/O useEffect
|
||||||
const [favoriteOverride, setFavoriteOverride] = useState(null);
|
const [favoriteOverride, setFavoriteOverride] = useState(null);
|
||||||
@@ -326,7 +383,7 @@ export default function ProductAllSection({
|
|||||||
|
|
||||||
// User Reviews 스크롤 핸들러 추가
|
// User Reviews 스크롤 핸들러 추가
|
||||||
const handleUserReviewsClick = useCallback(
|
const handleUserReviewsClick = useCallback(
|
||||||
() => scrollToSection('scroll-marker-user-reviews'),
|
() => scrollToSection("scroll-marker-user-reviews"),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -343,7 +400,7 @@ export default function ProductAllSection({
|
|||||||
// 동영상이 있으면 첫 번째에 추가 (Indicator.jsx와 동일한 로직)
|
// 동영상이 있으면 첫 번째에 추가 (Indicator.jsx와 동일한 로직)
|
||||||
if (productData && productData.prdtMediaUrl) {
|
if (productData && productData.prdtMediaUrl) {
|
||||||
items.push({
|
items.push({
|
||||||
type: 'video',
|
type: "video",
|
||||||
url: productData.prdtMediaUrl,
|
url: productData.prdtMediaUrl,
|
||||||
thumbnail: productData.thumbnailUrl960 || indicatorDefaultImage,
|
thumbnail: productData.thumbnailUrl960 || indicatorDefaultImage,
|
||||||
index: 0,
|
index: 0,
|
||||||
@@ -351,12 +408,17 @@ export default function ProductAllSection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 이미지들 추가
|
// 이미지들 추가
|
||||||
if (productData && productData.imgUrls600 && productData.imgUrls600.length > 0) {
|
if (
|
||||||
|
productData &&
|
||||||
|
productData.imgUrls600 &&
|
||||||
|
productData.imgUrls600.length > 0
|
||||||
|
) {
|
||||||
productData.imgUrls600.forEach((image, imgIndex) => {
|
productData.imgUrls600.forEach((image, imgIndex) => {
|
||||||
items.push({
|
items.push({
|
||||||
type: 'image',
|
type: "image",
|
||||||
url: image,
|
url: image,
|
||||||
index: productData && productData.prdtMediaUrl ? imgIndex + 1 : imgIndex,
|
index:
|
||||||
|
productData && productData.prdtMediaUrl ? imgIndex + 1 : imgIndex,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -375,7 +437,7 @@ export default function ProductAllSection({
|
|||||||
// FP: Pure function for focus navigation to back button
|
// FP: Pure function for focus navigation to back button
|
||||||
const handleSpotlightUpToBackButton = useCallback((e) => {
|
const handleSpotlightUpToBackButton = useCallback((e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
Spotlight.focus('spotlightId_backBtn');
|
Spotlight.focus("spotlightId_backBtn");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// FP: Pure function for favorite flag change
|
// FP: Pure function for favorite flag change
|
||||||
@@ -388,7 +450,7 @@ export default function ProductAllSection({
|
|||||||
const handleThemeItemButtonClick = useCallback(
|
const handleThemeItemButtonClick = useCallback(
|
||||||
pipe(
|
pipe(
|
||||||
() => setOpenThemeItemOverlay(true),
|
() => setOpenThemeItemOverlay(true),
|
||||||
tap(() => setTimeout(() => Spotlight.focus('theme-close-button'), 0))
|
tap(() => setTimeout(() => Spotlight.focus("theme-close-button"), 0))
|
||||||
),
|
),
|
||||||
[setOpenThemeItemOverlay]
|
[setOpenThemeItemOverlay]
|
||||||
);
|
);
|
||||||
@@ -409,12 +471,12 @@ export default function ProductAllSection({
|
|||||||
|
|
||||||
// FP: Curried scroll handlers
|
// FP: Curried scroll handlers
|
||||||
const handleProductDetailsClick = useCallback(
|
const handleProductDetailsClick = useCallback(
|
||||||
() => scrollToSection('scroll-marker-product-details'),
|
() => scrollToSection("scroll-marker-product-details"),
|
||||||
[scrollToSection]
|
[scrollToSection]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleYouMayAlsoLikeClick = useCallback(
|
const handleYouMayAlsoLikeClick = useCallback(
|
||||||
() => scrollToSection('scroll-marker-you-may-also-like'),
|
() => scrollToSection("scroll-marker-you-may-also-like"),
|
||||||
[scrollToSection]
|
[scrollToSection]
|
||||||
);
|
);
|
||||||
const scrollPositionRef = useRef(0);
|
const scrollPositionRef = useRef(0);
|
||||||
@@ -484,7 +546,12 @@ export default function ProductAllSection({
|
|||||||
(descriptionRef.current?.scrollHeight || 0) +
|
(descriptionRef.current?.scrollHeight || 0) +
|
||||||
(reviewRef.current?.scrollHeight || 0)
|
(reviewRef.current?.scrollHeight || 0)
|
||||||
);
|
);
|
||||||
}, [productDetailRef.current, descriptionRef.current, hasReviews, hasYouMayAlsoLike]);
|
}, [
|
||||||
|
productDetailRef.current,
|
||||||
|
descriptionRef.current,
|
||||||
|
hasReviews,
|
||||||
|
hasYouMayAlsoLike,
|
||||||
|
]);
|
||||||
|
|
||||||
//spot관련
|
//spot관련
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -508,10 +575,10 @@ export default function ProductAllSection({
|
|||||||
<div className={css.leftInfoWrapper}>
|
<div className={css.leftInfoWrapper}>
|
||||||
<div className={css.headerContent}>
|
<div className={css.headerContent}>
|
||||||
<ProductTag productInfo={productData} />
|
<ProductTag productInfo={productData} />
|
||||||
{revwGrd && revwGrd !== '0.0' && (
|
{revwGrd && revwGrd !== "0.0" && (
|
||||||
<StarRating
|
<StarRating
|
||||||
rating={revwGrd}
|
rating={revwGrd}
|
||||||
aria-label={'star rating ' + revwGrd + ' out of 5'}
|
aria-label={"star rating " + revwGrd + " out of 5"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -525,11 +592,24 @@ export default function ProductAllSection({
|
|||||||
productType={productType}
|
productType={productType}
|
||||||
>
|
>
|
||||||
<div className={css.qrWrapper}>
|
<div className={css.qrWrapper}>
|
||||||
<QRCode productInfo={productData} productType={productType} kind={'detail'} />
|
{isShowQRCode ? (
|
||||||
|
<QRCode
|
||||||
|
productInfo={productData}
|
||||||
|
productType={productType}
|
||||||
|
kind={"detail"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={css.qrRollingWrap}>
|
||||||
|
<div className={css.innerText}>
|
||||||
|
<h3>{$L("Scan QR")}</h3>
|
||||||
|
<p>{$L("with your phone, Check Product")}</p>
|
||||||
|
<p>{$L("info & Purchase easily")}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ProductOverview>
|
</ProductOverview>
|
||||||
|
|
||||||
{/* BUY NOW + ADD TO CART 버튼들 (결제 가능 상품일 때만 렌더링) */}
|
|
||||||
{isBillingProductVisible && (
|
{isBillingProductVisible && (
|
||||||
<HorizontalContainer className={css.buyNowCartContainer}>
|
<HorizontalContainer className={css.buyNowCartContainer}>
|
||||||
<TButton
|
<TButton
|
||||||
@@ -537,28 +617,37 @@ export default function ProductAllSection({
|
|||||||
className={css.buyNowButton}
|
className={css.buyNowButton}
|
||||||
onClick={handleBuyNowClick}
|
onClick={handleBuyNowClick}
|
||||||
onSpotlightUp={handleSpotlightUpToBackButton}
|
onSpotlightUp={handleSpotlightUpToBackButton}
|
||||||
|
type="detail_small"
|
||||||
>
|
>
|
||||||
<div className={css.buyNowText}>{$L('BUY NOW')}</div>
|
<div className={css.buyNowText}>{$L("BUY NOW")}</div>
|
||||||
</TButton>
|
</TButton>
|
||||||
<TButton
|
<TButton
|
||||||
spotlightId="detail-add-to-cart-button"
|
spotlightId="detail-add-to-cart-button"
|
||||||
className={css.addToCartButton}
|
className={css.addToCartButton}
|
||||||
onClick={handleAddToCartClick}
|
onClick={handleAddToCartClick}
|
||||||
onSpotlightUp={handleSpotlightUpToBackButton}
|
onSpotlightUp={handleSpotlightUpToBackButton}
|
||||||
|
type="detail_small"
|
||||||
>
|
>
|
||||||
<div className={css.addToCartText}>{$L('ADD TO CART')}</div>
|
<div className={css.addToCartText}>{$L("ADD TO CART")}</div>
|
||||||
</TButton>
|
</TButton>
|
||||||
</HorizontalContainer>
|
</HorizontalContainer>
|
||||||
)}
|
)}
|
||||||
|
<Container
|
||||||
<Container className={css.buttonContainer}>
|
className={classNames(
|
||||||
|
css.buttonContainer,
|
||||||
|
isBillingProductVisible && css.buttonHasNoCart
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* BUY NOW + ADD TO CART 버튼들 (결제 가능 상품일 때만 렌더링) */}
|
||||||
<TButton
|
<TButton
|
||||||
spotlightId={SpotlightIds.DETAIL_SHOPBYMOBILE}
|
spotlightId={SpotlightIds.DETAIL_SHOPBYMOBILE}
|
||||||
className={css.shopByMobileButton}
|
className={css.shopByMobileButton}
|
||||||
onClick={handleShopByMobileOpen}
|
onClick={handleShopByMobileOpen}
|
||||||
onSpotlightUp={handleSpotlightUpToBackButton}
|
onSpotlightUp={handleSpotlightUpToBackButton}
|
||||||
>
|
>
|
||||||
<div className={css.shopByMobileText}>{$L('SHOP BY MOBILE')}</div>
|
<div className={css.shopByMobileText}>
|
||||||
|
{$L("SHOP BY MOBILE")}
|
||||||
|
</div>
|
||||||
</TButton>
|
</TButton>
|
||||||
{panelInfo && (
|
{panelInfo && (
|
||||||
<div className={css.favoriteBtnWrapper}>
|
<div className={css.favoriteBtnWrapper}>
|
||||||
@@ -568,7 +657,7 @@ export default function ProductAllSection({
|
|||||||
selectedPrdtId={panelInfo && panelInfo.prdtId}
|
selectedPrdtId={panelInfo && panelInfo.prdtId}
|
||||||
favoriteFlag={favoriteFlag}
|
favoriteFlag={favoriteFlag}
|
||||||
onFavoriteFlagChanged={onFavoriteFlagChanged}
|
onFavoriteFlagChanged={onFavoriteFlagChanged}
|
||||||
kind={'item_detail'}
|
kind={"item_detail"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -577,7 +666,9 @@ export default function ProductAllSection({
|
|||||||
<div className={css.callToOrderSection}>
|
<div className={css.callToOrderSection}>
|
||||||
{orderPhnNo && (
|
{orderPhnNo && (
|
||||||
<>
|
<>
|
||||||
<div className={css.callToOrderText}>{$L('Call to Order')}</div>
|
<div className={css.callToOrderText}>
|
||||||
|
{$L("Call to Order")}
|
||||||
|
</div>
|
||||||
<div className={css.phoneSection}>
|
<div className={css.phoneSection}>
|
||||||
<div className={css.phoneIconContainer}>
|
<div className={css.phoneIconContainer}>
|
||||||
<div className={css.phoneIcon} />
|
<div className={css.phoneIcon} />
|
||||||
@@ -595,31 +686,34 @@ export default function ProductAllSection({
|
|||||||
<TButton
|
<TButton
|
||||||
className={classNames(
|
className={classNames(
|
||||||
css.productDetailsButton,
|
css.productDetailsButton,
|
||||||
activeProductBtn ? css.active : ''
|
activeProductBtn ? css.active : ""
|
||||||
)}
|
)}
|
||||||
onClick={handleProductDetailsClick}
|
onClick={handleProductDetailsClick}
|
||||||
spotlightId="product-details-button"
|
spotlightId="product-details-button"
|
||||||
>
|
>
|
||||||
{$L('PRODUCT DETAILS')}
|
{$L("PRODUCT DETAILS")}
|
||||||
</TButton>
|
</TButton>
|
||||||
{hasReviews && (
|
{hasReviews && (
|
||||||
<TButton
|
<TButton
|
||||||
className={classNames(css.userReviewsButton, activeReviewBtn ? css.active : '')}
|
className={classNames(
|
||||||
|
css.userReviewsButton,
|
||||||
|
activeReviewBtn ? css.active : ""
|
||||||
|
)}
|
||||||
onClick={handleUserReviewsClick}
|
onClick={handleUserReviewsClick}
|
||||||
spotlightId="user-reviews-button"
|
spotlightId="user-reviews-button"
|
||||||
>
|
>
|
||||||
{$L('USER REVIEWS')} ({reviewTotalCount})
|
{$L("USER REVIEWS")} ({reviewTotalCount})
|
||||||
</TButton>
|
</TButton>
|
||||||
)}
|
)}
|
||||||
{hasYouMayAlsoLike && (
|
{hasYouMayAlsoLike && (
|
||||||
<TButton
|
<TButton
|
||||||
className={classNames(
|
className={classNames(
|
||||||
css.youMayLikeButton,
|
css.youMayLikeButton,
|
||||||
activeYouMayLikeBtn ? css.active : ''
|
activeYouMayLikeBtn ? css.active : ""
|
||||||
)}
|
)}
|
||||||
onClick={handleYouMayAlsoLikeClick}
|
onClick={handleYouMayAlsoLikeClick}
|
||||||
>
|
>
|
||||||
{$L('YOU MAY ALSO LIKE')}
|
{$L("YOU MAY ALSO LIKE")}
|
||||||
</TButton>
|
</TButton>
|
||||||
)}
|
)}
|
||||||
{/* YouMayLike 버튼 렌더링 상태 로그 */}
|
{/* YouMayLike 버튼 렌더링 상태 로그 */}
|
||||||
@@ -633,15 +727,18 @@ export default function ProductAllSection({
|
|||||||
})()} */}
|
})()} */}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
{panelInfo && panelInfo && panelInfo.type === 'theme' && !openThemeItemOverlay && (
|
{panelInfo &&
|
||||||
<TButton
|
panelInfo &&
|
||||||
className={css.themeButton}
|
panelInfo.type === "theme" &&
|
||||||
onClick={handleThemeItemButtonClick}
|
!openThemeItemOverlay && (
|
||||||
spotlightId="theme-open-button"
|
<TButton
|
||||||
>
|
className={css.themeButton}
|
||||||
{$L('THEME ITEM')}
|
onClick={handleThemeItemButtonClick}
|
||||||
</TButton>
|
spotlightId="theme-open-button"
|
||||||
)}
|
>
|
||||||
|
{$L("THEME ITEM")}
|
||||||
|
</TButton>
|
||||||
|
)}
|
||||||
|
|
||||||
<DetailMobileSendPopUp
|
<DetailMobileSendPopUp
|
||||||
ismobileSendPopupOpen={mobileSendPopupOpen}
|
ismobileSendPopupOpen={mobileSendPopupOpen}
|
||||||
@@ -673,7 +770,10 @@ export default function ProductAllSection({
|
|||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
<div className={css.productDetail}>
|
<div className={css.productDetail}>
|
||||||
<div id="scroll-marker-product-details" className={css.scrollMarker}></div>
|
<div
|
||||||
|
id="scroll-marker-product-details"
|
||||||
|
className={css.scrollMarker}
|
||||||
|
></div>
|
||||||
{/* <LayoutSample onClick={handleLayoutSampleClick} /> */}
|
{/* <LayoutSample onClick={handleLayoutSampleClick} /> */}
|
||||||
<div
|
<div
|
||||||
id="product-details-section"
|
id="product-details-section"
|
||||||
@@ -683,7 +783,7 @@ export default function ProductAllSection({
|
|||||||
>
|
>
|
||||||
{renderItems.length > 0 ? (
|
{renderItems.length > 0 ? (
|
||||||
renderItems.map((item, index) =>
|
renderItems.map((item, index) =>
|
||||||
item.type === 'video' ? (
|
item.type === "video" ? (
|
||||||
<ProductVideo
|
<ProductVideo
|
||||||
key="product-video-0"
|
key="product-video-0"
|
||||||
productInfo={productData}
|
productInfo={productData}
|
||||||
@@ -706,13 +806,21 @@ export default function ProductAllSection({
|
|||||||
<ProductDetail productInfo={productData} />
|
<ProductDetail productInfo={productData} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div id="product-description-section" ref={descriptionRef}>
|
<div
|
||||||
|
id="product-description-section"
|
||||||
|
ref={descriptionRef}
|
||||||
|
onFocus={productFocus}
|
||||||
|
onBlur={_onBlur}
|
||||||
|
>
|
||||||
<ProductDescription productInfo={productData} />
|
<ProductDescription productInfo={productData} />
|
||||||
</div>
|
</div>
|
||||||
{/* 리뷰가 있을 때만 UserReviews 섹션 표시 */}
|
{/* 리뷰가 있을 때만 UserReviews 섹션 표시 */}
|
||||||
{hasReviews && (
|
{hasReviews && (
|
||||||
<>
|
<>
|
||||||
<div id="scroll-marker-user-reviews" className={css.scrollMarker}></div>
|
<div
|
||||||
|
id="scroll-marker-user-reviews"
|
||||||
|
className={css.scrollMarker}
|
||||||
|
></div>
|
||||||
<div
|
<div
|
||||||
id="user-reviews-section"
|
id="user-reviews-section"
|
||||||
ref={reviewRef}
|
ref={reviewRef}
|
||||||
@@ -736,7 +844,10 @@ export default function ProductAllSection({
|
|||||||
</div>
|
</div>
|
||||||
{hasYouMayAlsoLike && (
|
{hasYouMayAlsoLike && (
|
||||||
<div ref={youMayAlsoLikelRef}>
|
<div ref={youMayAlsoLikelRef}>
|
||||||
<div id="scroll-marker-you-may-also-like" className={css.scrollMarker}></div>
|
<div
|
||||||
|
id="scroll-marker-you-may-also-like"
|
||||||
|
className={css.scrollMarker}
|
||||||
|
></div>
|
||||||
<div id="you-may-also-like-section">
|
<div id="you-may-also-like-section">
|
||||||
{/* {(() => {
|
{/* {(() => {
|
||||||
console.log('[YouMayLike] YouMayAlsoLike 컴포넌트 렌더링:', {
|
console.log('[YouMayLike] YouMayAlsoLike 컴포넌트 렌더링:', {
|
||||||
@@ -774,5 +885,5 @@ export default function ProductAllSection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
ProductAllSection.propTypes = {
|
ProductAllSection.propTypes = {
|
||||||
productType: PropTypes.oneOf(['buyNow', 'shopByMobile', 'theme']).isRequired,
|
productType: PropTypes.oneOf(["buyNow", "shopByMobile", "theme"]).isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -710,7 +710,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
&.buttonHasNoCart {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
> * {
|
> * {
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
&:last-child {
|
&:last-child {
|
||||||
@@ -885,7 +887,37 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
.qrRollingWrap {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
.innerText {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 20px;
|
||||||
|
h3 {
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: @PRIMARY_COLOR_RED;
|
||||||
|
& + p {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.17;
|
||||||
|
color: @COLOR_GRAY05;
|
||||||
|
word-break: keep-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
> * {
|
> * {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
|||||||
@@ -152,6 +152,7 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 31px;
|
line-height: 31px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ export default function UserReviewsPopup({
|
|||||||
case "user-reviews":
|
case "user-reviews":
|
||||||
return {
|
return {
|
||||||
title: $L("User Reviews"),
|
title: $L("User Reviews"),
|
||||||
hasIcon: true,
|
hasIcon: false,
|
||||||
iconType: "user-reviews",
|
iconType: null,
|
||||||
};
|
};
|
||||||
case "customer-images":
|
case "customer-images":
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -118,9 +118,13 @@
|
|||||||
// margin-bottom: 30px; // 세로 마진도 증가
|
// margin-bottom: 30px; // 세로 마진도 증가
|
||||||
margin-right: 6px; // 마진 증가로 균등 분배
|
margin-right: 6px; // 마진 증가로 균등 분배
|
||||||
> div {
|
> div {
|
||||||
|
width: 226px;
|
||||||
|
height: 220px;
|
||||||
|
overflow: hidden;
|
||||||
> img {
|
> img {
|
||||||
width: 226px;
|
width: 226px;
|
||||||
height: 220px;
|
height: 220px;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +149,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
object-fit: cover;
|
object-fit: contain;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
@@ -191,7 +195,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
object-fit: cover;
|
object-fit: contain;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
}
|
}
|
||||||
.price {
|
.price {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 60px;
|
font-size: 52px;
|
||||||
color: @COLOR_WHITE;
|
color: @COLOR_WHITE;
|
||||||
margin-right: 9px;
|
margin-right: 9px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|||||||
@@ -1,14 +1,31 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
import React, {
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
useCallback,
|
||||||
import MobileSendPopUp from "../../../components/MobileSend/MobileSendPopUp";
|
useEffect,
|
||||||
import * as Config from "../../../utils/Config";
|
useMemo,
|
||||||
import { setHidePopup, setShowPopup } from "../../../actions/commonActions";
|
useRef,
|
||||||
import Spotlight from "@enact/spotlight";
|
} from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
|
import Spotlight from '@enact/spotlight';
|
||||||
|
|
||||||
|
import {
|
||||||
|
setHidePopup,
|
||||||
|
setShowPopup,
|
||||||
|
} from '../../../actions/commonActions';
|
||||||
import {
|
import {
|
||||||
sendLogShopByMobile,
|
sendLogShopByMobile,
|
||||||
sendLogTotalRecommend,
|
sendLogTotalRecommend,
|
||||||
} from "../../../actions/logActions";
|
} from '../../../actions/logActions';
|
||||||
import { $L, formatLocalDateTime } from "../../../utils/helperMethods";
|
import MobileSendPopUp from '../../../components/MobileSend/MobileSendPopUp';
|
||||||
|
import * as Config from '../../../utils/Config';
|
||||||
|
import {
|
||||||
|
$L,
|
||||||
|
formatLocalDateTime,
|
||||||
|
} from '../../../utils/helperMethods';
|
||||||
|
|
||||||
export default function DetailMobileSendPopUp({
|
export default function DetailMobileSendPopUp({
|
||||||
panelInfo,
|
panelInfo,
|
||||||
@@ -167,11 +184,13 @@ export default function DetailMobileSendPopUp({
|
|||||||
subTitle={mobileSendPopUpSubtitle}
|
subTitle={mobileSendPopUpSubtitle}
|
||||||
patncNm={productData?.patncNm}
|
patncNm={productData?.patncNm}
|
||||||
productImg={mobileSendPopUpProductImg}
|
productImg={mobileSendPopUpProductImg}
|
||||||
|
productId={productData?.prdtId}
|
||||||
patnrId={panelInfo?.patnrId}
|
patnrId={panelInfo?.patnrId}
|
||||||
prdtId={panelInfo?.prdtId}
|
prdtId={panelInfo?.prdtId}
|
||||||
smsTpCd={panelInfo?.type === "hotel" ? "APP00205" : "APP00201"}
|
smsTpCd={panelInfo?.type === "hotel" ? "APP00205" : "APP00201"}
|
||||||
curationId={panelInfo?.curationId}
|
curationId={panelInfo?.curationId}
|
||||||
curationNm={panelInfo?.curationNm}
|
curationNm={panelInfo?.curationNm}
|
||||||
|
brandLogo={productData?.patncLogoPath}
|
||||||
// hotelId={
|
// hotelId={
|
||||||
// panelInfo?.type === "hotel" && hotelInfos[selectedIndex]?.hotelId
|
// panelInfo?.type === "hotel" && hotelInfos[selectedIndex]?.hotelId
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -11,9 +11,10 @@
|
|||||||
background-color: transparent; // DetailPanel에서는 배경 투명
|
background-color: transparent; // DetailPanel에서는 배경 투명
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
width: 1710px;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #EAEAEA;
|
color: #eaeaea;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -46,4 +47,4 @@
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 10px; // 파트너사 로고 후 10px gap
|
margin-right: 10px; // 파트너사 로고 후 10px gap
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,38 @@
|
|||||||
import React, { useCallback, useEffect, useRef } from "react";
|
import React, {
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
useCallback,
|
||||||
import classNames from "classnames";
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import { popPanel } from "../../actions/panelActions";
|
import classNames from 'classnames';
|
||||||
import useReviews from "../../hooks/useReviews/useReviews";
|
import {
|
||||||
import UserReviewHeader from "./UserReviewHeader";
|
useDispatch,
|
||||||
import TPanel from "../../components/TPanel/TPanel";
|
useSelector,
|
||||||
import TBody from "../../components/TBody/TBody";
|
} from 'react-redux';
|
||||||
import StarRating from "../DetailPanel/components/StarRating";
|
|
||||||
import FilterItemButton from "./components/FilterItemButton";
|
import { popPanel } from '../../actions/panelActions';
|
||||||
import UserReviewsList from "./components/UserReviewsList";
|
import TBody from '../../components/TBody/TBody';
|
||||||
import fp from "../../utils/fp";
|
import TPanel from '../../components/TPanel/TPanel';
|
||||||
import css from "./UserReviewPanel.module.less";
|
import useReviews from '../../hooks/useReviews/useReviews';
|
||||||
|
import fp from '../../utils/fp';
|
||||||
|
import StarRating from '../DetailPanel/components/StarRating';
|
||||||
|
import FilterItemButton from './components/FilterItemButton';
|
||||||
|
import UserReviewsList from './components/UserReviewsList';
|
||||||
|
import UserReviewHeader from './UserReviewHeader';
|
||||||
|
import css from './UserReviewPanel.module.less';
|
||||||
|
|
||||||
const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
// panelInfo에서 prdtId와 patnrId 추출
|
// panelInfo에서 prdtId와 patnrId 추출
|
||||||
const prdtId = fp.pipe(
|
const prdtId = fp.pipe(
|
||||||
() => panelInfo,
|
() => panelInfo,
|
||||||
fp.get('prdtId'),
|
fp.get("prdtId"),
|
||||||
fp.defaultTo(null)
|
fp.defaultTo(null)
|
||||||
)();
|
)();
|
||||||
|
|
||||||
const patnrId = fp.pipe(
|
const patnrId = fp.pipe(
|
||||||
() => panelInfo,
|
() => panelInfo,
|
||||||
fp.get('patnrId'),
|
fp.get("patnrId"),
|
||||||
fp.defaultTo(null)
|
fp.defaultTo(null)
|
||||||
)();
|
)();
|
||||||
|
|
||||||
@@ -33,19 +41,19 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
previewReviews,
|
previewReviews,
|
||||||
displayReviews,
|
displayReviews,
|
||||||
filteredReviews,
|
filteredReviews,
|
||||||
userReviewPanelReviews, // 페이징된 리뷰들 (3개씩)
|
userReviewPanelReviews, // 페이징된 리뷰들 (3개씩)
|
||||||
userReviewPanelPage, // 현재 페이지
|
userReviewPanelPage, // 현재 페이지
|
||||||
userReviewPanelHasNext, // 다음 페이지 존재 여부
|
userReviewPanelHasNext, // 다음 페이지 존재 여부
|
||||||
userReviewPanelHasPrev, // 이전 페이지 존재 여부
|
userReviewPanelHasPrev, // 이전 페이지 존재 여부
|
||||||
userReviewPanelTotalPages, // 전체 페이지 수
|
userReviewPanelTotalPages, // 전체 페이지 수
|
||||||
goToNextUserReviewPage, // 다음 페이지로 이동
|
goToNextUserReviewPage, // 다음 페이지로 이동
|
||||||
goToPrevUserReviewPage, // 이전 페이지로 이동
|
goToPrevUserReviewPage, // 이전 페이지로 이동
|
||||||
applyRatingFilter,
|
applyRatingFilter,
|
||||||
applySentimentFilter,
|
applySentimentFilter,
|
||||||
clearAllFilters,
|
clearAllFilters,
|
||||||
currentFilter,
|
currentFilter,
|
||||||
filterCounts,
|
filterCounts,
|
||||||
stats
|
stats,
|
||||||
} = useReviews(prdtId, patnrId);
|
} = useReviews(prdtId, patnrId);
|
||||||
|
|
||||||
// 포커스 복원을 위한 ref
|
// 포커스 복원을 위한 ref
|
||||||
@@ -61,7 +69,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
productDataPrdtId: productData.prdtId,
|
productDataPrdtId: productData.prdtId,
|
||||||
hasProductData: !!productData,
|
hasProductData: !!productData,
|
||||||
previewReviews: previewReviews ? previewReviews.length : 0,
|
previewReviews: previewReviews ? previewReviews.length : 0,
|
||||||
displayReviews: displayReviews ? displayReviews.length : 0
|
displayReviews: displayReviews ? displayReviews.length : 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// UserReviewPanel은 새로운 API 호출 없이 기존 데이터만 사용
|
// UserReviewPanel은 새로운 API 호출 없이 기존 데이터만 사용
|
||||||
@@ -70,41 +78,43 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
hasPreviewReviews: !!previewReviews,
|
hasPreviewReviews: !!previewReviews,
|
||||||
hasDisplayReviews: !!displayReviews,
|
hasDisplayReviews: !!displayReviews,
|
||||||
previewCount: previewReviews ? previewReviews.length : 0,
|
previewCount: previewReviews ? previewReviews.length : 0,
|
||||||
displayCount: displayReviews ? displayReviews.length : 0
|
displayCount: displayReviews ? displayReviews.length : 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 데이터 파싱 - panelInfo에서 직접 가져오기 (Redux productData가 빈 객체가 되는 문제 해결)
|
// 데이터 파싱 - panelInfo에서 직접 가져오기 (Redux productData가 빈 객체가 되는 문제 해결)
|
||||||
const productImage = fp.pipe(
|
const productImage = fp.pipe(
|
||||||
() => panelInfo,
|
() => panelInfo,
|
||||||
fp.get('productImage'),
|
fp.get("productImage"),
|
||||||
fp.defaultTo('https://placehold.co/150x150')
|
fp.defaultTo("https://placehold.co/150x150")
|
||||||
)();
|
)();
|
||||||
|
|
||||||
const brandLogo = fp.pipe(
|
const brandLogo = fp.pipe(
|
||||||
() => panelInfo,
|
() => panelInfo,
|
||||||
fp.get('brandLogo'),
|
fp.get("brandLogo"),
|
||||||
fp.defaultTo('https://placehold.co/50x50')
|
fp.defaultTo("https://placehold.co/50x50")
|
||||||
)();
|
)();
|
||||||
|
|
||||||
const productId = fp.pipe(
|
const productId = fp.pipe(
|
||||||
() => panelInfo,
|
() => panelInfo,
|
||||||
fp.get('prdtId'),
|
fp.get("prdtId"),
|
||||||
fp.defaultTo(null)
|
fp.defaultTo(null)
|
||||||
)();
|
)();
|
||||||
|
|
||||||
const productName = fp.pipe(
|
const productName = fp.pipe(
|
||||||
() => panelInfo,
|
() => panelInfo,
|
||||||
fp.get('productName'),
|
fp.get("productName"),
|
||||||
fp.defaultTo('상품명 정보가 없습니다')
|
fp.defaultTo("상품명 정보가 없습니다")
|
||||||
)();
|
)();
|
||||||
|
|
||||||
// 페이징 후 포커스 복원 함수 - 중간 리뷰(index 1)로 포커스
|
// 페이징 후 포커스 복원 함수 - 중간 리뷰(index 1)로 포커스
|
||||||
const restoreFocusAfterPaging = useCallback(() => {
|
const restoreFocusAfterPaging = useCallback(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const targetElement = document.querySelector(`[data-spotlight-id="user-review-1"]`);
|
const targetElement = document.querySelector(
|
||||||
|
`[data-spotlight-id="user-review-1"]`
|
||||||
|
);
|
||||||
if (targetElement && targetElement.focus) {
|
if (targetElement && targetElement.focus) {
|
||||||
targetElement.focus();
|
targetElement.focus();
|
||||||
console.log('[UserReviewPanel] 중간 리뷰로 포커스 복원 완료');
|
console.log("[UserReviewPanel] 중간 리뷰로 포커스 복원 완료");
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -112,7 +122,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
// 개선된 페이징 함수들
|
// 개선된 페이징 함수들
|
||||||
const handleNextPage = useCallback(() => {
|
const handleNextPage = useCallback(() => {
|
||||||
if (userReviewPanelHasNext) {
|
if (userReviewPanelHasNext) {
|
||||||
console.log('[UserReviewPanel] 다음 페이지로 이동');
|
console.log("[UserReviewPanel] 다음 페이지로 이동");
|
||||||
goToNextUserReviewPage();
|
goToNextUserReviewPage();
|
||||||
restoreFocusAfterPaging();
|
restoreFocusAfterPaging();
|
||||||
}
|
}
|
||||||
@@ -120,7 +130,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
|
|
||||||
const handlePrevPage = useCallback(() => {
|
const handlePrevPage = useCallback(() => {
|
||||||
if (userReviewPanelHasPrev) {
|
if (userReviewPanelHasPrev) {
|
||||||
console.log('[UserReviewPanel] 이전 페이지로 이동');
|
console.log("[UserReviewPanel] 이전 페이지로 이동");
|
||||||
goToPrevUserReviewPage();
|
goToPrevUserReviewPage();
|
||||||
restoreFocusAfterPaging();
|
restoreFocusAfterPaging();
|
||||||
}
|
}
|
||||||
@@ -132,44 +142,92 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
const avgRating = stats.averageRating || 5;
|
const avgRating = stats.averageRating || 5;
|
||||||
|
|
||||||
// 별점 필터링 핸들러들
|
// 별점 필터링 핸들러들
|
||||||
const handleRatingFilter = useCallback((rating) => {
|
const handleRatingFilter = useCallback(
|
||||||
console.log('[ProductId] Rating filter applied:', rating);
|
(rating) => {
|
||||||
console.log('[ProductId] applyRatingFilter function:', !!applyRatingFilter);
|
console.log("[ProductId] Rating filter applied:", rating);
|
||||||
applyRatingFilter(rating); // 'all' 값을 그대로 전달
|
console.log(
|
||||||
}, [applyRatingFilter]);
|
"[ProductId] applyRatingFilter function:",
|
||||||
|
!!applyRatingFilter
|
||||||
|
);
|
||||||
|
applyRatingFilter(rating); // 'all' 값을 그대로 전달
|
||||||
|
},
|
||||||
|
[applyRatingFilter]
|
||||||
|
);
|
||||||
|
|
||||||
const handleAllStarsFilter = useCallback(() => handleRatingFilter('all'), [handleRatingFilter]);
|
const handleAllStarsFilter = useCallback(
|
||||||
const handle5StarsFilter = useCallback(() => handleRatingFilter(5), [handleRatingFilter]);
|
() => handleRatingFilter("all"),
|
||||||
const handle4StarsFilter = useCallback(() => handleRatingFilter(4), [handleRatingFilter]);
|
[handleRatingFilter]
|
||||||
const handle3StarsFilter = useCallback(() => handleRatingFilter(3), [handleRatingFilter]);
|
);
|
||||||
const handle2StarsFilter = useCallback(() => handleRatingFilter(2), [handleRatingFilter]);
|
const handle5StarsFilter = useCallback(
|
||||||
const handle1StarsFilter = useCallback(() => handleRatingFilter(1), [handleRatingFilter]);
|
() => handleRatingFilter(5),
|
||||||
|
[handleRatingFilter]
|
||||||
|
);
|
||||||
|
const handle4StarsFilter = useCallback(
|
||||||
|
() => handleRatingFilter(4),
|
||||||
|
[handleRatingFilter]
|
||||||
|
);
|
||||||
|
const handle3StarsFilter = useCallback(
|
||||||
|
() => handleRatingFilter(3),
|
||||||
|
[handleRatingFilter]
|
||||||
|
);
|
||||||
|
const handle2StarsFilter = useCallback(
|
||||||
|
() => handleRatingFilter(2),
|
||||||
|
[handleRatingFilter]
|
||||||
|
);
|
||||||
|
const handle1StarsFilter = useCallback(
|
||||||
|
() => handleRatingFilter(1),
|
||||||
|
[handleRatingFilter]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAromaClick = useCallback(() => console.log("Aroma clicked"), []);
|
||||||
|
const handleVanillaClick = useCallback(
|
||||||
|
() => console.log("Vanilla clicked"),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const handleCinnamonClick = useCallback(
|
||||||
|
() => console.log("Cinnamon clicked"),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const handleQualityClick = useCallback(
|
||||||
|
() => console.log("Quality clicked"),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const handleAromaClick = useCallback(() => console.log('Aroma clicked'), []);
|
|
||||||
const handleVanillaClick = useCallback(() => console.log('Vanilla clicked'), []);
|
|
||||||
const handleCinnamonClick = useCallback(() => console.log('Cinnamon clicked'), []);
|
|
||||||
const handleQualityClick = useCallback(() => console.log('Quality clicked'), []);
|
|
||||||
|
|
||||||
// 감정 필터링 핸들러들 - 별점 필터와 동일한 방식
|
// 감정 필터링 핸들러들 - 별점 필터와 동일한 방식
|
||||||
const handleSentimentFilter = useCallback((sentiment) => {
|
const handleSentimentFilter = useCallback(
|
||||||
console.log('[ProductId] Sentiment filter applied:', sentiment);
|
(sentiment) => {
|
||||||
applySentimentFilter(sentiment === 'all' ? null : sentiment);
|
console.log("[ProductId] Sentiment filter applied:", sentiment);
|
||||||
}, [applySentimentFilter]);
|
applySentimentFilter(sentiment === "all" ? null : sentiment);
|
||||||
|
},
|
||||||
const handlePositiveClick = useCallback(() => handleSentimentFilter('positive'), [handleSentimentFilter]);
|
[applySentimentFilter]
|
||||||
const handleNegativeClick = useCallback(() => handleSentimentFilter('negative'), [handleSentimentFilter]);
|
);
|
||||||
|
|
||||||
|
const handlePositiveClick = useCallback(
|
||||||
|
() => handleSentimentFilter("positive"),
|
||||||
|
[handleSentimentFilter]
|
||||||
|
);
|
||||||
|
const handleNegativeClick = useCallback(
|
||||||
|
() => handleSentimentFilter("negative"),
|
||||||
|
[handleSentimentFilter]
|
||||||
|
);
|
||||||
|
|
||||||
// UserReviewPanel 마운트 시 기본 All stars 필터 적용
|
// UserReviewPanel 마운트 시 기본 All stars 필터 적용
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prdtId && currentFilter.type === 'rating' && currentFilter.value === 'all') {
|
if (
|
||||||
console.log('[ProductId] UserReviewPanel 기본 All stars 필터 이미 적용됨');
|
prdtId &&
|
||||||
|
currentFilter.type === "rating" &&
|
||||||
|
currentFilter.value === "all"
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
"[ProductId] UserReviewPanel 기본 All stars 필터 이미 적용됨"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [prdtId, currentFilter]);
|
}, [prdtId, currentFilter]);
|
||||||
|
|
||||||
// 메모리 해제를 위한 cleanup 함수
|
// 메모리 해제를 위한 cleanup 함수
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
console.log('[ProductId] UserReviewPanel unmounting - clearing filters');
|
console.log("[ProductId] UserReviewPanel unmounting - clearing filters");
|
||||||
clearAllFilters(); // 필터 상태 초기화로 메모리 해제
|
clearAllFilters(); // 필터 상태 초기화로 메모리 해제
|
||||||
};
|
};
|
||||||
}, [clearAllFilters]);
|
}, [clearAllFilters]);
|
||||||
@@ -186,22 +244,33 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
userReviewPanelReviewsLength: userReviewPanelReviews.length, // UserReviewPanel에서 표시할 4개
|
userReviewPanelReviewsLength: userReviewPanelReviews.length, // UserReviewPanel에서 표시할 4개
|
||||||
currentFilter: currentFilter,
|
currentFilter: currentFilter,
|
||||||
filterCounts: filterCounts,
|
filterCounts: filterCounts,
|
||||||
isDataFromCache: true // API 호출 없이 캐시된 데이터 사용
|
isDataFromCache: true, // API 호출 없이 캐시된 데이터 사용
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[ProductId] UserReviewPanel 로그 오류:", error);
|
console.error("[ProductId] UserReviewPanel 로그 오류:", error);
|
||||||
}
|
}
|
||||||
}, [reviewCount, filteredCount, avgRating, displayReviews, userReviewPanelReviews, currentFilter, filterCounts]);
|
}, [
|
||||||
|
reviewCount,
|
||||||
|
filteredCount,
|
||||||
|
avgRating,
|
||||||
|
displayReviews,
|
||||||
|
userReviewPanelReviews,
|
||||||
|
currentFilter,
|
||||||
|
filterCounts,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleBackButton = useCallback(() => {
|
const handleBackButton = useCallback(() => {
|
||||||
console.log(`[ProductId] Back button clicked - returning to DetailPanel`);
|
console.log(`[ProductId] Back button clicked - returning to DetailPanel`);
|
||||||
dispatch(popPanel());
|
dispatch(popPanel());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleCancel = useCallback((e) => {
|
const handleCancel = useCallback(
|
||||||
dispatch(popPanel());
|
(e) => {
|
||||||
e.stopPropagation();
|
dispatch(popPanel());
|
||||||
}, [dispatch]);
|
e.stopPropagation();
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TPanel
|
<TPanel
|
||||||
@@ -259,7 +328,9 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
{/* 왼쪽 필터 영역 */}
|
{/* 왼쪽 필터 영역 */}
|
||||||
<div className={css.reviewsSection__filters}>
|
<div className={css.reviewsSection__filters}>
|
||||||
<div className={css.reviewsSection__filters__title}>
|
<div className={css.reviewsSection__filters__title}>
|
||||||
<div className={css.reviewsSection__filters__title__text}>Filter Reviews</div>
|
<div className={css.reviewsSection__filters__title__text}>
|
||||||
|
Filter Reviews
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 모든 필터들을 묶는 컨테이너 */}
|
{/* 모든 필터들을 묶는 컨테이너 */}
|
||||||
@@ -267,60 +338,82 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
{/* Rating 필터 섹션 */}
|
{/* Rating 필터 섹션 */}
|
||||||
<div className={css.reviewsSection__filters__section}>
|
<div className={css.reviewsSection__filters__section}>
|
||||||
<div className={css.reviewsSection__filters__sectionTitle}>
|
<div className={css.reviewsSection__filters__sectionTitle}>
|
||||||
<div className={css.reviewsSection__filters__sectionTitle__text}>Rating</div>
|
<div
|
||||||
|
className={css.reviewsSection__filters__sectionTitle__text}
|
||||||
|
>
|
||||||
|
Rating
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={css.reviewsSection__filters__group}>
|
<div className={css.reviewsSection__filters__group}>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`All stars(${filterCounts?.rating?.all || reviewCount || 0})`}
|
text={`All star(${filterCounts?.rating?.all || reviewCount || 0})`}
|
||||||
onClick={handleAllStarsFilter}
|
onClick={handleAllStarsFilter}
|
||||||
spotlightId="filter-all-stars"
|
spotlightId="filter-all-stars"
|
||||||
ariaLabel="Filter by all star ratings"
|
ariaLabel="Filter by all star ratings"
|
||||||
dataSpotlightDown="filter-5-stars"
|
dataSpotlightDown="filter-5-stars"
|
||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 'all'}
|
isActive={
|
||||||
|
currentFilter.type === "rating" &&
|
||||||
|
currentFilter.value === "all"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`5 stars (${filterCounts?.rating?.[5] || 0})`}
|
text={`5 star (${filterCounts?.rating?.[5] || 0})`}
|
||||||
onClick={handle5StarsFilter}
|
onClick={handle5StarsFilter}
|
||||||
spotlightId="filter-5-stars"
|
spotlightId="filter-5-stars"
|
||||||
ariaLabel="Filter by 5 star ratings"
|
ariaLabel="Filter by 5 star ratings"
|
||||||
dataSpotlightUp="filter-all-stars"
|
dataSpotlightUp="filter-all-stars"
|
||||||
dataSpotlightDown="filter-4-stars"
|
dataSpotlightDown="filter-4-stars"
|
||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 5}
|
isActive={
|
||||||
|
currentFilter.type === "rating" &&
|
||||||
|
currentFilter.value === 5
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`4 stars (${filterCounts?.rating?.[4] || 0})`}
|
text={`4 star (${filterCounts?.rating?.[4] || 0})`}
|
||||||
onClick={handle4StarsFilter}
|
onClick={handle4StarsFilter}
|
||||||
spotlightId="filter-4-stars"
|
spotlightId="filter-4-stars"
|
||||||
ariaLabel="Filter by 4 star ratings"
|
ariaLabel="Filter by 4 star ratings"
|
||||||
dataSpotlightUp="filter-5-stars"
|
dataSpotlightUp="filter-5-stars"
|
||||||
dataSpotlightDown="filter-3-stars"
|
dataSpotlightDown="filter-3-stars"
|
||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 4}
|
isActive={
|
||||||
|
currentFilter.type === "rating" &&
|
||||||
|
currentFilter.value === 4
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`3 stars (${filterCounts?.rating?.[3] || 0})`}
|
text={`3 star (${filterCounts?.rating?.[3] || 0})`}
|
||||||
onClick={handle3StarsFilter}
|
onClick={handle3StarsFilter}
|
||||||
spotlightId="filter-3-stars"
|
spotlightId="filter-3-stars"
|
||||||
ariaLabel="Filter by 3 star ratings"
|
ariaLabel="Filter by 3 star ratings"
|
||||||
dataSpotlightUp="filter-4-stars"
|
dataSpotlightUp="filter-4-stars"
|
||||||
dataSpotlightDown="filter-2-stars"
|
dataSpotlightDown="filter-2-stars"
|
||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 3}
|
isActive={
|
||||||
|
currentFilter.type === "rating" &&
|
||||||
|
currentFilter.value === 3
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`2 stars (${filterCounts?.rating?.[2] || 0})`}
|
text={`2 star (${filterCounts?.rating?.[2] || 0})`}
|
||||||
onClick={handle2StarsFilter}
|
onClick={handle2StarsFilter}
|
||||||
spotlightId="filter-2-stars"
|
spotlightId="filter-2-stars"
|
||||||
ariaLabel="Filter by 2 star ratings"
|
ariaLabel="Filter by 2 star ratings"
|
||||||
dataSpotlightUp="filter-3-stars"
|
dataSpotlightUp="filter-3-stars"
|
||||||
dataSpotlightDown="filter-1-stars"
|
dataSpotlightDown="filter-1-stars"
|
||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 2}
|
isActive={
|
||||||
|
currentFilter.type === "rating" &&
|
||||||
|
currentFilter.value === 2
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`1 stars (${filterCounts?.rating?.[1] || 0})`}
|
text={`1 star (${filterCounts?.rating?.[1] || 0})`}
|
||||||
onClick={handle1StarsFilter}
|
onClick={handle1StarsFilter}
|
||||||
spotlightId="filter-1-stars"
|
spotlightId="filter-1-stars"
|
||||||
ariaLabel="Filter by 1 star ratings"
|
ariaLabel="Filter by 1 star ratings"
|
||||||
dataSpotlightUp="filter-2-stars"
|
dataSpotlightUp="filter-2-stars"
|
||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 1}
|
isActive={
|
||||||
|
currentFilter.type === "rating" &&
|
||||||
|
currentFilter.value === 1
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -328,7 +421,11 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
{/* Keywords 필터 섹션 */}
|
{/* Keywords 필터 섹션 */}
|
||||||
<div className={css.reviewsSection__filters__section}>
|
<div className={css.reviewsSection__filters__section}>
|
||||||
<div className={css.reviewsSection__filters__sectionTitle}>
|
<div className={css.reviewsSection__filters__sectionTitle}>
|
||||||
<div className={css.reviewsSection__filters__sectionTitle__text}>Keywords</div>
|
<div
|
||||||
|
className={css.reviewsSection__filters__sectionTitle__text}
|
||||||
|
>
|
||||||
|
Keywords
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={css.reviewsSection__filters__group}>
|
<div className={css.reviewsSection__filters__group}>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
@@ -369,7 +466,11 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
{/* Sentiment 필터 섹션 */}
|
{/* Sentiment 필터 섹션 */}
|
||||||
<div className={css.reviewsSection__filters__section}>
|
<div className={css.reviewsSection__filters__section}>
|
||||||
<div className={css.reviewsSection__filters__sectionTitle}>
|
<div className={css.reviewsSection__filters__sectionTitle}>
|
||||||
<div className={css.reviewsSection__filters__sectionTitle__text}>Sentiment</div>
|
<div
|
||||||
|
className={css.reviewsSection__filters__sectionTitle__text}
|
||||||
|
>
|
||||||
|
Sentiment
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={css.reviewsSection__filters__group}>
|
<div className={css.reviewsSection__filters__group}>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
@@ -379,7 +480,10 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
ariaLabel="Filter by positive sentiment"
|
ariaLabel="Filter by positive sentiment"
|
||||||
dataSpotlightUp="filter-quality"
|
dataSpotlightUp="filter-quality"
|
||||||
dataSpotlightDown="filter-negative"
|
dataSpotlightDown="filter-negative"
|
||||||
isActive={currentFilter.type === 'sentiment' && currentFilter.value === 'positive'}
|
isActive={
|
||||||
|
currentFilter.type === "sentiment" &&
|
||||||
|
currentFilter.value === "positive"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`Negative (${filterCounts?.sentiment?.negative || 0})`}
|
text={`Negative (${filterCounts?.sentiment?.negative || 0})`}
|
||||||
@@ -387,7 +491,10 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
spotlightId="filter-negative"
|
spotlightId="filter-negative"
|
||||||
ariaLabel="Filter by negative sentiment"
|
ariaLabel="Filter by negative sentiment"
|
||||||
dataSpotlightUp="filter-positive"
|
dataSpotlightUp="filter-positive"
|
||||||
isActive={currentFilter.type === 'sentiment' && currentFilter.value === 'negative'}
|
isActive={
|
||||||
|
currentFilter.type === "sentiment" &&
|
||||||
|
currentFilter.value === "negative"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,35 +5,35 @@
|
|||||||
// TButton 기본 스타일 무력화
|
// TButton 기본 스타일 무력화
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
// FilterItem 레이아웃 스타일 (요구사항에 맞는 스타일 적용)
|
// FilterItem 레이아웃 스타일 (요구사항에 맞는 스타일 적용)
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
border: 1px solid #DADADA;
|
border: 1px solid #dadada;
|
||||||
background: #FFF;
|
background: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
// 고정 넓이와 마진 설정으로 일정한 간격 유지 (한 라인에 4개 표시)
|
// 고정 넓이와 마진 설정으로 일정한 간격 유지 (한 라인에 4개 표시)
|
||||||
width: 90px;
|
width: 90px;
|
||||||
margin-right: 40px;
|
margin-right: 15px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
// 각 라인의 마지막 아이템은 오른쪽 마진 제거
|
// 각 라인의 마지막 아이템은 오른쪽 마진 제거
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 포커스 상태: 빨간색 배경
|
// 포커스 상태: 빨간색 배경
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background: #D32F2F !important; // 빨간색
|
background: #d32f2f !important; // 빨간색
|
||||||
border: 1px solid #D32F2F !important;
|
border: 1px solid #d32f2f !important;
|
||||||
|
|
||||||
.filterItemButton__text {
|
.filterItemButton__text {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
@@ -45,9 +45,9 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
background: #7A808D !important; // 회색 (선택됨)
|
background: #7a808d !important; // 회색 (선택됨)
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
border: 1px solid #7A808D !important;
|
border: 1px solid #7a808d !important;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: black;
|
color: black;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-family: 'LG Smart UI';
|
font-family: "LG Smart UI";
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-family: 'LG Smart UI';
|
font-family: "LG Smart UI";
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
@@ -85,4 +85,4 @@
|
|||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user