diff --git a/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx b/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx index 9b3e3ced..9311440f 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx @@ -29,12 +29,11 @@ import { sendLogTotalRecommend, } from '../../../actions/logActions'; import { finishMediaPreview } from '../../../actions/mediaActions'; -import { addToMockCart } from '../../../actions/mockCartActions'; import { popPanel, pushPanel, } from '../../../actions/panelActions'; -import { clearAllVideoTimers } from '../../../actions/playActions'; +import { clearAllVideoTimers, finishVideoPreview } from '../../../actions/playActions'; import { getProductOption, getProductOptionId, @@ -163,9 +162,103 @@ const BuyOption = ({ // Mock Mode이고 유효한 옵션 데이터가 있으면 그대로 사용 console.log('[BuyOption] Mock Mode - using existing valid reduxProductOptionInfos'); return reduxProductOptionInfos; - }, [reduxProductOptionInfos, productData, isMockMode]); // logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용) + }, [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과 동일하게 처리 @@ -375,9 +468,7 @@ const BuyOption = ({ // CheckOutPanel 이동 전에 ProductVideoV2 타이머 및 MediaPanel/PlayerPanel 정리 console.log('[BuyOption] API Mode - Cleaning up media panels before checkout'); - clearAllVideoTimers(); // ProductVideoV2의 타이머 정리 - dispatch(finishMediaPreview()); // MediaPanel 정리 - dispatch(popPanel(Config.panel_names.PLAYER_PANEL)); // PlayerPanel 제거 + cleanupVideoAndPlayerPanels(); // 🔴 CRITICAL: 기존 CheckOutPanel 있으면 제거 후 새로 push (API Mode) dispatch((dispatchFn, getState) => { @@ -443,9 +534,11 @@ const BuyOption = ({ return; } if (!userNumber || userNumber === '') { - console.log('%c[BuyOption] ❌ User not logged in - showing login popup', 'background: red; color: white; padding: 3px;'); - dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup)); - return; + 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과 동일) @@ -459,7 +552,7 @@ const BuyOption = ({ // } // Mock Mode가 아닐 때만 옵션 선택 검증 - if (!BUYNOW_CONFIG.isMockMode()) { + if (!isMockMode) { //옵션 선택 안하면 구매 안돼도록. if ( productOptionInfos && @@ -513,39 +606,37 @@ const BuyOption = ({ dispatch(showError(null, 'Missing required information', false, null, null)); return; } - } else { - // Mock Mode: 최소한의 검증만 수행 - console.log('%c[BuyOption] Mock Mode - Bypassing option validation', 'background: blue; color: white; padding: 3px;'); - if (!selectedPatnrId || !selectedPrdtId) { - console.error('%c[BuyOption] ❌ Mock Mode - Missing critical IDs:', 'background: red; color: white; padding: 3px;', { - selectedPatnrId: !!selectedPatnrId, - selectedPrdtId: !!selectedPrdtId - }); - dispatch(showError(null, 'Product ID is missing', 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 = BUYNOW_CONFIG.isMockMode() - ? (selectedPatnrId && selectedPrdtId && quantity) + const shouldProceed = isMockMode + ? (effectivePatnrId && effectivePrdtId && effectiveQuantity) : (userNumber && selectedPatnrId && selectedPrdtId && quantity); console.log('%c[BuyOption] shouldProceed check:', 'background: purple; color: white; padding: 3px;', { shouldProceed, - isMockMode: BUYNOW_CONFIG.isMockMode(), + isMockMode, userNumber: !!userNumber, - selectedPatnrId: !!selectedPatnrId, - selectedPrdtId: !!selectedPrdtId, + selectedPatnrId: !!effectivePatnrId, + selectedPrdtId: !!effectivePrdtId, quantity }); if (shouldProceed) { - const { prodOptCval, priceInfo } = selectedOptions || {}; + const { prodOptCval, priceInfo } = optionForUse || {}; const { patncNm, brndNm, catNm, prdtNm, prdtId } = productInfo; console.log('[BuyOption] handleClickOrder - productInfo:', productInfo); - console.log('[BuyOption] handleClickOrder - selectedOptions:', selectedOptions); + console.log('[BuyOption] handleClickOrder - selectedOptions:', optionForUse); console.log('[BuyOption] handleClickOrder - priceInfo:', priceInfo); console.log('[BuyOption] handleClickOrder - logInfo:', logInfo); @@ -579,7 +670,7 @@ const BuyOption = ({ console.log('[BuyOption] handleClickOrder - Calculated prices - regularPrice:', regularPrice, 'discountPrice:', discountPrice); // selectedOptions가 없고 logInfo가 있으면, logInfo의 가격도 고려 - if (!selectedOptions && 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.]+/); @@ -606,7 +697,7 @@ const BuyOption = ({ price: discountRate ? discountPrice : regularPrice, discount: discountRate, brand: brndNm, - productOption: prodOptCval || '', + productOption: prodOptCval || optionForUse?.optNm || optionForUse?.prodOptCval || '', category: catNm, contextName: Config.LOG_CONTEXT_NAME.DETAILPAGE, messageId: Config.LOG_MESSAGE_ID.BUY_NOW, @@ -624,8 +715,8 @@ const BuyOption = ({ { patnrId: selectedPatnrId, prdtId: selectedPrdtId, - prodOptCdCval: selectedOptions?.prodOptCdCval - ? selectedOptions.prodOptCdCval + prodOptCdCval: optionForUse?.prodOptCdCval + ? optionForUse.prodOptCdCval : null, prodQty: String(quantity), prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval, @@ -644,9 +735,7 @@ const BuyOption = ({ // CheckOutPanel 이동 전에 ProductVideoV2 타이머 및 MediaPanel/PlayerPanel 정리 console.log('[BuyOption] Mock Mode - Cleaning up media panels before checkout'); - clearAllVideoTimers(); // ProductVideoV2의 타이머 정리 - dispatch(finishMediaPreview()); // MediaPanel 정리 - dispatch(popPanel(Config.panel_names.PLAYER_PANEL)); // PlayerPanel 제거 + cleanupVideoAndPlayerPanels(); // Mock 모드: 선택 상품의 정보를 panelInfo에 담아서 전달 // CheckOutPanel에서 이 정보로 Mock 상품 데이터 생성 @@ -657,11 +746,11 @@ const BuyOption = ({ '/mock/image.jpg'; const mockProductInfo = { - prdtId: selectedPrdtId, + prdtId: effectivePrdtId, prdtNm: productInfo?.prdtNm, - patnrId: selectedPatnrId, + patnrId: effectivePatnrId, patncNm: patncNm, - prodQty: quantity, + prodQty: effectiveQuantity, origPrice: regularPrice || 99999, // 원래 가격 (숫자) discountPrice: discountPrice || regularPrice || 99999, // 할인된 가격 (실제 판매 가격, 숫자) finalPrice: discountPrice || regularPrice || 99999, // 최종 가격 (숫자) @@ -681,11 +770,11 @@ const BuyOption = ({ const checkoutPanelInfo = { logInfo: { ...logInfo, cartTpSno: `MOCK_${Date.now()}` }, - productInfo: { - prdtId: productInfo?.prdtId || selectedPrdtId || 'MOCK_PRODUCT', - prdtNm: productInfo?.prdtNm || 'Mock Product', - patnrId: selectedPatnrId || '1', - patncNm: patncNm || 'Mock Partner', + productInfo: { + prdtId: productInfo?.prdtId || effectivePrdtId || 'MOCK_PRODUCT', + prdtNm: productInfo?.prdtNm || 'Mock Product', + patnrId: effectivePatnrId || '1', + patncNm: patncNm || 'Mock Partner', // calculateOrderSummaryFromProductInfo 함수가 필요한 필드들 // productInfo에서 직접 추출한 값을 전달 price2: price3Value, // Items (상품 가격) @@ -694,7 +783,7 @@ const BuyOption = ({ discount: price5Value, origPrice: regularPrice, discountPrice: discountPrice, - prodQty:quantity, + prodQty: quantity, // 추가 가격 필드들 (fallback용) price: discountPrice, originalPrice: regularPrice, @@ -730,9 +819,9 @@ const BuyOption = ({ const fallbackPanelInfo = { logInfo: { cartTpSno: `MOCK_FALLBACK_${Date.now()}` }, productInfo: { - prdtId: selectedPrdtId || 'MOCK_PRODUCT', + prdtId: effectivePrdtId || 'MOCK_PRODUCT', prdtNm: 'Mock Product', - patnrId: selectedPatnrId || '1', + patnrId: effectivePatnrId || '1', patncNm: 'Mock Partner', finalPrice: 99999, origPrice: 99999, @@ -805,308 +894,181 @@ const BuyOption = ({ logInfo, selectFirstOptionIndex, selectSecondOptionIndex, + nowMenu, + isMockMode, + selectDefaultOptionForMock, + cleanupVideoAndPlayerPanels, ]); // ADD TO CART 버튼 클릭 핸들러 const handleAddToCartClick = useCallback(() => { console.log('[BuyOption] ADD TO CART clicked'); - // 상품 품절 체크 - if (productInfo && productInfo?.soldoutFlag === 'Y') { + const isMock = isMockMode; + + // 상품 품절 체크 (Mock Mode에서는 우회) + if (productInfo && productInfo?.soldoutFlag === 'Y' && !isMock) { return; } - // 로그인 체크 - if (!userNumber || userNumber === '') { + // 로그인 체크 (Mock Mode에서는 우회) + if ((!userNumber || userNumber === '') && !isMock) { return dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup)); } - // 장바구니에 상품 추가 - 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 effectivePatnrId = + selectedPatnrId || productInfo?.patnrId || 'MOCK_PARTNER'; + const effectivePrdtId = + selectedPrdtId || productInfo?.prdtId || 'MOCK_PRODUCT'; + const effectiveQuantity = quantity > 0 ? quantity : 1; + const optionForUse = isMock ? selectDefaultOptionForMock() : selectedOptions; - // 로그 전송 - // dispatch( - // sendLogTotalRecommend({ - // nowMenu: nowMenu, - // productId: prdtId, - // productTitle: prdtNm, - // partner: patncNm, - // price: discountRate ? discountPrice : regularPrice, - // discount: discountRate, - // brand: brndNm, - // productOption: prodOptCval || '', - // category: catNm, - // contextName: Config.LOG_CONTEXT_NAME.DETAILPAGE, - // messageId: Config.LOG_MESSAGE_ID.ADD_TO_CART, - // }) - // ); - - // API Mode: 실제 API 호출 후 CartPanel로 이동 - if (!BUYNOW_CONFIG.isMockMode()) { - // 장바구니에 추가 - dispatch( - addToCart({ - mbrNo: userNumber, - patnrId: selectedPatnrId, - prdtId: selectedPrdtId, - prodQty: String(quantity), - prdtOpt: { - prodOptCdCval: selectedOptions?.prodOptCdCval - ? selectedOptions.prodOptCdCval - : "", - prodOptCval:productOptionInfos[0]?.optNm, - prodOptSno: productOptionInfos[0]?.prodOptSno, - prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval, - } - }) - ); - - // CartPanel로 이동 (productInfo 포함) - API에서는 이미 addToCart 호출됨 - // 이미지 URL 구성 (CheckOutPanel과 동일한 방식) - // ✅ 이미지 URL 추출 (ProductAllSection의 고품질 이미지 우선) - const imgUrl = productInfo?.imgUrls600?.[0] || // ✅ ProductAllSection에서 사용하는 고품질 이미지 - productInfo?.thumbnailUrl960 || // ✅ 960px 썸네일 - productInfo?.imgList?.[0]?.imgUrl || - productInfo?.thumbnailUrl || - productInfo?.patncLogPath || - '/assets/images/img-thumb-empty-144@3x.png'; - - // ✅ 정확한 가격 추출 (Mock Mode와 동일한 로직) - console.log('[BuyOption] API Mode ADD TO CART - Extracting accurate prices from productInfo'); - console.log('[BuyOption] API Mode ADD TO CART - productInfo.price2:', productInfo?.price2); - console.log('[BuyOption] API Mode ADD TO CART - productInfo.price5:', productInfo?.price5); - console.log('[BuyOption] API Mode ADD TO CART - productInfo.priceInfo:', productInfo?.priceInfo); - - // 정확한 가격 추출 로직 - const extractNumericPrice = (value) => { - if (typeof value === 'number') return value; - if (typeof value === 'string') { - const cleanedValue = value.replace(/,/g, ''); - const numMatch = cleanedValue.match(/[\d.]+/); - return numMatch ? parseFloat(numMatch[0]) : 0; - } - return 0; - }; - - const price2Value = extractNumericPrice(productInfo?.price2); - const price5Value = extractNumericPrice(productInfo?.price5); - - console.log('[BuyOption] API Mode ADD TO CART - extracted price2Value:', price2Value); - console.log('[BuyOption] API Mode ADD TO CART - extracted price5Value:', price5Value); - - // 정확한 가격 계산 (ProductAllSection의 가격 정보 사용) - let discountPrice = price2Value > 0 ? price2Value : 521.66; // ProductAllSection 가격 fallback - let regularPrice = price2Value + price5Value; - - // priceInfo에서 직접 가격 추출 시도 - if (productInfo?.priceInfo) { - const priceParts = productInfo.priceInfo.split('|'); - if (priceParts.length >= 2) { - const infoDiscountPrice = extractNumericPrice(priceParts[1]); - const infoRegularPrice = extractNumericPrice(priceParts[0]); - if (infoDiscountPrice > 0) { - discountPrice = infoDiscountPrice; - regularPrice = infoRegularPrice > 0 ? infoRegularPrice : discountPrice; - } - } - } - - console.log('[BuyOption] API Mode ADD TO CART - Final calculated prices:', { - discountPrice, - regularPrice, - price2Value, - price5Value - }); - - const productInfoForCart = { - // 기본 정보 - prdtId: selectedPrdtId, - prdtNm: productInfo?.prdtNm || 'Product', - patnrId: selectedPatnrId, - patncNm: productInfo?.patncNm || 'Partner', - patncLogPath: productInfo?.patncLogPath || '', - - // ✅ 이미지 정보 (ProductAllSection의 고품질 이미지 포함) - imgUrl: imgUrl, - thumbnailUrl: productInfo?.thumbnailUrl960 || imgUrl, - thumbnailUrl960: productInfo?.thumbnailUrl960, - imgList: productInfo?.imgList || [{ imgUrl: imgUrl }], - imgUrls: productInfo?.imgUrls || [{ imgUrl: imgUrl }], // imgUrls 배열 구조 추가 (CheckOutPanel 호환성) - imgUrls600: productInfo?.imgUrls600, // ✅ 고품질 이미지 배열 포함 - - // 가격 정보 (정확한 가격 정보 - 문자열 형식으로) - price2: regularPrice.toFixed(2), // 원가 (ProductAllSection 가격) - price3: discountPrice.toFixed(2), // 할인가/판매가 (ProductAllSection 가격) - // 추가 가격 필드들 (다양한 fallback 지원) - finalPrice: discountPrice, - discountPrice: discountPrice, - origPrice: regularPrice, - discount: Math.max(0, regularPrice - discountPrice), - priceInfo: productInfo?.priceInfo, // 원본 priceInfo 보존 - - // 수량 정보 - prodQty: quantity, - - // 옵션 정보 - optNm: productOptionInfos[0]?.optNm || '', - - // 배송비 정보 - shippingCharge: productInfo?.shippingFee || '12.99', - - // 기타 정보 - soldoutFlag: productInfo?.soldoutFlag || 'N', - }; - - const optionInfoForCart = { - name: productOptionInfos[0]?.optNm || '', - price: productOptionInfos[0]?.prdtOptDtl[0]?.optPrc || '0.00' - }; - - console.log('[BuyOption] 🛒 API Mode - Pushing CART_PANEL with name:', Config.panel_names.CART_PANEL); - console.log('[BuyOption] 🛒 API Mode - productInfoForCart:', productInfoForCart); - console.log('[BuyOption] 🛒 API Mode - optionInfoForCart:', optionInfoForCart); - console.log('[BuyOption] 🛒 API Mode - quantity:', quantity); - - dispatch( - pushPanel({ - name: Config.panel_names.CART_PANEL, - panelInfo: { - productInfo: productInfoForCart, - optionInfo: optionInfoForCart, - quantity: quantity - } - }) - ); - } else { - // Mock Mode: Mock 장바구니에 상품 추가 후 CartPanel로 이동 - console.log('[BuyOption] Mock Mode - Adding to cart (Mock)'); - - // ✅ 이미지 URL 구성 (ProductAllSection의 고품질 이미지 우선) - const imgUrl = productInfo?.imgUrls600?.[0] || // ✅ ProductAllSection에서 사용하는 고품질 이미지 - productInfo?.thumbnailUrl960 || // ✅ 960px 썸네일 - productInfo?.imgList?.[0]?.imgUrl || - productInfo?.thumbnailUrl || - productInfo?.patncLogPath || - '/assets/images/img-thumb-empty-144@3x.png'; - - // ✅ 정확한 가격 추출 (handleBuyNowClick와 동일한 로직 사용) - console.log('[BuyOption] Mock Mode ADD TO CART - Extracting accurate prices from productInfo'); - console.log('[BuyOption] Mock Mode ADD TO CART - productInfo.price2:', productInfo?.price2); - console.log('[BuyOption] Mock Mode ADD TO CART - productInfo.price5:', productInfo?.price5); - console.log('[BuyOption] Mock Mode ADD TO CART - productInfo.priceInfo:', productInfo?.priceInfo); - - // handleBuyNowClick의 가격 추출 로직과 동일하게 적용 - const extractNumericPrice = (value) => { - if (typeof value === 'number') return value; - if (typeof value === 'string') { - const cleanedValue = value.replace(/,/g, ''); - const numMatch = cleanedValue.match(/[\d.]+/); - return numMatch ? parseFloat(numMatch[0]) : 0; - } - return 0; - }; - - const price2Value = extractNumericPrice(productInfo?.price2); - const price5Value = extractNumericPrice(productInfo?.price5); - - console.log('[BuyOption] Mock Mode ADD TO CART - extracted price2Value:', price2Value); - console.log('[BuyOption] Mock Mode ADD TO CART - extracted price5Value:', price5Value); - - // 정확한 가격 계산 (ProductAllSection의 가격 정보 사용) - let discountPrice = price2Value > 0 ? price2Value : 521.66; // ProductAllSection 가격 fallback - let regularPrice = price2Value + price5Value; - - // priceInfo에서 직접 가격 추출 시도 - if (productInfo?.priceInfo) { - const priceParts = productInfo.priceInfo.split('|'); - if (priceParts.length >= 2) { - const infoDiscountPrice = extractNumericPrice(priceParts[1]); - const infoRegularPrice = extractNumericPrice(priceParts[0]); - if (infoDiscountPrice > 0) { - discountPrice = infoDiscountPrice; - regularPrice = infoRegularPrice > 0 ? infoRegularPrice : discountPrice; - } - } - } - - console.log('[BuyOption] Mock Mode ADD TO CART - Final calculated prices:', { - discountPrice, - regularPrice, - price2Value, - price5Value - }); - - // Mock 상품 정보 구성 (CheckOutPanel 구조 참고) - const mockProductInfo = { - // 기본 정보 - prdtId: selectedPrdtId, - prdtNm: productInfo?.prdtNm || 'Mock Product', - patnrId: selectedPatnrId, - patncNm: productInfo?.patncNm || 'Mock Partner', - patncLogPath: productInfo?.patncLogPath || '', - - // ✅ 이미지 정보 (ProductAllSection의 고품질 이미지 포함) - imgUrl: imgUrl, - thumbnailUrl: productInfo?.thumbnailUrl960 || imgUrl, - thumbnailUrl960: productInfo?.thumbnailUrl960, - imgList: productInfo?.imgList || [{ imgUrl: imgUrl }], // imgList 배열 구조 추가 - imgUrls: productInfo?.imgUrls || [{ imgUrl: imgUrl }], // imgUrls 배열 구조 추가 (CheckOutPanel 호환성) - imgUrls600: productInfo?.imgUrls600, // ✅ 고품질 이미지 배열 포함 - - // 가격 정보 (정확한 가격 정보 - 문자열 형식으로) - price2: regularPrice.toFixed(2), // 원가 (ProductAllSection 가격) - price3: discountPrice.toFixed(2), // 할인가/판매가 (ProductAllSection 가격) - // 추가 가격 필드들 (다양한 fallback 지원) - finalPrice: discountPrice, - discountPrice: discountPrice, - origPrice: regularPrice, - discount: Math.max(0, regularPrice - discountPrice), - priceInfo: productInfo?.priceInfo, // 원본 priceInfo 보존 - - // 수량 정보 - prodQty: quantity, - - // 옵션 정보 (필요시) - optNm: productOptionInfos[0]?.optNm || 'Default Option', - - // 배송비 정보 - shippingCharge: productInfo?.shippingFee || '12.99', - shippingFee: productInfo?.shippingFee || '12.99', - - // 기타 정보 - soldoutFlag: productInfo?.soldoutFlag || 'N', - }; - - // 옵션 정보 구성 - const optionInfo = { - name: productOptionInfos[0]?.optNm || 'Default Option', - price: productOptionInfos[0]?.prdtOptDtl[0]?.optPrc || '0.00' - }; - - // CartPanel로 이동 (productInfo 포함) - CartPanel에서 직접 상품 추가 - console.log('[BuyOption] 🛒 Pushing CART_PANEL with name:', Config.panel_names.CART_PANEL); - console.log('[BuyOption] 🛒 mockProductInfo:', mockProductInfo); - console.log('[BuyOption] 🛒 optionInfo:', optionInfo); - console.log('[BuyOption] 🛒 quantity:', quantity); - - dispatch( - pushPanel({ - name: Config.panel_names.CART_PANEL, - panelInfo: { - productInfo: mockProductInfo, - optionInfo: optionInfo, - quantity: quantity - } - }) - ); - } + // 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) { + dispatch( + addToCart({ + mbrNo: userNumber, + patnrId: selectedPatnrId, + prdtId: selectedPrdtId, + prodQty: String(effectiveQuantity), + prdtOpt: { + prodOptCdCval: optionForUse?.prodOptCdCval || '', + prodOptCval: optionLabel, + prodOptSno: productOptionInfos?.[0]?.prodOptSno || '', + prodOptTpCdCval: productOptionInfos?.[0]?.prodOptTpCdCval || '', + }, + }) + ); + + 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( + 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, @@ -1117,7 +1079,8 @@ const BuyOption = ({ productInfo, productOptionInfos, quantity, - nowMenu, + isMockMode, + selectDefaultOptionForMock, ]); const handleFirstOptionSelect = (selected) => { diff --git a/com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.jsx b/com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.jsx index 6caaa63f..b8947209 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.jsx @@ -312,6 +312,7 @@ const SearchResultsNew = ({ return { // 기본 정보 thumbnail: doc.thumbnail || doc.imgPath || '', // 이미지 경로 + thumbnailOptYn: doc.thumbnailOptYn || 'Y', // 썸네일 최적화 옵션 (Y/N) title: doc.title || doc.prdtName || '', // 제목 contentId, // 콘텐트 아이디 patnrId, // 파트너 아이디