Files
shoptime/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx
junghoon86.park 80c593e6f0 [상품 상세]
- 상풉 옵션별 ID노출 처리.
2025-12-08 20:10:02 +09:00

1626 lines
58 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import {
addToCart,
insertMyinfoCart,
} from '../../../actions/cartActions';
import { getMyInfoCheckoutInfo } from '../../../actions/checkoutActions';
import {
changeAppStatus,
setHidePopup,
setShowPopup,
showError,
} from '../../../actions/commonActions';
import { getProductCouponSearch } from '../../../actions/couponActions';
import {
sendLogPaymentEntry,
sendLogTotalRecommend,
} from '../../../actions/logActions';
import { finishMediaPreview } from '../../../actions/mediaActions';
import {
popPanel,
pushPanel,
} from '../../../actions/panelActions';
import {
clearAllVideoTimers,
finishVideoPreview,
} from '../../../actions/playActions';
import {
getProductOption,
getProductOptionId,
} from '../../../actions/productActions';
import { clearAllToasts } from '../../../actions/toastActions';
import TButton from '../../../components/TButton/TButton';
import TPopUp from '../../../components/TPopUp/TPopUp';
import { launchMembershipApp } from '../../../lunaSend';
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
import {
createMockProductOptionData,
} from '../../../utils/BuyNowDataManipulator';
import * as Config from '../../../utils/Config';
import { $L } from '../../../utils/helperMethods';
import { getSafeProductPrice } from '../../../utils/mockDataSafetyUtils';
import FavoriteBtn from '../components/FavoriteBtn';
import styles from './BuyOption.module.less';
import BuyOptionPriceBlock from './BuyOptionPriceBlock';
// import CustomDropDown from './CustomDropDown';
import CustomDropDown from './CustomDropDown.new';
import ProductQuantity from './ProductQuantity';
const Container = SpotlightContainerDecorator(
{
spotlightRestrict: 'self-only',
leaveFor: {}, // arrow key로 빠져나가지 않도록 설정
},
'div'
);
const BuyOption = ({
patncNm,
productInfo: propsProductInfo,
isSpotlight,
selectedPatnrId: propsSelectedPatnrId,
selectedPrdtId: propsSelectedPrdtId,
selectedIndex,
logMenu,
type,
}) => {
// DEBUG_LOG 설정 - 이 값이 true일 때만 console.log가 실행됨
const DEBUG_LOG = true;
// 🚀 컴포넌트 마운트 시 props 확인
useEffect(() => {
console.log('[BuyOption] 🚀 COMPONENT MOUNTED - Props received:', {
propsProductInfo: !!propsProductInfo,
propsSelectedPatnrId,
propsSelectedPrdtId,
patncNm,
type,
});
}, []);
// console.log 오버라이드를 위한 ref
const originalConsoleLog = useRef(console.log);
// 컴포넌트 마운트 시 console.log 오버라이드
useEffect(() => {
// 원래 함수 저장
originalConsoleLog.current = console.log;
// console.log 오버라이드 - 이 컴포넌트에서만 DEBUG_LOG 조건을 체크하도록 설정
console.log = (...args) => {
if (DEBUG_LOG) {
originalConsoleLog.current.apply(console, args);
}
};
return () => {
// console.log 원래 함수로 복원
console.log = originalConsoleLog.current;
};
}, []); // 의존성 배열이 비어있어 컴포넌트 마운트 시 한 번만 실행
const dispatch = useDispatch();
// Redux 상태 (props가 있으면 props 우선, 없으면 Redux에서)
const { userId, userNumber } = useSelector(
(state) => state.common.appStatus.loginUserData
);
const reduxProductInfo = useSelector((state) => state.main.productData);
const productInfo = propsProductInfo || reduxProductInfo;
const reduxProductOptionInfos = useSelector((state) => state.product.prdtOptInfo);
const productData = useSelector((state) => state.main.productData);
// 🔴 Redux productData 상태 확인
useEffect(() => {
console.log('[BuyOption] 🔴 REDUX PRODUCT DATA STATUS:', {
hasReduxProductInfo: !!reduxProductInfo,
hasProductData: !!productData,
reduxProductInfoFull: reduxProductInfo,
productDataFull: productData,
});
}, [reduxProductInfo, productData]);
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
);
// 옵션 선택 상태 관리 (SingleOption과 동일한 구조)
const [selectedBtnOptIdx, setSelectedBtnOptIdx] = useState(0);
const [selectedOptionItemIndex, setSelectedOptionItemIndex] = useState(0);
const [selectedOptions, setSelectedOptions] = useState();
const [isOptionValue, setIsOptionValue] = useState(false);
const [isOptionSelect, setIsOptionSelect] = useState(false);
const [quantity, setQuantity] = useState(1);
const [favoriteFlag, setFavoriteFlag] = useState(productInfo?.favorYn);
const [hasProductOptionArray, setHasProductOptionArray] = useState(true);
const [selectedOptionInfo, setSelectedOptionInfo] = useState();
const [selectFirstOptionIndex, setSelectFirstOptionIndex] = useState(0);
const [selectSecondOptionIndex, setSelectSecondOptionIndex] = useState(0);
// 상품 정보 (props 우선, 없으면 productInfo에서)
const selectedPatnrId = propsSelectedPatnrId || productInfo?.patnrId;
const selectedPrdtId = propsSelectedPrdtId || productInfo?.prdtId;
const isMockMode = BUYNOW_CONFIG.isMockMode();
// 🔴 selectedPatnrId, selectedPrdtId 변경 감지
useEffect(() => {
console.log('[BuyOption] 🔴 SELECTED IDS CHANGED:', {
selectedPatnrId,
selectedPrdtId,
propsSelectedPatnrId,
propsSelectedPrdtId,
});
}, [selectedPatnrId, selectedPrdtId, propsSelectedPatnrId, propsSelectedPrdtId]);
// 🔍 Redux 상태와 계산된 값 확인
useEffect(() => {
console.log('[BuyOption] 🔍 REDUX STATE & CALCULATED VALUES:', {
isMockMode,
propsSelectedPatnrId,
propsSelectedPrdtId,
propsProductInfo: !!propsProductInfo,
reduxProductInfo: !!reduxProductInfo,
productInfo: !!productInfo,
reduxProductInfoPatnrId: productInfo?.patnrId,
reduxProductInfoPrdtId: productInfo?.prdtId,
finalSelectedPatnrId: selectedPatnrId,
finalSelectedPrdtId: selectedPrdtId,
reduxProductOptionInfos: reduxProductOptionInfos,
fullProductInfo: productInfo,
});
}, [selectedPatnrId, selectedPrdtId, isMockMode, productInfo, reduxProductOptionInfos, reduxProductInfo, propsProductInfo]);
// Mock Mode에서 옵션 데이터 처리
const productOptionInfos = useMemo(() => {
console.log('[BuyOption] productOptionInfos useMemo - isMockMode:', isMockMode);
console.log('[BuyOption] productOptionInfos useMemo - reduxProductOptionInfos:', reduxProductOptionInfos);
// API Mode: 기존 로직 100% 유지
if (!isMockMode) {
console.log('[BuyOption] API Mode - using reduxProductOptionInfos');
return reduxProductOptionInfos;
}
// Mock Mode: 옵션 데이터가 없거나 배열이 아니면 Mock 데이터 생성
const isValidReduxData = Array.isArray(reduxProductOptionInfos) && reduxProductOptionInfos.length > 0;
if (!isValidReduxData) {
console.log('[BuyOption] Mock Mode - generating mock option data (reduxData invalid)');
const mockOptionData = createMockProductOptionData(productData);
console.log('[BuyOption] Mock Mode - createMockProductOptionData result:', mockOptionData);
// Mock 옵션 데이터는 이미 배열 구조로 반환됨
return mockOptionData || [];
}
// Mock Mode이고 유효한 옵션 데이터가 있으면 그대로 사용
console.log('[BuyOption] Mock Mode - using existing valid reduxProductOptionInfos');
return reduxProductOptionInfos;
}, [reduxProductOptionInfos, productData, productInfo, isMockMode]); // logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용)
const hasMockOptions =
isMockMode && Array.isArray(productOptionInfos) && productOptionInfos.length > 0;
const cleanupVideoAndPlayerPanels = useCallback(() => {
clearAllVideoTimers();
dispatch((dispatchFn, getState) => {
const getPanels = () => getState().panels?.panels || [];
const popIfTopMatches = (predicate, action) => {
const panels = getPanels();
if (!panels.length) {
return;
}
const topPanel = panels[panels.length - 1];
if (predicate(topPanel)) {
action();
}
};
popIfTopMatches(
(panel) =>
panel?.name === Config.panel_names.MEDIA_PANEL &&
panel.panelInfo?.modal,
() => dispatchFn(finishMediaPreview())
);
popIfTopMatches(
(panel) =>
panel &&
(panel.name === Config.panel_names.PLAYER_PANEL ||
panel.name === Config.panel_names.PLAYER_PANEL_NEW) &&
panel.panelInfo?.modal,
() => dispatchFn(finishVideoPreview())
);
});
}, [dispatch]);
const selectDefaultOptionForMock = useCallback(() => {
if (!isMockMode) {
return selectedOptions;
}
if (selectedOptions) {
return selectedOptions;
}
const firstGroup = Array.isArray(productOptionInfos)
? productOptionInfos[0]
: null;
const firstDetail = firstGroup?.prdtOptDtl?.[0];
if (firstDetail) {
const hasSecondDropdown = (firstGroup?.prdtOptDtl?.length || 0) > 1;
const hasMultipleGroups = (productOptionInfos?.length || 0) > 1;
setSelectedOptions((prev) => prev || firstDetail);
setIsOptionValue(true);
setIsOptionSelect(true);
setSelectedBtnOptIdx((prev) => prev || 0);
setSelectedOptionItemIndex((prev) =>
hasSecondDropdown ? (prev > 0 ? prev : 1) : 0
);
setSelectSecondOptionIndex((prev) =>
hasSecondDropdown ? (prev > 0 ? prev : 1) : 0
);
if (hasMultipleGroups) {
setSelectFirstOptionIndex((prev) => (prev > 0 ? prev : 1));
}
return firstDetail;
}
const fallbackLabel =
productInfo?.optNm || productInfo?.prdtNm || 'Mock Option';
const fallbackPriceInfo =
productInfo?.priceInfo || '999.99|999.99|0|0%|0';
const fallbackOption = {
prodOptCdCval: 'MOCK_DEFAULT_OPT',
prodOptCval: fallbackLabel,
optNm: fallbackLabel,
optPrc: productInfo?.price3 || productInfo?.price2 || '0.00',
priceInfo: fallbackPriceInfo,
};
setSelectedOptions((prev) => prev || fallbackOption);
setIsOptionValue(true);
setIsOptionSelect(true);
setSelectedBtnOptIdx((prev) => prev || 0);
setSelectedOptionItemIndex((prev) => (prev > 0 ? prev : 0));
setSelectSecondOptionIndex((prev) => (prev > 0 ? prev : 0));
return fallbackOption;
}, [
isMockMode,
selectedOptions,
productOptionInfos,
productInfo,
]);
const logInfo = useMemo(() => {
if (productData) {
// productData가 있으면 SingleOption과 동일하게 처리
let couponInfo;
if (partnerCoupon && partnerCoupon.length > 0) {
couponInfo = partnerCoupon[0];
}
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 || '',
patncNm,
patnrId,
prodId: prdtId,
prodNm: prdtNm,
prodOptSno: prodOptSno,
prodOptTpCdCval: prodOptTpCdCval,
qty: String(quantity),
};
} else if (productInfo) {
// productData가 없으면 productInfo 사용
let couponInfo;
if (partnerCoupon && partnerCoupon.length > 0) {
couponInfo = partnerCoupon[0];
}
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 || '',
patncNm,
patnrId,
prodId: prdtId,
prodNm: prdtNm,
prodOptSno: prodOptSno,
prodOptTpCdCval: prodOptTpCdCval,
qty: String(quantity),
};
}
return {};
}, [partnerCoupon, productData, productInfo, productOptionInfos, quantity]);
// 옵션 리셋 로직 (SingleOption과 동일)
useEffect(() => {
if (type !== 'theme') {
return;
}
setSelectedOptions();
setIsOptionValue(false);
setSelectedOptionItemIndex(0);
setIsOptionSelect(false);
setQuantity(1);
setSelectedBtnOptIdx(0);
}, [selectedIndex, productOptionInfos, type]);
// 옵션 자동 선택 로직 (SingleOption과 동일)
// Mock Mode: 항상 첫 번째 옵션을 자동으로 선택
useEffect(() => {
console.log('[BuyOption] autoSelect useEffect - productOptionInfos:', productOptionInfos);
console.log('[BuyOption] autoSelect useEffect - selectedBtnOptIdx:', selectedBtnOptIdx);
console.log('[BuyOption] autoSelect useEffect - isOptionValue:', isOptionValue);
if (!productOptionInfos || productOptionInfos.length === 0) {
console.log('[BuyOption] autoSelect - productOptionInfos is empty, returning');
return;
}
const currentOptionGroup = productOptionInfos[selectedBtnOptIdx];
if (!currentOptionGroup) {
console.log('[BuyOption] autoSelect - currentOptionGroup is not found at index:', selectedBtnOptIdx);
return;
}
const optionDetails = currentOptionGroup.prdtOptDtl;
console.log('[BuyOption] autoSelect - optionDetails:', optionDetails);
if (!optionDetails || optionDetails.length === 0) {
console.log('[BuyOption] autoSelect - optionDetails is empty');
return;
}
// 이미 선택되었으면 스킵
if (isOptionValue) {
console.log('[BuyOption] autoSelect - already selected, skipping');
return;
}
// Mock Mode: 첫 번째 옵션 자동 선택
if (BUYNOW_CONFIG.isMockMode()) {
console.log('[BuyOption] Mock Mode - Auto selecting first option:', optionDetails[0]);
setSelectedOptions(optionDetails[0]);
setIsOptionValue(true);
}
// API Mode: 옵션이 1개일 때만 자동 선택
else if (optionDetails.length === 1) {
console.log('[BuyOption] API Mode - Auto selecting only option:', optionDetails[0]);
setSelectedOptions(optionDetails[0]);
setIsOptionValue(true);
}
}, [productOptionInfos, selectedBtnOptIdx]);
// 필수 데이터 로드 (SingleOption과 동일)
// Mock Mode: API 호출 스킵 (Mock 데이터만 사용)
// 🚀 처음 마운트될 때와 선택된 ID가 변경될 때마다 호출
useEffect(() => {
// 디버그 로그: useEffect 진입
console.log('%c[BuyOption] 📍 DATA LOADING useEffect - Dependencies changed', 'background: red; color: white; font-weight: bold; padding: 5px;', {
isMockMode,
selectedPatnrId,
selectedPrdtId,
userNumber,
hasSelectedPatnrId: !!selectedPatnrId,
hasSelectedPrdtId: !!selectedPrdtId,
});
// API Mode: 실제 API 호출
if (!isMockMode) {
// ⚠️ 필수 값 검증
if (!selectedPatnrId || !selectedPrdtId) {
console.warn('[BuyOption] ⚠️ [getProductOption] MISSING REQUIRED PARAMS', {
selectedPatnrId,
selectedPrdtId,
reason: 'selectedPatnrId or selectedPrdtId is missing',
});
return; // API 호출하지 않음
}
console.log('[BuyOption] 🔄 [getProductOption] API CALL - Dispatching getProductOption', {
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
});
dispatch(
getProductOption({
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
})
);
console.log('[BuyOption] 🔄 [getProductCouponSearch] API CALL - Dispatching getProductCouponSearch', {
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
mbrNo: userNumber,
});
dispatch(
getProductCouponSearch({
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
mbrNo: userNumber,
})
);
} else {
// Mock Mode 상태 로그
console.log('[BuyOption] 🚫 Mock Mode Active - Skipping API calls');
console.log('[BuyOption] 🚫 [getProductOption] SKIPPED - Mock Mode enabled');
console.log('[BuyOption] 🚫 [getProductCouponSearch] SKIPPED - Mock Mode enabled');
}
// Mock Mode: API 호출 하지 않음
}, [dispatch, selectedPatnrId, selectedPrdtId, userNumber, isMockMode]);
// 포커스 관리 로직 (SingleOption과 유사)
useEffect(() => {
const shouldHandleOptionFocus =
productInfo?.optProdYn === 'Y' || hasMockOptions;
if (!shouldHandleOptionFocus) {
Spotlight.focus('buy-option-buy-now-button');
return;
}
if (productOptionInfos && productOptionInfos.length > 1) {
Spotlight.focus('buy-option-first-dropdown');
} else if (
productOptionInfos &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1
) {
Spotlight.focus('buy-option-second-dropdown');
} else {
Spotlight.focus('buy-option-buy-now-button');
}
}, [
productOptionInfos,
productInfo?.optProdYn,
hasMockOptions,
selectedBtnOptIdx,
]);
// checkOutValidate 콜백 함수 (SingleOption과 동일한 로직)
function checkOutValidate(response) {
console.log('%c[BuyOption] 🔴 checkOutValidate CALLED', 'background: red; color: white; font-weight: bold; padding: 5px;', {
hasResponse: !!response,
retCode: response?.retCode,
retMsg: response?.retMsg,
hasCardInfo: !!response?.data?.cardInfo,
hasBillingAddress: response?.data?.billingAddressList?.length > 0,
hasShippingAddress: response?.data?.shippingAddressList?.length > 0,
hasProductList: response?.data?.productList?.length > 0,
});
if (response) {
if (response.retCode === 0) {
// 🔍 조건 체크
const isCardInfoNull = response.data.cardInfo === null;
const isBillingAddressEmpty = response.data.billingAddressList.length === 0;
const isShippingAddressEmpty = response.data.shippingAddressList.length === 0;
console.log('%c[BuyOption] 🔍 Address & Card Validation:', 'background: blue; color: white; font-weight: bold; padding: 5px;', {
isCardInfoNull,
isBillingAddressEmpty,
isShippingAddressEmpty,
needsQRPopup: isCardInfoNull || isBillingAddressEmpty || isShippingAddressEmpty,
});
if (
isCardInfoNull ||
isBillingAddressEmpty ||
isShippingAddressEmpty
) {
console.log('%c[BuyOption] 🟡 Missing card/address - Showing QR Popup', 'background: orange; color: white; font-weight: bold; padding: 5px;');
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
dispatch(changeAppStatus({ isLoading: false }));
return;
} else {
console.log('%c[BuyOption] ✅ All address & card data present - Proceeding to CheckOutPanel', 'background: green; color: white; font-weight: bold; padding: 5px;');
const { mbrId, prdtId, prodSno } = response.data.productList[0];
const cartTpSno = `${mbrId}_${prdtId}_${prodSno}`;
// CheckOutPanel 이동 전에 ProductVideoV2 타이머 및 MediaPanel/PlayerPanel 정리
console.log('[BuyOption] API Mode - Cleaning up media panels before checkout');
cleanupVideoAndPlayerPanels();
// 🔴 CRITICAL: 기존 CheckOutPanel 있으면 제거 후 새로 push (API Mode)
dispatch((dispatchFn, getState) => {
const panels = getState().panels?.panels || [];
const checkoutPanelExists = panels.some(p => p.name === Config.panel_names.CHECKOUT_PANEL);
console.log('[BuyOption] 📊 API Mode - Current panels:', panels.map(p => p.name));
// 1⃣ DetailPanel 제거 (STANDALONE_PANEL이므로 다른 패널 제거 필수)
console.log('[BuyOption] 🗑️ API Mode - Removing DetailPanel');
dispatchFn(popPanel(Config.panel_names.DETAIL_PANEL));
// 2⃣ 기존 CheckOutPanel 제거 (있으면)
if (checkoutPanelExists) {
console.log('[BuyOption] 🗑️ API Mode - Removing existing CheckOutPanel');
dispatchFn(popPanel(Config.panel_names.CHECKOUT_PANEL));
}
// 3⃣ 새로운 CheckOutPanel push
console.log('[BuyOption] API Mode - Pushing new CheckOutPanel');
dispatchFn(pushPanel({
name: Config.panel_names.CHECKOUT_PANEL,
panelInfo: { logInfo: { ...logInfo, cartTpSno } },
}));
// 🚀 CheckOutPanel 이동 후 BuyOption Toast 닫기
console.log('[BuyOption] 🔥 Closing BuyOption Toast after CheckOutPanel push');
dispatchFn(clearAllToasts());
});
dispatch(sendLogPaymentEntry({ ...logInfo, cartTpSno }));
}
} else if (response.retCode === 1001) {
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
dispatch(changeAppStatus({ isLoading: false }));
} else {
dispatch(
showError(
response.retCode,
response.retMsg,
false,
response.retDetailCode,
response.returnBindStrings
)
);
dispatch(changeAppStatus({ isLoading: false }));
return;
}
}
Spotlight.focus('buy-option-buy-now-button');
}
const handleBuyNowClick = useCallback((e) => {
e.stopPropagation()
console.log('%c🔥🔥🔥 BUY NOW CLICKED! FUNCTION CALLED! 🔥🔥🔥', 'background: red; color: white; font-size: 16px; font-weight: bold; padding: 5px;');
console.log('%cproductInfo exists:', 'background: red; color: white; padding: 3px;', !!productInfo);
console.log('%cuserNumber exists:', 'background: red; color: white; padding: 3px;', !!userNumber);
console.log('%cselectedOptions exists:', 'background: red; color: white; padding: 3px;', !!selectedOptions);
console.log('%cselectedPatnrId:', 'background: red; color: white; padding: 3px;', selectedPatnrId);
console.log('%cselectedPrdtId:', 'background: red; color: white; padding: 3px;', selectedPrdtId);
// const optionName = renderOptionName();
// const optionValueName = renderOptionValue();
if (productInfo && productInfo?.soldoutFlag === 'Y') {
console.log('%c[BuyOption] ❌ Product is sold out', 'background: red; color: white; padding: 3px;');
return;
}
if (!userNumber || userNumber === '') {
if (!isMockMode) {
console.log('%c[BuyOption] ❌ User not logged in - showing login popup', 'background: red; color: white; padding: 3px;');
dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
return;
}
}
// // 옵션 선택 검증 (SingleOption과 동일)
// if (
// productOptionInfos &&
// productOptionInfos.length > 0 &&
// (optionName === "SELECT" || optionValueName === "SELECT")
// ) {
// console.log("###Test pjh 2", optionName);
// return dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
// }
// Mock Mode가 아닐 때만 옵션 선택 검증
if (!isMockMode) {
//옵션 선택 안하면 구매 안돼도록.
if (
productOptionInfos &&
productOptionInfos.length > 1 &&
productInfo?.optProdYn === 'Y'
) {
if (selectFirstOptionIndex === 0) {
dispatch(
showError(
null,
"PLEASE SELECT OPTION",
false,
null,
null
)
);
return;
}
}
if (
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1 &&
productInfo?.optProdYn === 'Y'
) {
if (selectSecondOptionIndex === 0) {
dispatch(
showError(
null,
"PLEASE SELECT OPTION",
false,
null,
null
)
);
return;
}
}
// 필수 데이터 검증 (API Mode만)
if (!userNumber || !selectedPatnrId || !selectedPrdtId || !quantity) {
console.error('%c[BuyOption] ❌ Missing required data:', 'background: red; color: white; padding: 3px;', {
userNumber: !!userNumber,
selectedPatnrId: !!selectedPatnrId,
selectedPrdtId: !!selectedPrdtId,
quantity: !!quantity
});
dispatch(showError(null, 'Missing required information', false, null, null));
return;
}
}
const effectivePatnrId =
selectedPatnrId || productInfo?.patnrId || 'MOCK_PARTNER';
const effectivePrdtId =
selectedPrdtId || productInfo?.prdtId || 'MOCK_PRODUCT';
const optionForUse = isMockMode
? selectDefaultOptionForMock()
: selectedOptions;
const effectiveQuantity = quantity > 0 ? quantity : 1;
// Mock Mode: 조건 완화 (userNumber 체크 제외)
const shouldProceed = isMockMode
? (effectivePatnrId && effectivePrdtId && effectiveQuantity)
: (userNumber && selectedPatnrId && selectedPrdtId && quantity);
console.log('%c[BuyOption] shouldProceed check:', 'background: purple; color: white; padding: 3px;', {
shouldProceed,
isMockMode,
userNumber: !!userNumber,
selectedPatnrId: !!effectivePatnrId,
selectedPrdtId: !!effectivePrdtId,
quantity
});
if (shouldProceed) {
const { prodOptCval, priceInfo } = optionForUse || {};
const { patncNm, brndNm, catNm, prdtNm, prdtId } = productInfo;
console.log('[BuyOption] handleClickOrder - productInfo:', productInfo);
console.log('[BuyOption] handleClickOrder - selectedOptions:', optionForUse);
console.log('[BuyOption] handleClickOrder - priceInfo:', priceInfo);
console.log('[BuyOption] handleClickOrder - logInfo:', logInfo);
// productInfo에서 직접 price2, price5 추출 (가장 신뢰할 수 있는 소스)
// price2 = 상품 가격 ("$ 521.66" 형식)
// price5 = 쿠폰 할인 ("$ 104.33" 형식)
const extractNumericPrice = (priceStr) => {
if (typeof priceStr === 'number') return priceStr;
if (typeof priceStr === 'string') {
const cleanedValue = priceStr.replace(/,/g, '');
const numMatch = cleanedValue.match(/[\d.]+/);
return numMatch ? parseFloat(numMatch[0]) : 0;
}
return 0;
};
const price3Value = extractNumericPrice(productInfo?.price3 ? productInfo?.price3 : productInfo?.price2);
const price5Value = extractNumericPrice(productInfo?.price5);
console.log('[BuyOption] handleClickOrder - productInfo.price2:', productInfo?.price3, '-> extracted:', price3Value);
console.log('[BuyOption] handleClickOrder - productInfo.price5:', productInfo?.price5, '-> extracted:', price5Value);
// 가격 계산:
// discountPrice = price2 (상품 가격)
// regularPrice = price2 + price5 (상품 가격 + 할인액 = 원래 가격)
let discountPrice = price3Value > 0 ? price3Value : 0;
let regularPrice = price3Value + price5Value; // price2 + price5 = 원래 가격
const discountRate = priceInfo?.split('|')[4];
console.log('[BuyOption] handleClickOrder - Calculated prices - regularPrice:', regularPrice, 'discountPrice:', discountPrice);
// selectedOptions가 없고 logInfo가 있으면, logInfo의 가격도 고려
if (!optionForUse && logInfo) {
console.log('[BuyOption] handleClickOrder - selectedOptions is undefined, checking logInfo prices');
// logInfo의 dcBefPrc와 dcAftrPrc는 "$ 521.66" 형식이므로 숫자만 추출 (소수점 포함)
const dcBefPrcMatch = logInfo.dcBefPrc?.match(/[\d.]+/);
const dcAftrPrcMatch = logInfo.dcAftrPrc?.match(/[\d.]+/);
const logRegularPrice = dcBefPrcMatch ? parseFloat(dcBefPrcMatch[0]) : 0;
const logDiscountPrice = dcAftrPrcMatch ? parseFloat(dcAftrPrcMatch[0]) : logRegularPrice;
// logInfo의 가격이 있으면 우선
if (logRegularPrice > 0 || logDiscountPrice > 0) {
regularPrice = logRegularPrice > 0 ? logRegularPrice : regularPrice;
discountPrice = logDiscountPrice > 0 ? logDiscountPrice : discountPrice;
console.log('[BuyOption] handleClickOrder - Using logInfo prices - regularPrice:', regularPrice, 'discountPrice:', discountPrice);
}
}
console.log('[BuyOption] handleClickOrder - Final prices - regularPrice:', regularPrice, 'discountPrice:', discountPrice, 'discountRate:', discountRate);
dispatch(
sendLogTotalRecommend({
nowMenu: nowMenu,
productId: prdtId,
productTitle: prdtNm,
partner: patncNm,
price: discountRate ? discountPrice : regularPrice,
discount: discountRate,
brand: brndNm,
productOption: prodOptCval || optionForUse?.optNm || optionForUse?.prodOptCval || '',
category: catNm,
contextName: Config.LOG_CONTEXT_NAME.DETAILPAGE,
messageId: Config.LOG_MESSAGE_ID.BUY_NOW,
})
);
// Mock Mode: API 호출 스킵
console.log('%c[BuyOption] 🔍 CHECK BUYNOW_CONFIG.isMockMode() at BUY NOW time:', 'background: orange; color: white; font-weight: bold; padding: 5px;', {
isMockMode: BUYNOW_CONFIG.isMockMode(),
localIsMockMode: isMockMode,
});
if (!BUYNOW_CONFIG.isMockMode()) {
console.log('%c[BuyOption] 🚀 API MODE DETECTED - Calling getMyInfoCheckoutInfo', 'background: green; color: white; font-weight: bold; padding: 5px;');
console.log('[BuyOption] API 파라미터:', {
mbrNo: userNumber,
dirPurcSelYn: 'Y',
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
prodQty: String(quantity),
});
dispatch(
getMyInfoCheckoutInfo(
{
mbrNo: userNumber,
dirPurcSelYn: 'Y',
cartList: [
{
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
prodOptCdCval: optionForUse?.prodOptCdCval
? optionForUse.prodOptCdCval
: null,
prodQty: String(quantity),
prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval,
},
],
},
checkOutValidate
)
);
} else {
// Mock Mode: 체크아웃 페이지로 이동 (시뮬레이션)
// panelInfo의 logInfo에 선택한 상품 정보를 포함시켜 CheckOutPanel에서 사용
console.log('%c[BuyOption] 🟠 MOCK MODE DETECTED - Simulating checkout WITHOUT API', 'background: orange; color: white; font-weight: bold; padding: 5px;');
console.log('[BuyOption] logInfo:', logInfo);
console.log('[BuyOption] Dispatching pushPanel to CHECKOUT_PANEL');
// CheckOutPanel 이동 전에 ProductVideoV2 타이머 및 MediaPanel/PlayerPanel 정리
console.log('[BuyOption] Mock Mode - Cleaning up media panels before checkout');
cleanupVideoAndPlayerPanels();
// Mock 모드: 선택 상품의 정보를 panelInfo에 담아서 전달
// CheckOutPanel에서 이 정보로 Mock 상품 데이터 생성
// 이미지 URL 추출 (productInfo의 imgList 또는 thumbnailUrl 사용)
const imgUrl = productInfo?.imgList?.[0]?.imgUrl ||
productInfo?.thumbnailUrl ||
productInfo?.patncLogPath ||
'/mock/image.jpg';
const mockProductInfo = {
prdtId: effectivePrdtId,
prdtNm: productInfo?.prdtNm,
patnrId: effectivePatnrId,
patncNm: patncNm,
prodQty: effectiveQuantity,
origPrice: regularPrice || 99999, // 원래 가격 (숫자)
discountPrice: discountPrice || regularPrice || 99999, // 할인된 가격 (실제 판매 가격, 숫자)
finalPrice: discountPrice || regularPrice || 99999, // 최종 가격 (숫자)
currSign: '$',
currSignLoc: 'left',
imgUrl: imgUrl, // 상품 이미지 URL 추가
};
// Mock Mode: productInfo 최소 검증만 수행
if (!productInfo || !productInfo.prdtId) {
console.error('%c[BuyOption] ❌ Mock Mode - productInfo is invalid:', 'background: red; color: white; font-weight: bold; padding: 5px;', productInfo);
// Mock Mode에서는 기본값으로 진행 시도
console.log('%c[BuyOption] Mock Mode - Attempting to proceed with fallback data', 'background: orange; color: white; padding: 3px;');
} else {
console.log('%c[BuyOption] ✅ Mock Mode - productInfo is valid:', 'background: green; color: white; font-weight: bold; padding: 5px;', productInfo.prdtId);
}
const checkoutPanelInfo = {
logInfo: { ...logInfo, cartTpSno: `MOCK_${Date.now()}` },
productInfo: {
prdtId: productInfo?.prdtId || effectivePrdtId || 'MOCK_PRODUCT',
prdtNm: productInfo?.prdtNm || 'Mock Product',
patnrId: effectivePatnrId || '1',
patncNm: patncNm || 'Mock Partner',
// calculateOrderSummaryFromProductInfo 함수가 필요한 필드들
// productInfo에서 직접 추출한 값을 전달
price2: price3Value, // Items (상품 가격)
price5: price5Value, // Coupon Savings (할인액)
finalPrice: discountPrice,
discount: price5Value,
origPrice: regularPrice,
discountPrice: discountPrice,
prodQty: quantity,
// 추가 가격 필드들 (fallback용)
price: discountPrice,
originalPrice: regularPrice,
// 이미지 정보
...(productInfo.imgList && { imgList: productInfo.imgList }),
...(productInfo.thumbnailUrl && { thumbnailUrl: productInfo.thumbnailUrl }),
...(productInfo.imgUrls && { imgUrls: productInfo.imgUrls }),
shippingCharge: productInfo?.shippingFee || '12.99', // 배송비
shippingFee: productInfo?.shippingFee || '12.99',
currSign: '$',
currSignLoc: 'left',
},
defaultPrice: discountPrice,
};
console.log('[BuyOption] Mock Mode - checkoutPanelInfo.productInfo price fields:', {
price2: price3Value,
price5: price5Value,
finalPrice: discountPrice,
origPrice: regularPrice,
discountPrice: discountPrice,
});
console.log('[BuyOption] Mock Mode - checkoutPanelInfo:', checkoutPanelInfo);
console.log('[BuyOption] Mock Mode - regularPrice:', regularPrice, 'discountPrice:', discountPrice);
console.log('%c[BuyOption] 🔍 Determining UPDATE vs PUSH for CheckOutPanel', 'background: orange; color: white; font-weight: bold; padding: 5px;');
// Mock Mode: checkoutPanelInfo가 있으면 무조건 진행
if (!checkoutPanelInfo) {
console.error('%c[BuyOption] ❌ Mock Mode - checkoutPanelInfo is null/undefined', 'background: red; color: white; padding: 3px;');
// 최소한의 fallback 데이터로 진행 시도
const fallbackPanelInfo = {
logInfo: { cartTpSno: `MOCK_FALLBACK_${Date.now()}` },
productInfo: {
prdtId: effectivePrdtId || 'MOCK_PRODUCT',
prdtNm: 'Mock Product',
patnrId: effectivePatnrId || '1',
patncNm: 'Mock Partner',
finalPrice: 99999,
origPrice: 99999,
discountPrice: 99999,
currSign: '$',
currSignLoc: 'left',
},
defaultPrice: 99999,
};
console.log('%c[BuyOption] Mock Mode - Using fallback panelInfo', 'background: orange; color: white; padding: 3px;', fallbackPanelInfo);
// 🔴 CRITICAL: DetailPanel Pop 완료 후 CheckOutPanel Push (Promise 체인)
dispatch((dispatchFn) => {
return new Promise((resolve) => {
console.log('[BuyOption] 1⃣ Popping DetailPanel...');
dispatchFn(popPanel(Config.panel_names.DETAIL_PANEL));
// Pop이 끝난 후 Push 실행
Promise.resolve().then(() => {
console.log('[BuyOption] 2⃣ Pushing CheckOutPanel...');
dispatchFn(
pushPanel({
name: Config.panel_names.CHECKOUT_PANEL,
panelInfo: fallbackPanelInfo,
})
);
console.log('[BuyOption] ✅ Panel transition complete');
resolve();
});
});
});
} else {
// 정상 케이스: checkoutPanelInfo 사용
// 🔴 CRITICAL: DetailPanel Pop 완료 후 CheckOutPanel Push (Promise 체인)
dispatch((dispatchFn) => {
return new Promise((resolve) => {
console.log('[BuyOption] 1⃣ Normal - Popping DetailPanel...');
dispatchFn(popPanel(Config.panel_names.DETAIL_PANEL));
// Pop이 끝난 후 Push 실행
Promise.resolve().then(() => {
console.log('[BuyOption] 2⃣ Normal - Pushing CheckOutPanel...');
dispatchFn(
pushPanel({
name: Config.panel_names.CHECKOUT_PANEL,
panelInfo: checkoutPanelInfo,
})
);
console.log('[BuyOption] ✅ Normal - Panel transition complete');
resolve();
});
});
});
}
console.log('%c[BuyOption] ✅ AFTER pushPanel dispatch', 'background: orange; color: white; font-weight: bold; padding: 5px;');
// Toast 정리는 성공적으로 패널 이동 후에만 실행
// dispatch(clearAllToasts());
}
}
}, [
dispatch,
userNumber,
selectedPatnrId,
selectedPrdtId,
selectedOptions,
productInfo,
productOptionInfos,
quantity,
logInfo,
selectFirstOptionIndex,
selectSecondOptionIndex,
nowMenu,
isMockMode,
selectDefaultOptionForMock,
cleanupVideoAndPlayerPanels,
]);
// ADD TO CART 버튼 클릭 핸들러
const handleAddToCartClick = useCallback((e) => {
e.stopPropagation();
console.log('[BuyOption] ADD TO CART clicked');
const isMock = isMockMode;
// 상품 품절 체크 (Mock Mode에서는 우회)
if (productInfo && productInfo?.soldoutFlag === 'Y' && !isMock) {
return;
}
// 로그인 체크 (Mock Mode에서는 우회)
if ((!userNumber || userNumber === '') && !isMock) {
return dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
}
const effectivePatnrId =
selectedPatnrId || productInfo?.patnrId || 'MOCK_PARTNER';
const effectivePrdtId =
selectedPrdtId || productInfo?.prdtId || 'MOCK_PRODUCT';
const effectiveQuantity = quantity > 0 ? quantity : 1;
const optionForUse = isMock ? selectDefaultOptionForMock() : selectedOptions;
// API Mode 조건 검증
if (
!isMock &&
(!userNumber || !selectedPatnrId || !selectedPrdtId || !quantity)
) {
return;
}
const parseNumericPrice = (value) => {
if (typeof value === 'number') return value;
if (!value) return 0;
const cleaned = String(value).replace(/,/g, '');
const match = cleaned.match(/-?[\d.]+/);
return match ? parseFloat(match[0]) : 0;
};
const priceInfoSource =
optionForUse?.priceInfo ||
productInfo?.priceInfo ||
'0|0|0|0%|0';
const priceParts = priceInfoSource.split('|');
const regularPriceValue = parseNumericPrice(priceParts[0]);
const discountPriceValue =
priceParts.length > 1
? parseNumericPrice(priceParts[1])
: regularPriceValue;
const optionLabel =
optionForUse?.optNm || optionForUse?.prodOptCval || '';
if (!isMock) {
if (
productOptionInfos &&
productOptionInfos.length > 1 &&
productInfo?.optProdYn === 'Y'
) {
if (selectFirstOptionIndex === 0) {
dispatch(
showError(
null,
"PLEASE SELECT OPTION",
false,
null,
null
)
);
return;
}
}
if (
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1 &&
productInfo?.optProdYn === 'Y'
) {
if (selectSecondOptionIndex === 0) {
dispatch(
showError(
null,
"PLEASE SELECT OPTION",
false,
null,
null
)
);
return;
}
}
dispatch(
insertMyinfoCart({
mbrNo: userNumber,
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
prodQty: Number(effectiveQuantity),
prdtOpt: [{
prodOptSno: productOptionInfos?.[0]?.prodOptSno || '',
prodOptCdCval: optionForUse?.prodOptCdCval || '',
prodOptTpCdCval: productOptionInfos?.[0]?.prodOptTpCdCval || '',
prodOptCval: optionLabel,
}],
})
);
const imgUrl =
productInfo?.imgUrls600?.[0] ||
productInfo?.thumbnailUrl960 ||
productInfo?.imgList?.[0]?.imgUrl ||
productInfo?.thumbnailUrl ||
productInfo?.patncLogPath ||
'/assets/images/img-thumb-empty-144@3x.png';
const productInfoForCart = {
prdtId: effectivePrdtId,
prdtNm: productInfo?.prdtNm || 'Product',
patnrId: effectivePatnrId,
patncNm: productInfo?.patncNm || 'Partner',
patncLogPath: productInfo?.patncLogPath || '',
imgUrl,
thumbnailUrl: productInfo?.thumbnailUrl960 || imgUrl,
thumbnailUrl960: productInfo?.thumbnailUrl960,
imgList: productInfo?.imgList || [{ imgUrl }],
imgUrls: productInfo?.imgUrls || [{ imgUrl }],
imgUrls600: productInfo?.imgUrls600,
price2: regularPriceValue.toFixed(2),
price3: discountPriceValue.toFixed(2),
finalPrice: discountPriceValue,
discountPrice: discountPriceValue,
origPrice: regularPriceValue,
discount: Math.max(0, regularPriceValue - discountPriceValue),
priceInfo: priceInfoSource,
prodQty: effectiveQuantity,
optNm: optionLabel,
shippingCharge: productInfo?.shippingFee || '12.99',
soldoutFlag: productInfo?.soldoutFlag || 'N',
};
const optionInfoForCart = {
name: optionLabel,
price: optionForUse?.optPrc || '0.00',
};
dispatch(setShowPopup(Config.ACTIVE_POPUP.addCartPopup));
return;
// dispatch(
// pushPanel({
// name: Config.panel_names.CART_PANEL,
// panelInfo: {
// productInfo: productInfoForCart,
// optionInfo: optionInfoForCart,
// quantity: effectiveQuantity,
// },
// })
// );
} else {
console.log('[BuyOption] Mock Mode - Adding to cart (Mock)');
const imgUrl =
productInfo?.imgUrls600?.[0] ||
productInfo?.thumbnailUrl960 ||
productInfo?.imgList?.[0]?.imgUrl ||
productInfo?.thumbnailUrl ||
productInfo?.patncLogPath ||
'/assets/images/img-thumb-empty-144@3x.png';
const mockProductInfo = {
prdtId: effectivePrdtId,
prdtNm: productInfo?.prdtNm || 'Mock Product',
patnrId: effectivePatnrId,
patncNm: productInfo?.patncNm || 'Mock Partner',
patncLogPath: productInfo?.patncLogPath || '',
imgUrl,
thumbnailUrl: productInfo?.thumbnailUrl960 || imgUrl,
thumbnailUrl960: productInfo?.thumbnailUrl960,
imgList: productInfo?.imgList || [{ imgUrl }],
imgUrls: productInfo?.imgUrls || [{ imgUrl }],
imgUrls600: productInfo?.imgUrls600,
price2: regularPriceValue.toFixed(2),
price3: discountPriceValue.toFixed(2),
finalPrice: discountPriceValue,
discountPrice: discountPriceValue,
origPrice: regularPriceValue,
discount: Math.max(0, regularPriceValue - discountPriceValue),
priceInfo: priceInfoSource,
prodQty: effectiveQuantity,
optNm: optionLabel || 'Default Option',
shippingCharge: productInfo?.shippingFee || '12.99',
shippingFee: productInfo?.shippingFee || '12.99',
soldoutFlag: productInfo?.soldoutFlag || 'N',
};
const optionInfo = {
name: optionLabel || 'Default Option',
price: optionForUse?.optPrc || discountPriceValue.toFixed(2),
};
dispatch(
pushPanel({
name: Config.panel_names.CART_PANEL,
panelInfo: {
productInfo: mockProductInfo,
optionInfo,
quantity: effectiveQuantity,
},
})
);
}
// dispatch(clearAllToasts());
}, [
dispatch,
userNumber,
selectedPatnrId,
selectedPrdtId,
selectedOptions,
productInfo,
productOptionInfos,
quantity,
isMockMode,
selectDefaultOptionForMock,
]);
const handleFirstOptionSelect = (selected) => {
const selectedIndex = selected.selected;
console.log('[BuyOption] First option selected:', selectedIndex);
// "Select Option"이 선택된 경우 (index 0)
if (selectedIndex === 0) {
// 초기 상태로 리셋
setSelectFirstOptionIndex(0);
setSelectedBtnOptIdx(0);
setSelectedOptionItemIndex(0);
setSelectedOptions(undefined);
setIsOptionValue(false);
setIsOptionSelect(false);
return;
}
// selectedBtnOptIdx는 UI 인덱스 그대로 저장 (드롭다운 표시용)
setSelectedBtnOptIdx(selectedIndex);
setSelectFirstOptionIndex(selectedIndex);
// 실제 데이터 접근 시에는 selectedIndex - 1 사용
const dataIndex = selectedIndex - 1;
setSelectedOptionItemIndex(0);
setSelectedOptions(productOptionInfos[dataIndex]?.prdtOptDtl[0]);
setIsOptionValue(false);
setIsOptionSelect(true);
};
const handleSecondOptionSelect = (selected) => {
const selectedIndex = selected.selected;
console.log('[BuyOption] Second option selected:', selectedIndex);
// selectedOptionItemIndex는 UI 인덱스 그대로 저장
setSelectedOptionItemIndex(selectedIndex);
setSelectSecondOptionIndex(selectedIndex);
// 실제 데이터 접근 시에는 -1 적용
const firstOptionDataIndex = selectedBtnOptIdx - 1; // 첫 번째 옵션의 실제 데이터 인덱스
const secondOptionDataIndex = selectedIndex - 1; // 두 번째 옵션의 실제 데이터 인덱스
setSelectedOptions(
productOptionInfos.length > 1
? productOptionInfos[firstOptionDataIndex]?.prdtOptDtl[
secondOptionDataIndex
]
: productOptionInfos[0]?.prdtOptDtl[secondOptionDataIndex]
);
dispatch(
getProductOptionId(
productOptionInfos.length > 1
? productOptionInfos[firstOptionDataIndex]?.prdtOptDtl[
secondOptionDataIndex
]?.prodOptCdCval
: productOptionInfos[0]?.prdtOptDtl[secondOptionDataIndex]
?.prodOptCdCval
)
);
setIsOptionValue(true);
};
// 수량 선택 핸들러
const handleQuantitySelect = (selected) => {
const qty = selected.selected + 1;
console.log('[BuyOption] Quantity selected:', qty);
setQuantity(qty);
};
// // 옵션명 렌더링 함수 (SingleOption과 동일)
// const renderOptionName = useCallback(() => {
// if (selectedOptions) {
// return productOptionInfos[selectedBtnOptIdx]?.optNm || null;
// }
// return $L("SELECT");
// }, [productOptionInfos, selectedOptions, selectedBtnOptIdx]);
// // 옵션값 렌더링 함수 (SingleOption과 동일)
// const renderOptionValue = useCallback(() => {
// if (
// productOptionInfos &&
// productOptionInfos[selectedBtnOptIdx] &&
// productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
// productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 0 &&
// isOptionValue
// ) {
// return (
// productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl[
// selectedOptionItemIndex
// ]?.prodOptCval || null
// );
// }
// return $L("SELECT");
// }, [
// productOptionInfos,
// selectedBtnOptIdx,
// isOptionValue,
// selectedOptionItemIndex,
// ]);
// Favorite 플래그 업데이트 (SingleOption과 동일)
useEffect(() => {
setFavoriteFlag(productInfo?.favorYn ? productInfo?.favorYn : 'N');
}, [productInfo]);
// Favorite 플래그 변경 콜백 (SingleOption과 동일)
const onFavoriteFlagChanged = useCallback((ev) => {
setFavoriteFlag(ev);
}, []);
// 컴포넌트 마운트 시 상품 정보 출력
useEffect(() => {
console.log('[BuyOption]', '상품 정보:', JSON.stringify(productInfo));
}, []); // 컴포넌트 마운트 시 한 번만 출력
// hasOnClose 로직 (SingleOption과 동일)
const hasOnClose = useMemo(() => {
if (productOptionInfos && productOptionInfos.length > 0) {
return true;
}
return false;
}, [productOptionInfos, isOptionValue, isOptionSelect, selectedOptions]);
// 로그인 팝업 텍스트 로직 (SingleOption과 동일)
const loginPopupText = useMemo(() => {
if (!userNumber) {
return $L('Would you like to sign in?');
}
if (!hasOnClose) {
return $L('Please select Option');
}
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') {
currentSpot = spotlightId;
} else {
currentSpot = 'buy-option-add-to-cart-button';
}
if (currentSpot) {
setTimeout(() => {
Spotlight.focus(currentSpot);
});
}
},
[dispatch]
);
// 로그인 팝업 열기 핸들러 (SingleOption과 동일)
const handleLoginPopUpOpen = useCallback(() => {
if (!userNumber) {
if (webOSVersion >= '6.0') {
setTimeout(() => {
Spotlight.focus('buy-option-buy-now-button');
});
dispatch(launchMembershipApp());
} else {
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
}
return;
}
if (hasOnClose) {
dispatch(setHidePopup());
let spotlightId = 'buy-option-first-dropdown';
//옵션이 하나만 있는경우 isOptionValue === false
if (!isOptionValue) {
spotlightId = 'buy-option-second-dropdown';
}
setTimeout(() => {
Spotlight.focus(spotlightId);
}, 100);
return;
}
}, [dispatch, hasOnClose, isOptionValue, webOSVersion, userNumber]);
useEffect(()=>{
return(()=>{
dispatch(clearAllToasts());
})
},[])
const handleCartMove = useCallback(() => {
dispatch(setHidePopup());
clearAllToasts();
dispatch(
pushPanel({
name: Config.panel_names.CART_PANEL,
})
)
},[dispatch])
return (
<Container
className={styles.buy_option}
>
<div className={styles.buy_option__left_section}>
{/* 동적 옵션 렌더링 */}
{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={[
'Select Option',
...(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}>
{productOptionInfos.length === 1
? 'OPTION'
: 'OPTION 2'}
</div>
</div>
<div className={styles.buy_option__option_control}>
<CustomDropDown
options={[
'Select Option',
...(productOptionInfos[
productOptionInfos.length > 1
? selectedBtnOptIdx - 1
: selectedBtnOptIdx
]?.prdtOptDtl.map((detail) => ({
label: detail.prodOptCval,
disabled: detail.optStkQty <= 0,
imageUrl: detail.optImgUrl || null,
price: detail.priceInfo.split('|')[1],
prodOptCdCval: detail.prodOptCdCval,
})) || []),
]}
selectedIndex={selectedOptionItemIndex}
onSelect={handleSecondOptionSelect}
spotlightId="buy-option-second-dropdown"
onSpotlightUp={handleArrowUpWithinBuyOption}
/>
</div>
</div>
)}
</>
)}
{/* 수량 선택 */}
<div className={styles.buy_option__option_row}>
<div className={styles.buy_option__option_label}>
<div className={styles.buy_option__label_text}>QUANTITY</div>
</div>
<div className={styles.buy_option__option_control}>
{/* <CustomDropDown
options={["1", "2", "3", "4", "5"]}
selectedIndex={quantity - 1}
onSelect={handleQuantitySelect}
spotlightId="buy-option-quantity-dropdown"
/> */}
<ProductQuantity
onSpotlightUp={handleArrowUpWithinBuyOption}
setQuantity={setQuantity}
quantity={quantity}
/>
</div>
</div>
</div>
<div className={styles.buy_option__right_section}>
<BuyOptionPriceBlock
className={styles.buy_option__price_block}
productInfo={productInfo || productData}
selectedOptions={selectedOptions}
patncNm={patncNm || productData?.patncNm || productInfo?.patncNm}
quantity={quantity}
/>
<div className={styles.buy_option__button_section}>
<TButton
className={styles.buy_option__buy_button}
spotlightId="buy-option-buy-now-button"
size="detailButton"
onClick={handleBuyNowClick}
onSpotlightUp={handleArrowUpWithinBuyOption}
>
<span className={styles.buy_option__button_text}>BUY NOW</span>
</TButton>
<TButton
className={styles.buy_option__cart_button}
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>
</div>
</div>
{/* LOGIN POPUP */}
{activePopup === Config.ACTIVE_POPUP.loginPopup && (
<TPopUp
kind="textPopup"
hasText
open={popupVisible}
text={loginPopupText}
hasButton
hasOnClose={hasOnClose}
button1Text={$L('OK')}
button2Text={$L('CANCEL')}
onClick={handleLoginPopUpOpen}
onClose={onClose}
/>
)}
{activePopup === Config.ACTIVE_POPUP.addCartPopup && (
<TPopUp
kind="textPopup"
hasText
open={popupVisible}
text={"Added to Cart"}
hasButton
hasOnClose={hasOnClose}
button1Text={$L('VIEW CART')}
button2Text={$L('CONTINUE SHOPPING')}
onClick={handleCartMove}
onClose={onClose}
/>
)}
</Container>
);
};
export default BuyOption;