[251101] fix: CheckOutPanel Mock-2

🕐 커밋 시간: 2025. 11. 01. 21:01:06

📊 변경 통계:
  • 총 파일: 10개
  • 추가: +617줄
  • 삭제: -142줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js
  ~ com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx
  ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/OrderItemsSideBar.jsx
  ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/SummaryContainerMock.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.module.less
  ~ com.twin.app.shoptime/src/views/ErrorBoundary.js

🔧 주요 변경 내용:
  • 공통 유틸리티 함수 최적화
  • UI 컴포넌트 아키텍처 개선
  • 오류 처리 로직 개선
  • 대규모 기능 개발
  • 모듈 구조 개선
This commit is contained in:
2025-11-01 21:01:11 +09:00
parent 0e5282476d
commit 6292e84052
10 changed files with 942 additions and 141 deletions

View File

@@ -110,23 +110,30 @@ const BuyOption = ({
// Mock Mode에서 옵션 데이터 처리
const productOptionInfos = useMemo(() => {
console.log('[BuyOption] productOptionInfos useMemo - isMockMode:', BUYNOW_CONFIG.isMockMode());
console.log('[BuyOption] productOptionInfos useMemo - reduxProductOptionInfos:', reduxProductOptionInfos);
// API Mode: 기존 로직 100% 유지
if (!BUYNOW_CONFIG.isMockMode()) {
console.log('[BuyOption] API Mode - using reduxProductOptionInfos');
return reduxProductOptionInfos;
}
// Mock Mode: 옵션 데이터가 없면 Mock 데이터 생성
if (!reduxProductOptionInfos || reduxProductOptionInfos.length === 0) {
// 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);
// Mock 옵션 데이터 배열 반환 (기존 구조와 호환)
return mockOptionData?.optionList || [];
console.log('[BuyOption] Mock Mode - createMockProductOptionData result:', mockOptionData);
// Mock 옵션 데이터는 이미 배열 구조로 반환됨
return mockOptionData || [];
}
// Mock Mode이고 옵션 데이터가 있으면 그대로 사용
// Mock Mode이고 유효한 옵션 데이터가 있으면 그대로 사용
console.log('[BuyOption] Mock Mode - using existing valid reduxProductOptionInfos');
return reduxProductOptionInfos;
}, [reduxProductOptionInfos, productData]);
// logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용)
}, [reduxProductOptionInfos, productData]); // logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용)
const logInfo = useMemo(() => {
if (productData) {
// productData가 있으면 SingleOption과 동일하게 처리
@@ -222,17 +229,50 @@ const BuyOption = ({
}, [selectedIndex, productOptionInfos, type]);
// 옵션 자동 선택 로직 (SingleOption과 동일)
// Mock Mode: 항상 첫 번째 옵션을 자동으로 선택
useEffect(() => {
if (
productOptionInfos &&
selectedBtnOptIdx >= 0 &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length === 1 &&
!isOptionValue
) {
setSelectedOptions(productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl[0]);
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);
}
}, [productOptionInfos, selectedBtnOptIdx, isOptionValue]);
// 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 데이터만 사용)
@@ -320,17 +360,17 @@ const BuyOption = ({
}
} else if (response.retCode === 1001) {
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
dispatch(changeAppStatus({ isLoading: false }));
} else {
dispatch(changeAppStatus({ isLoading: false }));
} else {
dispatch(
showError(
response.retCode,
response.retMsg,
false,
response.retMsg,
false,
response.retDetailCode,
response.returnBindStrings
)
);
);
dispatch(changeAppStatus({ isLoading: false }));
return;
}
@@ -369,18 +409,18 @@ const BuyOption = ({
productOptionInfos.length > 1 &&
productInfo?.optProdYn === 'Y'
) {
if (selectFirstOptionIndex === 0) {
dispatch(
showError(
showError(
null,
"PLEASE SELECT OPTION",
false,
null,
null
)
);
);
return;
}
}
@@ -389,18 +429,18 @@ const BuyOption = ({
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1 &&
productInfo?.optProdYn === 'Y'
) {
if (selectSecondOptionIndex === 0) {
dispatch(
showError(
showError(
null,
"PLEASE SELECT OPTION",
false,
null,
null
)
);
);
return;
}
}
@@ -408,10 +448,27 @@ 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];
console.log('[BuyOption] handleClickOrder - selectedOptions:', selectedOptions);
console.log('[BuyOption] handleClickOrder - priceInfo:', priceInfo);
console.log('[BuyOption] handleClickOrder - logInfo:', logInfo);
// priceInfo 파싱 및 숫자 변환
let regularPrice = parseInt(priceInfo?.split('|')[0], 10) || 0;
let discountPrice = parseInt(priceInfo?.split('|')[1], 10) || 0;
const discountRate = priceInfo?.split('|')[4];
// selectedOptions가 없으면 logInfo에서 가격 추출
if (!selectedOptions && logInfo) {
console.log('[BuyOption] handleClickOrder - selectedOptions is undefined, using logInfo prices');
// logInfo의 dcBefPrc와 dcAftrPrc는 "$ 521.66" 형식이므로 숫자만 추출 (소수점 포함)
const dcBefPrcMatch = logInfo.dcBefPrc?.match(/[\d.]+/);
const dcAftrPrcMatch = logInfo.dcAftrPrc?.match(/[\d.]+/);
regularPrice = dcBefPrcMatch ? parseFloat(dcBefPrcMatch[0]) : 0;
discountPrice = dcAftrPrcMatch ? parseFloat(dcAftrPrcMatch[0]) : regularPrice;
console.log('[BuyOption] handleClickOrder - extracted from logInfo - dcBefPrc:', logInfo.dcBefPrc, 'dcAftrPrc:', logInfo.dcAftrPrc);
}
console.log('[BuyOption] handleClickOrder - regularPrice:', regularPrice, 'discountPrice:', discountPrice, 'discountRate:', discountRate);
dispatch(
sendLogTotalRecommend({
nowMenu: nowMenu,
@@ -452,13 +509,44 @@ const BuyOption = ({
);
} else {
// Mock Mode: 체크아웃 페이지로 이동 (시뮬레이션)
// panelInfo의 logInfo에 선택한 상품 정보를 포함시켜 CheckOutPanel에서 사용
console.log('[BuyOption] Mock Mode - Simulating checkout');
console.log('[BuyOption] logInfo:', logInfo);
console.log('[BuyOption] Dispatching pushPanel to CHECKOUT_PANEL');
dispatch(finishVideoPreview());
dispatch(finishMediaPreview());
const checkoutPanelInfo = { logInfo: { ...logInfo, cartTpSno: `MOCK_${Date.now()}` } };
console.log('[BuyOption] checkoutPanelInfo:', checkoutPanelInfo);
// Mock 모드: 선택 상품의 정보를 panelInfo에 담아서 전달
// CheckOutPanel에서 이 정보로 Mock 상품 데이터 생성
// 이미지 URL 추출 (productInfo의 imgList 또는 thumbnailUrl 사용)
const imgUrl = productInfo?.imgList?.[0]?.imgUrl ||
productInfo?.thumbnailUrl ||
productInfo?.patncLogPath ||
'/mock/image.jpg';
const mockProductInfo = {
prdtId: selectedPrdtId,
prdtNm: productInfo?.prdtNm,
patnrId: selectedPatnrId,
patncNm: patncNm,
prodQty: quantity,
origPrice: regularPrice || 99999, // 원래 가격 (숫자)
discountPrice: discountPrice || regularPrice || 99999, // 할인된 가격 (실제 판매 가격, 숫자)
finalPrice: discountPrice || regularPrice || 99999, // 최종 가격 (숫자)
currSign: '$',
currSignLoc: 'left',
imgUrl: imgUrl, // 상품 이미지 URL 추가
};
const checkoutPanelInfo = {
logInfo: { ...logInfo, cartTpSno: `MOCK_${Date.now()}` },
productInfo: productInfo,
defaultPrice: discountPrice,
};
console.log('[BuyOption] Mock Mode - mockProductInfo:', mockProductInfo);
console.log('[BuyOption] Mock Mode - regularPrice(숫자):', regularPrice, 'discountPrice(숫자):', discountPrice);
console.log('[BuyOption] Mock Mode - checkoutPanelInfo:', checkoutPanelInfo);
dispatch(
pushPanel({
name: Config.panel_names.CHECKOUT_PANEL,
@@ -673,6 +761,11 @@ const BuyOption = ({
setFavoriteFlag(ev);
}, []);
// 구매창이 뜰 때 콘솔 로그 출력 (상품 정보 포함 태그 [BuyOption] 붙임)
useEffect(() => {
console.log('[BuyOption]', '상품 정보:', JSON.stringify(productInfo));
}, []); // 컴포넌트 마운트 시 한 번만 출력
// hasOnClose 로직 (SingleOption과 동일)
const hasOnClose = useMemo(() => {
if (productOptionInfos && productOptionInfos.length > 0) {