[250925] refactor: views - ProductAllSection.jsx - 기능 개선

🕐 커밋 시간: 2025. 09. 25. 13:17:33

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +134줄
  • 삭제: -95줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript):
    🔄 Modified: LayoutSample()
This commit is contained in:
djaco
2025-09-25 13:17:34 +09:00
parent c2c914f655
commit 797a5648af

View File

@@ -1,29 +1,18 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import classNames from 'classnames';
import { throttle } from 'lodash';
import { PropTypes } from 'prop-types';
import {
useDispatch,
useSelector,
} from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import Spotlight from '@enact/spotlight';
/* eslint-disable react/jsx-no-bind */
// src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
//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 { pushPanel } from '../../../actions/panelActions';
import { resetShowAllReviews } from '../../../actions/productActions';
import { showToast } from '../../../actions/toastActions';
@@ -57,15 +46,11 @@ import ProductTag from '../components/ProductTag';
import StarRating from '../components/StarRating';
// ProductContentSection imports
import TScrollerDetail from '../components/TScroller/TScrollerDetail';
import ProductDescription
from '../ProductContentSection/ProductDescription/ProductDescription';
import ProductDetail
from '../ProductContentSection/ProductDetail/ProductDetail.new';
import ProductDescription from '../ProductContentSection/ProductDescription/ProductDescription';
import ProductDetail from '../ProductContentSection/ProductDetail/ProductDetail.new';
import UserReviews from '../ProductContentSection/UserReviews/UserReviews';
import ViewAllReviewsButton
from '../ProductContentSection/UserReviews/ViewAllReviewsButton';
import YouMayAlsoLike
from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
import ViewAllReviewsButton from '../ProductContentSection/UserReviews/ViewAllReviewsButton';
import YouMayAlsoLike from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
import QRCode from '../ProductInfoSection/QRCode/QRCode';
import ProductOverview from '../ProductOverview/ProductOverview';
// CSS imports
@@ -75,45 +60,42 @@ import css from './ProductAllSection.module.less';
const Container = SpotlightContainerDecorator(
{
enterTo: "last-focused",
enterTo: 'last-focused',
preserveld: true,
leaveFor: { right: "content-scroller-container" },
spotlightDirection: "vertical",
leaveFor: { right: 'content-scroller-container' },
spotlightDirection: 'vertical',
},
"div"
'div'
);
const ContentContainer = SpotlightContainerDecorator(
{
enterTo: "default-element",
enterTo: 'default-element',
preserveld: true,
leaveFor: {
left: "spotlight-product-info-section-container",
left: 'spotlight-product-info-section-container',
},
restrict: "none",
spotlightDirection: "vertical",
restrict: 'none',
spotlightDirection: 'vertical',
},
"div"
'div'
);
const HorizontalContainer = SpotlightContainerDecorator(
{
enterTo: "last-focused",
enterTo: 'last-focused',
preserveld: true,
defaultElement: "spotlight-product-info-section-container",
spotlightDirection: "horizontal",
defaultElement: 'spotlight-product-info-section-container',
spotlightDirection: 'horizontal',
},
"div"
'div'
);
// FP: Pure function to determine product data based on type
const getProductData = curry((productType, themeProductInfo, productInfo) =>
pipe(
when(
() =>
isVal(productType) &&
productType === "theme" &&
isVal(themeProductInfo),
() => isVal(productType) && productType === 'theme' && isVal(themeProductInfo),
() => themeProductInfo
),
defaultTo(productInfo),
@@ -125,18 +107,18 @@ const getProductData = curry((productType, themeProductInfo, productInfo) =>
const deriveFavoriteFlag = curry((favoriteOverride, productData) =>
pipe(
when(isNotNil, identity),
defaultWith(() => pipe(get("favorYn"), defaultTo("N"))(productData))
defaultWith(() => pipe(get('favorYn'), defaultTo('N'))(productData))
)(favoriteOverride)
);
// FP: Pure function to extract review grade and order phone
const extractProductMeta = (productInfo) => ({
revwGrd: get("revwGrd", productInfo),
orderPhnNo: get("orderPhnNo", productInfo),
revwGrd: get('revwGrd', productInfo),
orderPhnNo: get('orderPhnNo', productInfo),
});
// 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경
const SpottableComponent = Spottable("div");
const SpottableComponent = Spottable('div');
const LayoutSample = ({ onClick }) => (
<SpottableComponent
@@ -163,6 +145,11 @@ export default function ProductAllSection({
isOnRender,
}) {
const dispatch = useDispatch();
// Redux 상태 추가 - DetailPanel.backup.jsx와 동일하게
const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion);
const groupInfos = useSelector((state) => state.product.groupInfo);
const [currentHeight, setCurrentHeight] = useState(0);
//하단부분까지 갔을때 체크용
const [documentHeight, setDocumentHeight] = useState(0);
@@ -178,6 +165,42 @@ export default function ProductAllSection({
[productType, themeProductInfo, productInfo]
);
// 단품(결제 가능 상품) - DetailPanel.backup.jsx와 동일한 로직
const isBillingProductVisible = useMemo(() => {
return (
productData?.pmtSuptYn === 'Y' &&
productData?.grPrdtProcYn === 'N' &&
panelInfo?.prdtId &&
webOSVersion >= '6.0'
);
}, [productData, webOSVersion, panelInfo?.prdtId]);
// 구매 불가 상품 - DetailPanel.backup.jsx와 동일한 로직
const isUnavailableProductVisible = useMemo(() => {
return (
productData?.pmtSuptYn === 'N' ||
(productData?.pmtSuptYn === 'Y' &&
productData?.grPrdtProcYn === 'N' &&
webOSVersion < '6.0' &&
panelInfo?.prdtId)
);
}, [productData, webOSVersion, panelInfo?.prdtId]);
// 그룹 상품 - DetailPanel.backup.jsx와 동일한 로직
const isGroupProductVisible = useMemo(() => {
return (
productData?.pmtSuptYn === 'Y' &&
productData?.grPrdtProcYn === 'Y' &&
groupInfos &&
groupInfos.length > 0
);
}, [productData, groupInfos]);
// 여행/테마 상품 - DetailPanel.backup.jsx와 동일한 로직
const isTravelProductVisible = useMemo(() => {
return panelInfo?.curationId && (panelInfo?.type === 'theme' || panelInfo?.type === 'hotel');
}, [panelInfo]);
// useReviews Hook 사용 - 모든 리뷰 관련 로직을 담당
const {
previewReviews,
@@ -187,17 +210,12 @@ export default function ProductAllSection({
} = useReviews(productData.prdtId, panelInfo && panelInfo.patnrId);
// YouMayAlsoLike 데이터 확인
const youmaylikeProductData = useSelector(
(state) => state.main.youmaylikeData
);
const hasYouMayAlsoLike =
youmaylikeProductData && youmaylikeProductData.length > 0;
const youmaylikeProductData = useSelector((state) => state.main.youmaylikeData);
const hasYouMayAlsoLike = youmaylikeProductData && youmaylikeProductData.length > 0;
// ProductAllSection 마운트 시 showAllReviews 초기화
useEffect(() => {
console.log(
"[ProductAllSection] Component mounted - resetting showAllReviews to false"
);
console.log('[ProductAllSection] Component mounted - resetting showAllReviews to false');
dispatch(resetShowAllReviews());
}, []); // 빈 dependency array = 마운트 시에만 실행
@@ -219,9 +237,9 @@ export default function ProductAllSection({
(productData.imgUrls600 && productData.imgUrls600[0]) ||
(productData.imgUrls && productData.imgUrls[0]) ||
productData.thumbnailUrl ||
"https://placehold.co/150x150",
brandLogo: productData.patncLogoPath || "https://placehold.co/50x50",
productName: productData.prdtNm || "상품명 정보가 없습니다",
'https://placehold.co/150x150',
brandLogo: productData.patncLogoPath || 'https://placehold.co/50x50',
productName: productData.prdtNm || '상품명 정보가 없습니다',
avgRating: stats.averageRating || 5,
reviewCount: stats.totalReviews || 0,
},
@@ -250,23 +268,19 @@ export default function ProductAllSection({
// 디버깅: 실제 이미지 데이터 확인
useEffect(() => {
console.log("[ProductId] ProductAllSection productData check:", {
console.log('[ProductId] ProductAllSection productData check:', {
hasProductData: !!productData,
productDataPrdtId: productData && productData.prdtId,
imgUrls600: productData && productData.imgUrls600,
imgUrls600Length:
productData && productData.imgUrls600 && productData.imgUrls600.length,
imgUrls600Length: productData && productData.imgUrls600 && productData.imgUrls600.length,
imgUrls600Type: Array.isArray(productData && productData.imgUrls600)
? "array"
? 'array'
: typeof (productData && productData.imgUrls600),
productData: productData,
});
}, [productData]);
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
const [favoriteOverride, setFavoriteOverride] = useState(null);
@@ -289,7 +303,7 @@ export default function ProductAllSection({
// User Reviews 스크롤 핸들러 추가
const handleUserReviewsClick = useCallback(
() => scrollToSection("scroll-marker-user-reviews"),
() => scrollToSection('scroll-marker-user-reviews'),
[]
);
@@ -310,7 +324,7 @@ export default function ProductAllSection({
// FP: Pure function for focus navigation to back button
const handleSpotlightUpToBackButton = useCallback((e) => {
e.stopPropagation();
Spotlight.focus("spotlightId_backBtn");
Spotlight.focus('spotlightId_backBtn');
}, []);
// FP: Pure function for favorite flag change
@@ -323,7 +337,7 @@ export default function ProductAllSection({
const handleThemeItemButtonClick = useCallback(
pipe(
() => setOpenThemeItemOverlay(true),
tap(() => setTimeout(() => Spotlight.focus("theme-close-button"), 0))
tap(() => setTimeout(() => Spotlight.focus('theme-close-button'), 0))
),
[setOpenThemeItemOverlay]
);
@@ -344,12 +358,12 @@ export default function ProductAllSection({
// FP: Curried scroll handlers
const handleProductDetailsClick = useCallback(
() => scrollToSection("scroll-marker-product-details"),
() => scrollToSection('scroll-marker-product-details'),
[scrollToSection]
);
const handleYouMayAlsoLikeClick = useCallback(
() => scrollToSection("scroll-marker-you-may-also-like"),
() => scrollToSection('scroll-marker-you-may-also-like'),
[scrollToSection]
);
const scrollPositionRef = useRef(0);
@@ -419,12 +433,7 @@ export default function ProductAllSection({
(descriptionRef.current?.scrollHeight || 0) +
(reviewRef.current?.scrollHeight || 0)
);
}, [
productDetailRef.current,
descriptionRef.current,
hasReviews,
hasYouMayAlsoLike,
]);
}, [productDetailRef.current, descriptionRef.current, hasReviews, hasYouMayAlsoLike]);
//spot관련
useEffect(() => {
@@ -448,10 +457,10 @@ export default function ProductAllSection({
<div className={css.leftInfoWrapper}>
<div className={css.headerContent}>
<ProductTag productInfo={productData} />
{revwGrd && revwGrd !== "0.0" && (
{revwGrd && revwGrd !== '0.0' && (
<StarRating
rating={revwGrd}
aria-label={"star rating " + revwGrd + " out of 5"}
aria-label={'star rating ' + revwGrd + ' out of 5'}
/>
)}
</div>
@@ -465,33 +474,31 @@ export default function ProductAllSection({
productType={productType}
>
<div className={css.qrWrapper}>
<QRCode
productInfo={productData}
productType={productType}
kind={"detail"}
/>
<QRCode productInfo={productData} productType={productType} kind={'detail'} />
</div>
</ProductOverview>
{/* BUY NOW + ADD TO CART 버튼들 (결제 가능 상품일 때만 렌더링) */}
<HorizontalContainer className={css.buyNowCartContainer}>
<TButton
spotlightId="detail-buy-now-button"
className={css.buyNowButton}
onClick={handleBuyNowClick}
onSpotlightUp={handleSpotlightUpToBackButton}
>
<div className={css.buyNowText}>{$L('BUY NOW')}</div>
</TButton>
<TButton
spotlightId="detail-add-to-cart-button"
className={css.addToCartButton}
onClick={handleAddToCartClick}
onSpotlightUp={handleSpotlightUpToBackButton}
>
<div className={css.addToCartText}>{$L('ADD TO CART')}</div>
</TButton>
</HorizontalContainer>
{isBillingProductVisible && (
<HorizontalContainer className={css.buyNowCartContainer}>
<TButton
spotlightId="detail-buy-now-button"
className={css.buyNowButton}
onClick={handleBuyNowClick}
onSpotlightUp={handleSpotlightUpToBackButton}
>
<div className={css.buyNowText}>{$L('BUY NOW')}</div>
</TButton>
<TButton
spotlightId="detail-add-to-cart-button"
className={css.addToCartButton}
onClick={handleAddToCartClick}
onSpotlightUp={handleSpotlightUpToBackButton}
>
<div className={css.addToCartText}>{$L('ADD TO CART')}</div>
</TButton>
</HorizontalContainer>
)}
<Container className={css.buttonContainer}>
<TButton
@@ -500,9 +507,7 @@ export default function ProductAllSection({
onClick={handleShopByMobileOpen}
onSpotlightUp={handleSpotlightUpToBackButton}
>
<div className={css.shopByMobileText}>
{$L("SHOP BY MOBILE")}
</div>
<div className={css.shopByMobileText}>{$L('SHOP BY MOBILE')}</div>
</TButton>
{panelInfo && (
<div className={css.favoriteBtnWrapper}>
@@ -512,7 +517,7 @@ export default function ProductAllSection({
selectedPrdtId={panelInfo && panelInfo.prdtId}
favoriteFlag={favoriteFlag}
onFavoriteFlagChanged={onFavoriteFlagChanged}
kind={"item_detail"}
kind={'item_detail'}
/>
</div>
)}
@@ -521,9 +526,7 @@ export default function ProductAllSection({
<div className={css.callToOrderSection}>
{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.phoneIconContainer}>
<div className={css.phoneIcon} />
@@ -541,50 +544,44 @@ export default function ProductAllSection({
<TButton
className={classNames(
css.productDetailsButton,
activeProductBtn ? css.active : ""
activeProductBtn ? css.active : ''
)}
onClick={handleProductDetailsClick}
spotlightId="product-details-button"
>
{$L("PRODUCT DETAILS")}
{$L('PRODUCT DETAILS')}
</TButton>
{hasReviews && (
<TButton
className={classNames(
css.userReviewsButton,
activeReviewBtn ? css.active : ""
)}
className={classNames(css.userReviewsButton, activeReviewBtn ? css.active : '')}
onClick={handleUserReviewsClick}
spotlightId="user-reviews-button"
>
{$L("USER REVIEWS")} ({reviewTotalCount})
{$L('USER REVIEWS')} ({reviewTotalCount})
</TButton>
)}
{hasYouMayAlsoLike && (
<TButton
className={classNames(
css.youMayLikeButton,
activeYouMayLikeBtn ? css.active : ""
activeYouMayLikeBtn ? css.active : ''
)}
onClick={handleYouMayAlsoLikeClick}
>
{$L("YOU MAY ALSO LIKE")}
{$L('YOU MAY ALSO LIKE')}
</TButton>
)}
</Container>
{panelInfo &&
panelInfo &&
panelInfo.type === "theme" &&
!openThemeItemOverlay && (
<TButton
className={css.themeButton}
onClick={handleThemeItemButtonClick}
spotlightId="theme-open-button"
>
{$L("THEME ITEM")}
</TButton>
)}
{panelInfo && panelInfo && panelInfo.type === 'theme' && !openThemeItemOverlay && (
<TButton
className={css.themeButton}
onClick={handleThemeItemButtonClick}
spotlightId="theme-open-button"
>
{$L('THEME ITEM')}
</TButton>
)}
<DetailMobileSendPopUp
ismobileSendPopupOpen={mobileSendPopupOpen}
@@ -616,10 +613,7 @@ export default function ProductAllSection({
onScroll={handleScroll}
>
<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} /> */}
<div
id="product-details-section"
@@ -627,9 +621,7 @@ export default function ProductAllSection({
onFocus={productFocus}
onBlur={_onBlur}
>
{productData &&
productData.imgUrls600 &&
productData.imgUrls600.length > 0 ? (
{productData && productData.imgUrls600 && productData.imgUrls600.length > 0 ? (
productData.imgUrls600.map((image, index) => (
<ProductDetail
key={`product-detail-${index}`}
@@ -651,10 +643,7 @@ export default function ProductAllSection({
{/* 리뷰가 있을 때만 UserReviews 섹션 표시 */}
{hasReviews && (
<>
<div
id="scroll-marker-user-reviews"
className={css.scrollMarker}
></div>
<div id="scroll-marker-user-reviews" className={css.scrollMarker}></div>
<div
id="user-reviews-section"
ref={reviewRef}
@@ -678,10 +667,7 @@ export default function ProductAllSection({
</div>
{hasYouMayAlsoLike && (
<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">
<YouMayAlsoLike
productInfo={productData}
@@ -710,5 +696,5 @@ export default function ProductAllSection({
}
ProductAllSection.propTypes = {
productType: PropTypes.oneOf(["buyNow", "shopByMobile", "theme"]).isRequired,
productType: PropTypes.oneOf(['buyNow', 'shopByMobile', 'theme']).isRequired,
};