[251102] fix: CheckOutPanel Mock-4

🕐 커밋 시간: 2025. 11. 02. 06:00:47

📊 변경 통계:
  • 총 파일: 4개
  • 추가: +238줄
  • 삭제: -96줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/productActions.js
  ~ com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js
  ~ com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 공통 유틸리티 함수 최적화
  • UI 컴포넌트 아키텍처 개선
  • 대규모 기능 개발
This commit is contained in:
2025-11-02 06:00:49 +09:00
parent 830dc65871
commit 4fb13fcfaa
4 changed files with 239 additions and 97 deletions

View File

@@ -500,6 +500,16 @@ const extractReviewFiltersApiData = (apiResponse) => {
if (apiResponse && apiResponse.data) {
// data 경로에서 추출
const apiData = apiResponse.data;
// retCode가 0 또는 '0'이 아니면 NoData 처리
if (apiData.retCode !== 0 && apiData.retCode !== '0') {
console.log('[ReviewFilters] ⚠️ retCode가 0이 아님 (NoData):', {
retCode: apiData.retCode,
retMsg: apiData.retMsg
});
return null;
}
const reviewFilterInfos = apiData.reviewFilterInfos || {};
data = reviewFilterInfos;

View File

@@ -5,11 +5,13 @@
/**
* 상품 정보의 유효성 체크 - 표시할 데이터가 있는지 확인
* Mock Mode에서는 product 객체만 있으면 표시 가능으로 간주
* @param {Object} product - 상품 객체
* @returns {boolean} 표시 가능 여부
*/
export const isProductDisplayable = (product) => {
return product && (product.prdtNm || product.prdtId);
// Mock Mode: product 객체만 있으면 OK (필드 검증 안함)
return !!product;
};
/**
@@ -172,8 +174,9 @@ export const isAuctionProduct = (product) => {
* @returns {Object} 정규화된 상품 객체
*/
export const normalizeProductDataForDisplay = (product) => {
if (!isProductDisplayable(product)) {
// 표시 불가능하면 빈 제품으로 반환
// Mock Mode: product가 없어도 기본값으로 진행
if (!product) {
console.log('[mockDataSafetyUtils] normalizeProductDataForDisplay - product is null/undefined, using defaults');
return {
prdtId: 'N/A',
prdtNm: 'Product',

View File

@@ -45,9 +45,33 @@ import SummaryContainerMock from './container/SummaryContainerMock';
import InformationContainerMock from './container/InformationContainerMock';
export default function CheckOutPanel({ panelInfo }) {
console.log('%c[BuyOption][CheckOutPanel] ▶️ Component mounted START', 'background: blue; color: white; font-weight: bold; padding: 5px;');
console.log('[BuyOption][CheckOutPanel] panelInfo:', panelInfo);
console.log('[BuyOption][CheckOutPanel] panelInfo.logInfo:', panelInfo?.logInfo);
// DEBUG_LOG 설정 - 이 값이 true일 때만 console.log가 실행됨
const DEBUG_LOG = false;
// console.log 오버라이드를 위한 ref
const originalConsoleLog = useRef();
// 컴포넌트 마운트 시 console.log 오버라이드
useEffect(() => {
// 원래 함수 저장
originalConsoleLog.current = console.log;
// console.log 오버라이드 - 이 컴포넌트에서만 DEBUG_LOG 조건을 체크하도록 설정
console.log = (...args) => {
if (DEBUG_LOG) {
originalConsoleLog.current.apply(console, args);
}
};
console.log('%c[BuyOption][CheckOutPanel] ▶️ Component mounted START', 'background: blue; color: white; font-weight: bold; padding: 5px;');
console.log('[BuyOption][CheckOutPanel] panelInfo:', panelInfo);
console.log('[BuyOption][CheckOutPanel] panelInfo.logInfo:', panelInfo?.logInfo);
return () => {
// console.log 원래 함수로 복원
console.log = originalConsoleLog.current;
};
}, []); // 의존성 배열이 비어있어 컴포넌트 마운트 시 한 번만 실행
const dispatch = useDispatch();
const panels = useSelector((state) => state.panels.panels);
console.log(
@@ -68,20 +92,20 @@ export default function CheckOutPanel({ panelInfo }) {
console.log('[BuyOption][CheckOutPanel] Mock Mode - panelInfo:', panelInfo);
console.log('[BuyOption][CheckOutPanel] Mock Mode - panelInfo.productInfo:', panelInfo?.productInfo);
// 1순위: BuyOption에서 전달된 productInfo
if (panelInfo?.productInfo && panelInfo.productInfo.prdtId) {
console.log('%c[BuyOption][CheckOutPanel] ✅ SUCCESS - Using panelInfo.productInfo:', 'background: green; color: white; font-weight: bold; padding: 5px;', 'prdtId=' + panelInfo.productInfo.prdtId);
// 1순위: BuyOption에서 전달된 productInfo (Mock Mode에서는 검증 최소화)
if (panelInfo?.productInfo) {
console.log('%c[BuyOption][CheckOutPanel] ✅ Mock Mode - Using panelInfo.productInfo (no validation):', 'background: green; color: white; font-weight: bold; padding: 5px;', panelInfo.productInfo);
return [panelInfo.productInfo];
}
// 2순위: Redux에서 가져온 상품 데이터
if (reduxProductData && Array.isArray(reduxProductData) && reduxProductData.length > 0) {
console.log('%c[BuyOption][CheckOutPanel] ✅ SUCCESS - Using reduxProductData:', 'background: green; color: white; font-weight: bold; padding: 5px;', 'count=' + reduxProductData.length);
console.log('%c[BuyOption][CheckOutPanel] ✅ Mock Mode - Using reduxProductData:', 'background: green; color: white; font-weight: bold; padding: 5px;', 'count=' + reduxProductData.length);
return reduxProductData;
}
// 3순위: 기본 Hardcoded Mock 데이터 (실패 케이스)
console.error('%c[BuyOption][CheckOutPanel] ❌ FAIL - Using fallback mock data:', 'background: red; color: white; font-weight: bold; padding: 5px;', 'panelInfo=', panelInfo, 'reduxProductData=', reduxProductData);
// 3순위: 기본 Hardcoded Mock 데이터 (최후의 fallback)
console.error('%c[BuyOption][CheckOutPanel] ⚠️ Mock Mode - Using fallback mock data:', 'background: orange; color: white; font-weight: bold; padding: 5px;', 'panelInfo=', panelInfo, 'reduxProductData=', reduxProductData);
return [
{
prdtId: 'MOCK_PRODUCT_1',
@@ -168,7 +192,13 @@ export default function CheckOutPanel({ panelInfo }) {
useEffect(() => {
console.log('[BuyOption][CheckOutPanel] getShoptimeTerms useEffect');
dispatch(getShoptimeTerms());
// Mock Mode: API 호출 스킵
if (!BUYNOW_CONFIG.isMockMode()) {
dispatch(getShoptimeTerms());
} else {
console.log('[BuyOption][CheckOutPanel] Mock Mode - Skipping getShoptimeTerms');
}
}, [dispatch]);
function totalAmtValidate(response) {
@@ -254,11 +284,17 @@ export default function CheckOutPanel({ panelInfo }) {
console.log('[BuyOption][CheckOutPanel] cleanup useEffect - setting up cleanup');
return () => {
console.log('[BuyOption][CheckOutPanel] cleanup useEffect - calling resetCheckoutData');
// Mock 모드일 때는 데이터를 유지해야 다시 진입했을 때 올바른 상품 정보 로드 가능
// Mock 모드에서도 상태 초기화 필요 (Firefox 홀수/짝수번 패턴 문제 해결)
if (!BUYNOW_CONFIG.isMockMode()) {
dispatch(resetCheckoutData());
} else {
console.log('[BuyOption][CheckOutPanel] Mock Mode - Skipping resetCheckoutData to preserve product data');
console.log('[BuyOption][CheckOutPanel] Mock Mode - Partial cleanup to prevent state accumulation');
// Mock Mode에서도 팝업 상태와 관련된 부분 초기화
dispatch(setHidePopup());
// empTermsData 초기화를 위한 액션 디스패치 (empActions에서 reset 액션이 있는지 확인 필요)
// dispatch({ type: 'RESET_EMP_TERMS' });
}
};
}, [dispatch]);
@@ -469,33 +505,21 @@ export default function CheckOutPanel({ panelInfo }) {
doSendLogPaymentEntry={doSendLogPaymentEntry}
/>
)}
{(() => {
console.log(
'[CheckOutPanel] Before conditional - isMockMode:',
BUYNOW_CONFIG.isMockMode()
);
if (BUYNOW_CONFIG.isMockMode()) {
console.log('[BuyOption][CheckOutPanel] Rendering InformationContainerMock');
return (
<InformationContainerMock
toggleOrderSideBar={toggleOrderSideBar}
toggleOfferSideBar={toggleOfferSideBar}
scrollTopBody={scrollTopBody}
doSendLogMyInfoEdit={doSendLogMyInfoEdit}
/>
);
} else {
console.log('[BuyOption][CheckOutPanel] Rendering InformationContainer');
return (
<InformationContainer
toggleOrderSideBar={toggleOrderSideBar}
toggleOfferSideBar={toggleOfferSideBar}
scrollTopBody={scrollTopBody}
doSendLogMyInfoEdit={doSendLogMyInfoEdit}
/>
);
}
})()}
{BUYNOW_CONFIG.isMockMode() ? (
<InformationContainerMock
toggleOrderSideBar={toggleOrderSideBar}
toggleOfferSideBar={toggleOfferSideBar}
scrollTopBody={scrollTopBody}
doSendLogMyInfoEdit={doSendLogMyInfoEdit}
/>
) : (
<InformationContainer
toggleOrderSideBar={toggleOrderSideBar}
toggleOfferSideBar={toggleOfferSideBar}
scrollTopBody={scrollTopBody}
doSendLogMyInfoEdit={doSendLogMyInfoEdit}
/>
)}
</div>
</TBody>
</TPanel>

View File

@@ -3,6 +3,7 @@ import React, {
useEffect,
useMemo,
useState,
useRef,
} from 'react';
import {
@@ -70,6 +71,30 @@ const BuyOption = ({
logMenu,
type,
}) => {
// DEBUG_LOG 설정 - 이 값이 true일 때만 console.log가 실행됨
const DEBUG_LOG = false;
// 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에서)
@@ -381,17 +406,24 @@ const BuyOption = ({
}
const handleBuyNowClick = useCallback(() => {
console.log('[BuyOption] BUY NOW clicked');
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 === '') {
return;
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과 동일)
@@ -404,49 +436,89 @@ const BuyOption = ({
// return dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
// }
//옵션 선택 안하면 구매 안돼도록.
if (
productOptionInfos &&
productOptionInfos.length > 1 &&
productInfo?.optProdYn === 'Y'
) {
// Mock Mode가 아닐 때만 옵션 선택 검증
if (!BUYNOW_CONFIG.isMockMode()) {
//옵션 선택 안하면 구매 안돼도록.
if (
productOptionInfos &&
productOptionInfos.length > 1 &&
productInfo?.optProdYn === 'Y'
) {
if (selectFirstOptionIndex === 0) {
dispatch(
showError(
null,
"PLEASE SELECT OPTION",
false,
null,
null
)
);
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;
}
}
if (
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1 &&
productInfo?.optProdYn === 'Y'
) {
if (selectSecondOptionIndex === 0) {
dispatch(
showError(
null,
"PLEASE SELECT OPTION",
false,
null,
null
)
);
} 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;
}
}
if (userNumber && selectedPatnrId && selectedPrdtId && quantity) {
// Mock Mode: 조건 완화 (userNumber 체크 제외)
const shouldProceed = BUYNOW_CONFIG.isMockMode()
? (selectedPatnrId && selectedPrdtId && quantity)
: (userNumber && selectedPatnrId && selectedPrdtId && quantity);
console.log('%c[BuyOption] shouldProceed check:', 'background: purple; color: white; padding: 3px;', {
shouldProceed,
isMockMode: BUYNOW_CONFIG.isMockMode(),
userNumber: !!userNumber,
selectedPatnrId: !!selectedPatnrId,
selectedPrdtId: !!selectedPrdtId,
quantity
});
if (shouldProceed) {
const { prodOptCval, priceInfo } = selectedOptions || {};
const { patncNm, brndNm, catNm, prdtNm, prdtId } = productInfo;
@@ -571,22 +643,22 @@ const BuyOption = ({
imgUrl: imgUrl, // 상품 이미지 URL 추가
};
// 필수 데이터 검증
// Mock Mode: productInfo 최소 검증만 수행
if (!productInfo || !productInfo.prdtId) {
console.error('%c[BuyOption] ❌ FAIL - productInfo is invalid:', 'background: red; color: white; font-weight: bold; padding: 5px;', productInfo);
dispatch(showError(null, 'Product information is missing', false, null, null));
return;
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);
}
console.log('%c[BuyOption] ✅ SUCCESS - 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,
prdtNm: productInfo.prdtNm,
patnrId: selectedPatnrId,
patncNm: patncNm,
prdtId: productInfo?.prdtId || selectedPrdtId || 'MOCK_PRODUCT',
prdtNm: productInfo?.prdtNm || 'Mock Product',
patnrId: selectedPatnrId || '1',
patncNm: patncNm || 'Mock Partner',
// calculateOrderSummaryFromProductInfo 함수가 필요한 필드들
// productInfo에서 직접 추출한 값을 전달
price2: price2Value, // Items (상품 가격)
@@ -621,16 +693,49 @@ const BuyOption = ({
console.log('[BuyOption] Mock Mode - regularPrice:', regularPrice, 'discountPrice:', discountPrice);
console.log('%c[BuyOption] ▶️ BEFORE pushPanel dispatch', 'background: orange; color: white; font-weight: bold; padding: 5px;');
dispatch(
pushPanel({
name: Config.panel_names.CHECKOUT_PANEL,
panelInfo: checkoutPanelInfo,
})
);
console.log('%c[BuyOption] Mock Mode - Final checkoutPanelInfo:', 'background: blue; color: white; padding: 3px;', checkoutPanelInfo);
// 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: selectedPrdtId || 'MOCK_PRODUCT',
prdtNm: 'Mock Product',
patnrId: selectedPatnrId || '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);
dispatch(
pushPanel({
name: Config.panel_names.CHECKOUT_PANEL,
panelInfo: fallbackPanelInfo,
})
);
} else {
// 정상 케이스: checkoutPanelInfo 사용
dispatch(
pushPanel({
name: Config.panel_names.CHECKOUT_PANEL,
panelInfo: checkoutPanelInfo,
})
);
}
console.log('%c[BuyOption] ✅ AFTER pushPanel dispatch', 'background: orange; color: white; font-weight: bold; padding: 5px;');
// Toast 정리는 성공적으로 패널 이동 후에만 실행
dispatch(clearAllToasts());
}
}
dispatch(clearAllToasts());
}, [
dispatch,
userNumber,
@@ -835,7 +940,7 @@ const BuyOption = ({
setFavoriteFlag(ev);
}, []);
// 구매창이 뜰 때 콘솔 로그 출력 (상품 정보 포함 태그 [BuyOption] 붙임)
// 컴포넌트 마운트 시 상품 정보 출력
useEffect(() => {
console.log('[BuyOption]', '상품 정보:', JSON.stringify(productInfo));
}, []); // 컴포넌트 마운트 시 한 번만 출력