[251019] fix: DetailPanel,ProductAllSection 개선

🕐 커밋 시간: 2025. 10. 19. 13:04:56

📊 변경 통계:
  • 총 파일: 7개
  • 추가: +50줄
  • 삭제: -9줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/toastActions.js
  ~ com.twin.app.shoptime/src/components/TToast/TToastEnhanced.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.module.less
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/CustomDropDown.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/ProductQuantity.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/actions/toastActions.js (javascript):
    🔄 Modified: curry()
  📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript):
    🔄 Modified: extractProductMeta()
  📄 com.twin.app.shoptime/src/views/DetailPanel/components/CustomDropDown.jsx (javascript):
     Added: Spottable()
  📄 com.twin.app.shoptime/src/views/DetailPanel/components/ProductQuantity.jsx (javascript):
     Added: ProductQuantity()
     Deleted: ProductQuantity()

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • UI 컴포넌트 아키텍처 개선
This commit is contained in:
2025-10-19 13:05:00 +09:00
parent 40df2c042e
commit 325b648993
7 changed files with 218 additions and 205 deletions

View File

@@ -1,18 +1,9 @@
import React, {
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
useDispatch,
useSelector,
} from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import { addToCart } from '../../../actions/cartActions';
import { getMyInfoCheckoutInfo } from '../../../actions/checkoutActions';
@@ -23,15 +14,9 @@ import {
showError,
} from '../../../actions/commonActions';
import { getProductCouponSearch } from '../../../actions/couponActions';
import {
sendLogPaymentEntry,
sendLogTotalRecommend,
} from '../../../actions/logActions';
import { sendLogPaymentEntry, sendLogTotalRecommend } from '../../../actions/logActions';
import { pushPanel } from '../../../actions/panelActions';
import {
getProductOption,
getProductOptionId,
} from '../../../actions/productActions';
import { getProductOption, getProductOptionId } from '../../../actions/productActions';
import { clearAllToasts } from '../../../actions/toastActions';
import TButton from '../../../components/TButton/TButton';
import TPopUp from '../../../components/TPopUp/TPopUp';
@@ -44,8 +29,11 @@ import CustomDropDown from './CustomDropDown';
import ProductQuantity from './ProductQuantity';
const Container = SpotlightContainerDecorator(
{ spotlightRestrict: "self-only" },
"div"
{
spotlightRestrict: 'self-only',
leaveFor: {}, // arrow key로 빠져나가지 않도록 설정
},
'div'
);
const BuyOption = ({
@@ -61,23 +49,15 @@ const BuyOption = ({
const dispatch = useDispatch();
// Redux 상태 (props가 있으면 props 우선, 없으면 Redux에서)
const { userId, userNumber } = useSelector(
(state) => state.common.appStatus.loginUserData
);
const { userId, userNumber } = useSelector((state) => state.common.appStatus.loginUserData);
const reduxProductInfo = useSelector((state) => state.main.productData);
const productInfo = propsProductInfo || reduxProductInfo;
const productOptionInfos = useSelector((state) => state.product.prdtOptInfo);
const productData = useSelector((state) => state.main.productData);
const { partnerCoupon } = useSelector(
(state) => state.coupon.productCouponSearchData || {}
);
const { popupVisible, activePopup } = useSelector(
(state) => state.common.popup
);
const { partnerCoupon } = useSelector((state) => state.coupon.productCouponSearchData || {});
const { popupVisible, activePopup } = useSelector((state) => state.common.popup);
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
const webOSVersion = useSelector(
(state) => state.common.appStatus.webOSVersion
);
const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion);
// 옵션 선택 상태 관리 (SingleOption과 동일한 구조)
const [selectedBtnOptIdx, setSelectedBtnOptIdx] = useState(0);
@@ -104,27 +84,26 @@ const BuyOption = ({
couponInfo = partnerCoupon[0];
}
const { catCd, catNm, patncNm, patnrId, prdtId, prdtNm, priceInfo } =
productData;
const { catCd, catNm, patncNm, patnrId, prdtId, prdtNm, priceInfo } = productData;
const { cpnSno, cpnTtl } = couponInfo || {};
const prodOptSno =
(productOptionInfos &&
productOptionInfos.length > 0 &&
productOptionInfos[0]?.prodOptSno) ||
"";
'';
const prodOptTpCdCval =
(productOptionInfos &&
productOptionInfos.length > 0 &&
productOptionInfos[0]?.prodOptTpCdCval) ||
"";
'';
return {
cpnSno: String(cpnSno) || "",
cpnTtl: cpnTtl || "",
dcAftrPrc: priceInfo.split("|")[1],
dcBefPrc: priceInfo.split("|")[0],
lgCatCd: catCd || "",
lgCatNm: catNm || "",
cpnSno: String(cpnSno) || '',
cpnTtl: cpnTtl || '',
dcAftrPrc: priceInfo.split('|')[1],
dcBefPrc: priceInfo.split('|')[0],
lgCatCd: catCd || '',
lgCatNm: catNm || '',
patncNm,
patnrId,
prodId: prdtId,
@@ -141,27 +120,26 @@ const BuyOption = ({
couponInfo = partnerCoupon[0];
}
const { catCd, catNm, patncNm, patnrId, prdtId, prdtNm, priceInfo } =
productInfo;
const { catCd, catNm, patncNm, patnrId, prdtId, prdtNm, priceInfo } = productInfo;
const { cpnSno, cpnTtl } = couponInfo || {};
const prodOptSno =
(productOptionInfos &&
productOptionInfos.length > 0 &&
productOptionInfos[0]?.prodOptSno) ||
"";
'';
const prodOptTpCdCval =
(productOptionInfos &&
productOptionInfos.length > 0 &&
productOptionInfos[0]?.prodOptTpCdCval) ||
"";
'';
return {
cpnSno: String(cpnSno) || "",
cpnTtl: cpnTtl || "",
dcAftrPrc: priceInfo.split("|")[1],
dcBefPrc: priceInfo.split("|")[0],
lgCatCd: catCd || "",
lgCatNm: catNm || "",
cpnSno: String(cpnSno) || '',
cpnTtl: cpnTtl || '',
dcAftrPrc: priceInfo.split('|')[1],
dcBefPrc: priceInfo.split('|')[0],
lgCatCd: catCd || '',
lgCatNm: catNm || '',
patncNm,
patnrId,
prodId: prdtId,
@@ -177,7 +155,7 @@ const BuyOption = ({
// 옵션 리셋 로직 (SingleOption과 동일)
useEffect(() => {
if (type !== "theme") {
if (type !== 'theme') {
return;
}
@@ -224,20 +202,20 @@ const BuyOption = ({
useEffect(() => {
if (!isSpotlight) {
// isSpotlight이 false면 일반적인 BuyOption 포커스
console.log("[BuyOption] Component mounted - focusing BUY NOW button");
console.log('[BuyOption] Component mounted - focusing BUY NOW button');
setTimeout(() => {
Spotlight.focus("buy-option-buy-now-button");
Spotlight.focus('buy-option-buy-now-button');
}, 100);
return;
}
// isSpotlight이 true이고 SingleOption 동작이 필요한 경우
if (productInfo?.optProdYn === "N") {
Spotlight.focus("buy-option-buy-now-button");
if (productInfo?.optProdYn === 'N') {
Spotlight.focus('buy-option-buy-now-button');
} else if (productOptionInfos && productOptionInfos.length > 0) {
Spotlight.focus("buy-option-first-dropdown");
Spotlight.focus('buy-option-first-dropdown');
if (productOptionInfos.length === 1) {
Spotlight.focus("buy-option-second-dropdown");
Spotlight.focus('buy-option-second-dropdown');
}
}
}, [productOptionInfos, productInfo, isSpotlight]);
@@ -285,19 +263,19 @@ const BuyOption = ({
}
}
Spotlight.focus("buy-option-buy-now-button");
Spotlight.focus('buy-option-buy-now-button');
}
const handleBuyNowClick = useCallback(() => {
console.log("[BuyOption] BUY NOW clicked");
console.log('[BuyOption] BUY NOW clicked');
// const optionName = renderOptionName();
// const optionValueName = renderOptionValue();
if (productInfo && productInfo?.soldoutFlag === "Y") {
if (productInfo && productInfo?.soldoutFlag === 'Y') {
return;
}
if (!userNumber || userNumber === "") {
if (!userNumber || userNumber === '') {
return dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
}
@@ -314,9 +292,9 @@ const BuyOption = ({
if (userNumber && selectedPatnrId && selectedPrdtId && quantity) {
const { prodOptCval, priceInfo } = selectedOptions || {};
const { patncNm, brndNm, catNm, prdtNm, prdtId } = productInfo;
const regularPrice = priceInfo?.split("|")[0];
const discountPrice = priceInfo?.split("|")[1];
const discountRate = priceInfo?.split("|")[4];
const regularPrice = priceInfo?.split('|')[0];
const discountPrice = priceInfo?.split('|')[1];
const discountRate = priceInfo?.split('|')[4];
dispatch(
sendLogTotalRecommend({
@@ -327,7 +305,7 @@ const BuyOption = ({
price: discountRate ? discountPrice : regularPrice,
discount: discountRate,
brand: brndNm,
productOption: prodOptCval || "",
productOption: prodOptCval || '',
category: catNm,
contextName: Config.LOG_CONTEXT_NAME.DETAILPAGE,
messageId: Config.LOG_MESSAGE_ID.BUY_NOW,
@@ -338,7 +316,7 @@ const BuyOption = ({
getMyInfoCheckoutInfo(
{
mbrNo: userNumber,
dirPurcSelYn: "Y",
dirPurcSelYn: 'Y',
cartList: [
{
patnrId: selectedPatnrId,
@@ -368,15 +346,15 @@ const BuyOption = ({
// ADD TO CART 버튼 클릭 핸들러
const handleAddToCartClick = useCallback(() => {
console.log("[BuyOption] ADD TO CART clicked");
console.log('[BuyOption] ADD TO CART clicked');
// 상품 품절 체크
if (productInfo && productInfo?.soldoutFlag === "Y") {
if (productInfo && productInfo?.soldoutFlag === 'Y') {
return;
}
// 로그인 체크
if (!userNumber || userNumber === "") {
if (!userNumber || userNumber === '') {
return dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
}
@@ -384,9 +362,9 @@ const BuyOption = ({
if (userNumber && selectedPatnrId && selectedPrdtId && quantity) {
const { prodOptCval, priceInfo } = selectedOptions || {};
const { patncNm, brndNm, catNm, prdtNm, prdtId } = productInfo;
const regularPrice = priceInfo?.split("|")[0];
const discountPrice = priceInfo?.split("|")[1];
const discountRate = priceInfo?.split("|")[4];
const regularPrice = priceInfo?.split('|')[0];
const discountPrice = priceInfo?.split('|')[1];
const discountRate = priceInfo?.split('|')[4];
// 로그 전송
dispatch(
@@ -398,7 +376,7 @@ const BuyOption = ({
price: discountRate ? discountPrice : regularPrice,
discount: discountRate,
brand: brndNm,
productOption: prodOptCval || "",
productOption: prodOptCval || '',
category: catNm,
contextName: Config.LOG_CONTEXT_NAME.DETAILPAGE,
messageId: Config.LOG_MESSAGE_ID.ADD_TO_CART,
@@ -441,7 +419,7 @@ const BuyOption = ({
// 첫번째 옵션 선택 핸들러 (SingleOption과 동일)
const handleFirstOptionSelect = (selected) => {
const optionValIdx = selected.selected;
console.log("[BuyOption] First option selected:", optionValIdx);
console.log('[BuyOption] First option selected:', optionValIdx);
setSelectedBtnOptIdx(optionValIdx);
setSelectedOptionItemIndex(0);
@@ -453,15 +431,11 @@ const BuyOption = ({
// 두번째 옵션 선택 핸들러 (SingleOption과 동일)
const handleSecondOptionSelect = (selected) => {
const index = selected.selected;
console.log("[BuyOption] Second option selected:", index);
console.log('[BuyOption] Second option selected:', index);
setSelectedOptionItemIndex(index);
setSelectedOptions(
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl[index]
);
setSelectedOptions(productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl[index]);
dispatch(
getProductOptionId(
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl[index]?.prodOptCdCval
)
getProductOptionId(productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl[index]?.prodOptCdCval)
);
setIsOptionValue(true);
};
@@ -469,7 +443,7 @@ const BuyOption = ({
// 수량 선택 핸들러
const handleQuantitySelect = (selected) => {
const qty = selected.selected + 1;
console.log("[BuyOption] Quantity selected:", qty);
console.log('[BuyOption] Quantity selected:', qty);
setQuantity(qty);
};
@@ -506,7 +480,7 @@ const BuyOption = ({
// Favorite 플래그 업데이트 (SingleOption과 동일)
useEffect(() => {
setFavoriteFlag(productInfo?.favorYn ? productInfo?.favorYn : "N");
setFavoriteFlag(productInfo?.favorYn ? productInfo?.favorYn : 'N');
}, [productInfo]);
// Favorite 플래그 변경 콜백 (SingleOption과 동일)
@@ -525,24 +499,30 @@ const BuyOption = ({
// 로그인 팝업 텍스트 로직 (SingleOption과 동일)
const loginPopupText = useMemo(() => {
if (!userNumber) {
return $L("Would you like to sign in?");
return $L('Would you like to sign in?');
}
if (!hasOnClose) {
return $L("Please select Option");
return $L('Please select Option');
}
return $L("Would you like to sign in?");
return $L('Would you like to sign in?');
}, [hasOnClose, userNumber]);
// BuyOption 내에서 arrow up으로 빠져나가지 않도록 처리
const handleArrowUpWithinBuyOption = useCallback((e) => {
e.stopPropagation();
// arrow up 이벤트를 막음 (컨테이너의 leaveFor: {}와 함께 작동)
}, []);
// 팝업 닫기 핸들러 (SingleOption과 동일)
const onClose = useCallback(
(spotlightId) => {
dispatch(setHidePopup());
let currentSpot;
if (typeof spotlightId === "string") {
if (typeof spotlightId === 'string') {
currentSpot = spotlightId;
} else {
currentSpot = "buy-option-buy-now-button";
currentSpot = 'buy-option-buy-now-button';
}
if (currentSpot) {
@@ -557,9 +537,9 @@ const BuyOption = ({
// 로그인 팝업 열기 핸들러 (SingleOption과 동일)
const handleLoginPopUpOpen = useCallback(() => {
if (!userNumber) {
if (webOSVersion >= "6.0") {
if (webOSVersion >= '6.0') {
setTimeout(() => {
Spotlight.focus("buy-option-buy-now-button");
Spotlight.focus('buy-option-buy-now-button');
});
dispatch(setHidePopup());
@@ -573,11 +553,11 @@ const BuyOption = ({
if (hasOnClose) {
dispatch(setHidePopup());
let spotlightId = "buy-option-first-dropdown";
let spotlightId = 'buy-option-first-dropdown';
//옵션이 하나만 있는경우 isOptionValue === false
if (!isOptionValue) {
spotlightId = "buy-option-second-dropdown";
spotlightId = 'buy-option-second-dropdown';
}
setTimeout(() => {
Spotlight.focus(spotlightId);
@@ -590,57 +570,52 @@ const BuyOption = ({
<Container className={styles.buy_option}>
<div className={styles.buy_option__left_section}>
{/* 동적 옵션 렌더링 */}
{productOptionInfos &&
productOptionInfos.length > 0 &&
productInfo?.optProdYn === "Y" && (
<>
{/* 첫번째 옵션 (여러 옵션이 있을 때만) */}
{productOptionInfos.length > 1 && (
{productOptionInfos && productOptionInfos.length > 0 && productInfo?.optProdYn === 'Y' && (
<>
{/* 첫번째 옵션 (여러 옵션이 있을 때만) */}
{productOptionInfos.length > 1 && (
<div className={styles.buy_option__option_row}>
<div className={styles.buy_option__option_label}>
<div className={styles.buy_option__label_text}>OPTION 1</div>
</div>
<div className={styles.buy_option__option_control}>
<CustomDropDown
options={productOptionInfos.map((option) => option.optNm)}
selectedIndex={selectedBtnOptIdx}
onSelect={handleFirstOptionSelect}
spotlightId="buy-option-first-dropdown"
onSpotlightUp={handleArrowUpWithinBuyOption}
/>
</div>
</div>
)}
{/* 두번째 옵션 (옵션 상세값들) */}
{productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1 && (
<div className={styles.buy_option__option_row}>
<div className={styles.buy_option__option_label}>
<div className={styles.buy_option__label_text}>
OPTION 1
{productOptionInfos.length === 1 ? 'OPTION' : 'OPTION 2'}
</div>
</div>
<div className={styles.buy_option__option_control}>
<CustomDropDown
options={productOptionInfos.map((option) => option.optNm)}
selectedIndex={selectedBtnOptIdx}
onSelect={handleFirstOptionSelect}
spotlightId="buy-option-first-dropdown"
options={
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.map(
(detail) => detail.prodOptCval
) || []
}
selectedIndex={selectedOptionItemIndex}
onSelect={handleSecondOptionSelect}
spotlightId="buy-option-second-dropdown"
onSpotlightUp={handleArrowUpWithinBuyOption}
/>
</div>
</div>
)}
{/* 두번째 옵션 (옵션 상세값들) */}
{productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length >
1 && (
<div className={styles.buy_option__option_row}>
<div className={styles.buy_option__option_label}>
<div className={styles.buy_option__label_text}>
{productOptionInfos.length === 1
? "OPTION"
: "OPTION 2"}
</div>
</div>
<div className={styles.buy_option__option_control}>
<CustomDropDown
options={
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.map(
(detail) => detail.prodOptCval
) || []
}
selectedIndex={selectedOptionItemIndex}
onSelect={handleSecondOptionSelect}
spotlightId="buy-option-second-dropdown"
/>
</div>
</div>
)}
</>
)}
</>
)}
{/* 수량 선택 */}
<div className={styles.buy_option__option_row}>
@@ -654,7 +629,7 @@ const BuyOption = ({
onSelect={handleQuantitySelect}
spotlightId="buy-option-quantity-dropdown"
/> */}
<ProductQuantity />
<ProductQuantity onSpotlightUp={handleArrowUpWithinBuyOption} />
</div>
</div>
</div>
@@ -671,6 +646,7 @@ const BuyOption = ({
spotlightId="buy-option-buy-now-button"
size="detailButton"
onClick={handleBuyNowClick}
onSpotlightUp={handleArrowUpWithinBuyOption}
>
<span className={styles.buy_option__button_text}>BUY NOW</span>
</TButton>
@@ -679,6 +655,7 @@ const BuyOption = ({
spotlightId="buy-option-add-to-cart-button"
size="detailButton"
onClick={handleAddToCartClick}
onSpotlightUp={handleArrowUpWithinBuyOption}
>
<span className={styles.buy_option__button_text}>ADD TO CART</span>
</TButton>
@@ -694,8 +671,8 @@ const BuyOption = ({
text={loginPopupText}
hasButton
hasOnClose={hasOnClose}
button1Text={$L("OK")}
button2Text={$L("CANCEL")}
button1Text={$L('OK')}
button2Text={$L('CANCEL')}
onClick={handleLoginPopUpOpen}
onClose={onClose}
/>