[251101] fix: BuyNow Mock Mocde
🕐 커밋 시간: 2025. 11. 01. 16:14:38 📊 변경 통계: • 총 파일: 13개 • 추가: +504줄 • 삭제: -170줄 📁 추가된 파일: + com.twin.app.shoptime/src/utils/BuyNowConfig.js + com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js 📝 수정된 파일: ~ com.twin.app.shoptime/src/hooks/useReviews/useReviews.js ~ com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/FixedSideBar.jsx ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/InformationContainer.jsx ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/OrderItemsSideBar.jsx ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/SummaryCotainer.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx ~ com.twin.app.shoptime/src/views/MainView/MainView.jsx ~ com.twin.app.shoptime/src/views/UserReview/ShowUserReviews.jsx 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • UI 컴포넌트 아키텍처 개선 • 공통 유틸리티 함수 최적화 • 대규모 기능 개발 • 모듈 구조 개선
This commit is contained in:
@@ -10,18 +10,18 @@ const STEP_SIZE = 1; // 페이징 시 이동할 리뷰 개수
|
||||
// 이 값을 변경하면 전체 앱에서 API 버전이 변경됩니다
|
||||
export const REVIEW_VERSION = 2; // ← 여기서 1 또는 2로 변경
|
||||
|
||||
console.log('[useReviews] 🔑 REVIEW_VERSION 설정:', REVIEW_VERSION);
|
||||
// console.log('[useReviews] 🔑 REVIEW_VERSION 설정:', REVIEW_VERSION);
|
||||
|
||||
// reviewVersion 파라미터는 더 이상 사용하지 않음 (호환성 유지를 위해 파라미터는 남겨둠)
|
||||
const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
|
||||
const reviewVersion = REVIEW_VERSION; // 항상 REVIEW_VERSION 상수 사용
|
||||
|
||||
console.log('[useReviews] 🟢 useReviews Hook 호출됨 (REVIEW_VERSION 사용):', {
|
||||
prdtId,
|
||||
patnrId,
|
||||
reviewVersion,
|
||||
usingGlobalVersion: true
|
||||
});
|
||||
// console.log('[useReviews] 🟢 useReviews Hook 호출됨 (REVIEW_VERSION 사용):', {
|
||||
// prdtId,
|
||||
// patnrId,
|
||||
// reviewVersion,
|
||||
// usingGlobalVersion: true
|
||||
// });
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -71,12 +71,12 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
|
||||
const reviewFiltersData = useSelector((state) => {
|
||||
const data = state.product.reviewFiltersData;
|
||||
|
||||
console.log('[useReviews_reviewFilters] 📥 Redux reviewFiltersData 선택:', {
|
||||
dataExists: !!data,
|
||||
dataKeys: data ? Object.keys(data) : 'null',
|
||||
filtersLength: data?.filters?.length || 0,
|
||||
filters: data?.filters
|
||||
});
|
||||
// console.log('[useReviews_reviewFilters] 📥 Redux reviewFiltersData 선택:', {
|
||||
// dataExists: !!data,
|
||||
// dataKeys: data ? Object.keys(data) : 'null',
|
||||
// filtersLength: data?.filters?.length || 0,
|
||||
// filters: data?.filters
|
||||
// });
|
||||
|
||||
return data;
|
||||
});
|
||||
@@ -87,20 +87,20 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
|
||||
const filteredReviewListData = useSelector((state) => state.product.filteredReviewListData);
|
||||
const currentReviewFilter = useSelector((state) => state.product.currentReviewFilter);
|
||||
|
||||
console.log('[useReviews_filteredReviewList] 📥 Redux filteredReviewListData 선택:', {
|
||||
filteredReviewListDataExists: !!filteredReviewListData,
|
||||
filteredReviewListDataKeys: filteredReviewListData ? Object.keys(filteredReviewListData) : 'null',
|
||||
reviewListLength: filteredReviewListData?.reviewList?.length || 0,
|
||||
currentReviewFilter
|
||||
});
|
||||
// console.log('[useReviews_filteredReviewList] 📥 Redux filteredReviewListData 선택:', {
|
||||
// filteredReviewListDataExists: !!filteredReviewListData,
|
||||
// filteredReviewListDataKeys: filteredReviewListData ? Object.keys(filteredReviewListData) : 'null',
|
||||
// reviewListLength: filteredReviewListData?.reviewList?.length || 0,
|
||||
// currentReviewFilter
|
||||
// });
|
||||
|
||||
// 활성 리뷰 데이터 결정 (필터링된 데이터가 있으면 사용, 없으면 ALL 데이터 사용)
|
||||
const activeReviewData = useMemo(() => {
|
||||
if (filteredReviewListData) {
|
||||
console.log('[useReviews] 🟢 activeReviewData: filteredReviewListData 사용');
|
||||
// console.log('[useReviews] 🟢 activeReviewData: filteredReviewListData 사용');
|
||||
return filteredReviewListData;
|
||||
}
|
||||
console.log('[useReviews] 🟢 activeReviewData: reviewListData (ALL) 사용');
|
||||
// console.log('[useReviews] 🟢 activeReviewData: reviewListData (ALL) 사용');
|
||||
return reviewData;
|
||||
}, [filteredReviewListData, reviewData]);
|
||||
|
||||
@@ -156,12 +156,12 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
|
||||
// 리뷰 데이터 로드 함수 - reviewVersion에 따라 API 선택
|
||||
const loadReviews = useCallback(async () => {
|
||||
if (!prdtId) {
|
||||
console.warn('[useReviews] loadReviews 호출되었지만 prdtId가 없음');
|
||||
// console.warn('[useReviews] loadReviews 호출되었지만 prdtId가 없음');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!patnrId) {
|
||||
console.warn('[useReviews] loadReviews 호출되었지만 patnrId가 없음');
|
||||
// console.warn('[useReviews] loadReviews 호출되었지만 patnrId가 없음');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
|
||||
try {
|
||||
if (reviewVersion === 1) {
|
||||
// 기존 API 호출
|
||||
console.log('[useReviews] 🔄 getUserReviews 호출 중... (v1)');
|
||||
// console.log('[useReviews] 🔄 getUserReviews 호출 중... (v1)');
|
||||
await dispatch(getUserReviews({ prdtId, patnrId }));
|
||||
} else {
|
||||
// 신 API 호출 (v2)
|
||||
@@ -191,7 +191,7 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
|
||||
}));
|
||||
|
||||
// IF-LGSP-100 필터 데이터 조회
|
||||
console.log('[useReviews] 🔄 getReviewFilters 호출 중... (IF-LGSP-100)');
|
||||
// console.log('[useReviews] 🔄 getReviewFilters 호출 중... (IF-LGSP-100)');
|
||||
await dispatch(getReviewFilters({
|
||||
prdtId,
|
||||
patnrId
|
||||
@@ -213,16 +213,16 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
|
||||
const needsReviewLoad = prdtId !== loadedPrdtId;
|
||||
const needsFiltersLoad = prdtId !== loadedFiltersPrdtId;
|
||||
|
||||
console.log('[useReviews] 🔄 로드 필요 여부 체크 (캐싱 로직 포함):', {
|
||||
prdtId,
|
||||
patnrId,
|
||||
loadedPrdtId,
|
||||
loadedFiltersPrdtId,
|
||||
reviewVersion,
|
||||
needsReviewLoad,
|
||||
needsFiltersLoad,
|
||||
reason: (needsReviewLoad || needsFiltersLoad) ? 'prdtId가 변경됨' : '캐시된 데이터 사용'
|
||||
});
|
||||
// console.log('[useReviews] 🔄 로드 필요 여부 체크 (캐싱 로직 포함):', {
|
||||
// prdtId,
|
||||
// patnrId,
|
||||
// loadedPrdtId,
|
||||
// loadedFiltersPrdtId,
|
||||
// reviewVersion,
|
||||
// needsReviewLoad,
|
||||
// needsFiltersLoad,
|
||||
// reason: (needsReviewLoad || needsFiltersLoad) ? 'prdtId가 변경됨' : '캐시된 데이터 사용'
|
||||
// });
|
||||
|
||||
if (needsReviewLoad || needsFiltersLoad) {
|
||||
// prdtId 변경 시 로딩 상태 즉시 설정으로 UI 깜빡임 방지
|
||||
@@ -711,22 +711,22 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
|
||||
_activeSource: isFilterActive ? 'filteredReviewDetail' : 'initialReviewDetail'
|
||||
};
|
||||
|
||||
console.log('[useReviews_useReviewList] 📊 stats 계산:', {
|
||||
isFilterActive,
|
||||
totalReviewsCount,
|
||||
averageRating,
|
||||
activeSource: isFilterActive ? 'filteredReviewDetail' : 'initialReviewDetail',
|
||||
initialReviewDetail: {
|
||||
totRvwCnt: initialReviewDetail.totRvwCnt,
|
||||
totRvwAvg: initialReviewDetail.totRvwAvg
|
||||
},
|
||||
filteredReviewDetail: {
|
||||
totRvwCnt: filteredReviewDetail.totRvwCnt,
|
||||
totRvwAvg: filteredReviewDetail.totRvwAvg
|
||||
},
|
||||
allReviewsLength: allReviews.length,
|
||||
prdtId
|
||||
});
|
||||
// console.log('[useReviews_useReviewList] 📊 stats 계산:', {
|
||||
// isFilterActive,
|
||||
// totalReviewsCount,
|
||||
// averageRating,
|
||||
// activeSource: isFilterActive ? 'filteredReviewDetail' : 'initialReviewDetail',
|
||||
// initialReviewDetail: {
|
||||
// totRvwCnt: initialReviewDetail.totRvwCnt,
|
||||
// totRvwAvg: initialReviewDetail.totRvwAvg
|
||||
// },
|
||||
// filteredReviewDetail: {
|
||||
// totRvwCnt: filteredReviewDetail.totRvwCnt,
|
||||
// totRvwAvg: filteredReviewDetail.totRvwAvg
|
||||
// },
|
||||
// allReviewsLength: allReviews.length,
|
||||
// prdtId
|
||||
// });
|
||||
|
||||
return result;
|
||||
}, [allReviews.length, filteredReviews.length, displayReviews.length, reviewData, filteredReviewListData, currentReviewFilter, reviewDetail, hasLoadedData, isLoading, isCurrentProductLoaded, reviewVersion, loadedPrdtId, prdtId]);
|
||||
|
||||
55
com.twin.app.shoptime/src/utils/BuyNowConfig.js
Normal file
55
com.twin.app.shoptime/src/utils/BuyNowConfig.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* BUY NOW Mock Mode Configuration
|
||||
*
|
||||
* 시연을 위해 구매 불가능한 상품도 구매 가능하게 표시하는 Mock Mode 설정
|
||||
* - API Mode (기본값): 기존 로직 100% 유지
|
||||
* - Mock Mode: 데이터를 조작하여 구매 가능하게 표시
|
||||
*
|
||||
* 활성화 방법:
|
||||
* 1. URL 파라미터: ?mock=true
|
||||
* 2. localStorage: BUYNOW_CONFIG.setMode('mock')
|
||||
*/
|
||||
|
||||
let MOCK_MODE = false;
|
||||
|
||||
export const BUYNOW_CONFIG = {
|
||||
/**
|
||||
* MainView.jsx에서 한 번만 호출
|
||||
* localStorage와 URL 파라미터를 읽어서 MOCK_MODE 설정
|
||||
*/
|
||||
init(isMockMode) {
|
||||
MOCK_MODE = isMockMode;
|
||||
console.log('[BUYNOW_CONFIG] Initialized - Mock Mode:', MOCK_MODE);
|
||||
},
|
||||
|
||||
/**
|
||||
* ProductAllSection 등 모든 곳에서 호출
|
||||
* 메모리 변수를 읽기만 함 (localStorage 접근 없음)
|
||||
*/
|
||||
isMockMode() {
|
||||
return MOCK_MODE;
|
||||
},
|
||||
|
||||
/**
|
||||
* 개발자 콘솔에서 Mock Mode 토글
|
||||
* 예: console에서 BUYNOW_CONFIG.toggle() 호출
|
||||
*/
|
||||
toggle() {
|
||||
MOCK_MODE = !MOCK_MODE;
|
||||
console.log('[BUYNOW_CONFIG] Toggled - Mock Mode:', MOCK_MODE);
|
||||
},
|
||||
|
||||
/**
|
||||
* Mock Mode를 특정 값으로 설정 (콘솔 용)
|
||||
*/
|
||||
setMode(mode) {
|
||||
MOCK_MODE = mode === 'mock';
|
||||
console.log('[BUYNOW_CONFIG] Mode set to:', MOCK_MODE ? 'mock' : 'api');
|
||||
}
|
||||
};
|
||||
|
||||
// globalThis에 노출 (개발자 콘솔 접근 용)
|
||||
// 구형 브라우저 호환성을 위해 window 체크
|
||||
if (typeof window !== 'undefined') {
|
||||
window.BUYNOW_CONFIG = BUYNOW_CONFIG;
|
||||
}
|
||||
92
com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js
Normal file
92
com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* BUY NOW Mock Mode Data Manipulation Utilities
|
||||
*
|
||||
* Mock Mode에서 원본 상품 데이터를 기반으로 가상 옵션 데이터를 생성
|
||||
*/
|
||||
|
||||
import { BUYNOW_CONFIG } from './BuyNowConfig';
|
||||
|
||||
/**
|
||||
* Mock Mode에서 원본 상품 데이터를 기반으로 Mock 옵션 데이터 생성
|
||||
*
|
||||
* @param {Object} originalProductData - 원본 상품 데이터
|
||||
* @returns {Object|null} Mock 옵션 데이터 또는 null
|
||||
*/
|
||||
export const createMockProductOptionData = (originalProductData) => {
|
||||
// API Mode: null 반환
|
||||
if (!BUYNOW_CONFIG.isMockMode() || !originalProductData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 원본 데이터에서 필요한 정보 추출
|
||||
const basePrice = originalProductData.prdtPrice || 0;
|
||||
const prdtId = originalProductData.prdtId;
|
||||
const prdtNm = originalProductData.prdtNm;
|
||||
|
||||
// Mock 옵션 데이터 생성
|
||||
return {
|
||||
// 옵션 ID (기본 값)
|
||||
prodOptSno: `MOCK_OPT_${prdtId}_1`,
|
||||
|
||||
// 옵션 목록 (기본값 1개)
|
||||
optionList: [
|
||||
{
|
||||
optId: `MOCK_OPT_${prdtId}_1`,
|
||||
optNm: `${prdtNm} - 기본 옵션`,
|
||||
optPrice: basePrice,
|
||||
optDscntPrice: basePrice,
|
||||
stockCnt: 999,
|
||||
soldOutYn: 'N',
|
||||
optImgUrl: originalProductData.thumbnailUrl || null
|
||||
}
|
||||
],
|
||||
|
||||
// 옵션 타입 정보
|
||||
optionTypeInfo: {
|
||||
optTypeCd: 'BASIC',
|
||||
optTypeNm: '기본'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock Mode에서 원본 상품 데이터를 기반으로 Mock 장바구니 데이터 생성
|
||||
*
|
||||
* @param {Object} productData - 상품 데이터
|
||||
* @param {Object} optionInfo - 옵션 정보
|
||||
* @param {number} quantity - 수량
|
||||
* @returns {Object} Mock 장바구니 데이터
|
||||
*/
|
||||
export const createMockCartData = (productData, optionInfo = {}, quantity = 1) => {
|
||||
if (!BUYNOW_CONFIG.isMockMode() || !productData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
cartId: `MOCK_CART_${productData.prdtId}_${Date.now()}`,
|
||||
prdtId: productData.prdtId,
|
||||
prdtNm: productData.prdtNm,
|
||||
patnrId: productData.patnrId,
|
||||
patncNm: productData.patncNm,
|
||||
qty: quantity,
|
||||
price: productData.prdtPrice || 0,
|
||||
thumbnailUrl: productData.thumbnailUrl || null,
|
||||
optionInfo: optionInfo,
|
||||
addedAt: new Date().toISOString()
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock Mode에서 가격 정보 포맷팅
|
||||
* priceInfo는 "원가|할인가" 형식 (예: "100000|80000")
|
||||
*
|
||||
* @param {number} basePrice - 기본 가격
|
||||
* @returns {string} 포맷된 가격 정보
|
||||
*/
|
||||
export const formatMockPriceInfo = (basePrice) => {
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${basePrice}|${basePrice}`;
|
||||
};
|
||||
@@ -32,6 +32,7 @@ import useScrollTo from "../../hooks/useScrollTo";
|
||||
import * as Config from "../../utils/Config";
|
||||
import { $L, scaleH, scaleW } from "../../utils/helperMethods";
|
||||
import { SpotlightIds } from "../../utils/SpotlightIds";
|
||||
import { BUYNOW_CONFIG } from "../../utils/BuyNowConfig";
|
||||
import css from "./CheckOutPanel.module.less";
|
||||
import PinCodeInput from "./components/PinCodeInput";
|
||||
import FixedSideBar from "./container/FixedSideBar";
|
||||
@@ -40,12 +41,16 @@ import OrderItemsSideBar from "./container/OrderItemsSideBar";
|
||||
import SummaryContainer from "./container/SummaryCotainer";
|
||||
|
||||
export default function CheckOutPanel({ panelInfo }) {
|
||||
console.log('[CheckOutPanel] Component mounted');
|
||||
console.log('[CheckOutPanel] panelInfo:', panelInfo);
|
||||
console.log('[CheckOutPanel] panelInfo.logInfo:', panelInfo?.logInfo);
|
||||
const dispatch = useDispatch();
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
console.log('[CheckOutPanel] panels:', panels?.map(p => p.name));
|
||||
const { userNumber } = useSelector(
|
||||
(state) => state.common.appStatus.loginUserData
|
||||
);
|
||||
const productData = useSelector(
|
||||
const reduxProductData = useSelector(
|
||||
(state) => state.checkout?.checkoutData.productList
|
||||
);
|
||||
const infoForCheckoutData = useSelector(
|
||||
@@ -59,7 +64,32 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
(state) => state.common.popup
|
||||
);
|
||||
const popup = useSelector((state) => state.common.popup);
|
||||
|
||||
// Mock Mode: 가짜 상품 데이터 생성
|
||||
const productData = BUYNOW_CONFIG.isMockMode()
|
||||
? reduxProductData || [
|
||||
{
|
||||
prdtId: 'MOCK_PRODUCT_1',
|
||||
prdtNm: 'Mock Product for Demonstration',
|
||||
patnrId: '1',
|
||||
patncNm: 'Mock Partner',
|
||||
prodSno: 'MOCK_123',
|
||||
prodQty: 1,
|
||||
finalPrice: 99999,
|
||||
origPrice: 99999,
|
||||
discountPrice: 99999,
|
||||
currSign: '$',
|
||||
currSignLoc: 'left',
|
||||
}
|
||||
]
|
||||
: reduxProductData;
|
||||
|
||||
console.log('[CheckOutPanel] isMockMode:', BUYNOW_CONFIG.isMockMode());
|
||||
console.log('[CheckOutPanel] reduxProductData:', reduxProductData);
|
||||
console.log('[CheckOutPanel] productData:', productData);
|
||||
|
||||
const { currSign, currSignLoc } = productData?.[0] || {};
|
||||
console.log('[CheckOutPanel] currSign:', currSign, 'currSignLoc:', currSignLoc);
|
||||
|
||||
const [orderSideBarOpen, setOrderSideBarOpen] = useState(false);
|
||||
const [offerSideBarOpen, setOfferSideBarOpen] = useState(false);
|
||||
@@ -79,9 +109,11 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
const spotJob = useRef(new Job((func) => func(), 0));
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[CheckOutPanel] sendLogGNB useEffect - isOrderSuccessful:', isOrderSuccessful);
|
||||
let nowMenu;
|
||||
|
||||
if (isOrderSuccessful) {
|
||||
console.log('[CheckOutPanel] Order successful, returning early');
|
||||
return;
|
||||
}
|
||||
//
|
||||
@@ -93,18 +125,22 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
nowMenu = Config.LOG_MENU.CHECKOUT_PIN_CODE;
|
||||
}
|
||||
|
||||
console.log('[CheckOutPanel] Dispatching sendLogGNB with menu:', nowMenu);
|
||||
dispatch(sendLogGNB(nowMenu));
|
||||
}, [isOrderSuccessful, placeOrderPopup, popupVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[CheckOutPanel] isMounted useEffect');
|
||||
isMounted.current = true;
|
||||
|
||||
return () => {
|
||||
console.log('[CheckOutPanel] isMounted cleanup - component unmounting');
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[CheckOutPanel] getShoptimeTerms useEffect');
|
||||
dispatch(getShoptimeTerms());
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -123,6 +159,18 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[CheckOutPanel] checkout total amount useEffect triggered');
|
||||
console.log('[CheckOutPanel] infoForCheckoutData:', infoForCheckoutData);
|
||||
console.log('[CheckOutPanel] productData length:', productData?.length);
|
||||
|
||||
// Mock Mode: API 호출 스킵
|
||||
if (BUYNOW_CONFIG.isMockMode()) {
|
||||
console.log('[CheckOutPanel] Mock Mode - Skipping checkout total amount calculation');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[CheckOutPanel] API Mode - calling getCheckoutTotalAmt');
|
||||
// API Mode: 기존 로직 유지
|
||||
if (infoForCheckoutData && productData) {
|
||||
const orderProductCoupontUse = Object.keys(selectedCoupons).map(
|
||||
(productId) => {
|
||||
@@ -161,6 +209,7 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
}, [dispatch, infoForCheckoutData, productData, userNumber, selectedCoupons]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[CheckOutPanel] empTermsData useEffect - empTermsData length:', empTermsData?.length);
|
||||
const newTabList = [];
|
||||
|
||||
if (empTermsData) {
|
||||
@@ -168,12 +217,15 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
newTabList.push(term.termsTypeName);
|
||||
});
|
||||
|
||||
console.log('[CheckOutPanel] Setting tabList:', newTabList);
|
||||
setTabList(newTabList);
|
||||
}
|
||||
}, [empTermsData]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[CheckOutPanel] cleanup useEffect - setting up cleanup');
|
||||
return () => {
|
||||
console.log('[CheckOutPanel] cleanup useEffect - calling resetCheckoutData');
|
||||
dispatch(resetCheckoutData());
|
||||
};
|
||||
}, [dispatch]);
|
||||
@@ -195,10 +247,12 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
}, [activePopup]);
|
||||
|
||||
const onBackClick = useCallback(() => {
|
||||
console.log('[CheckOutPanel] onBackClick called - dispatching popPanel');
|
||||
dispatch(popPanel());
|
||||
}, [dispatch]);
|
||||
|
||||
const toggleOrderSideBar = useCallback(() => {
|
||||
console.log('[CheckOutPanel] toggleOrderSideBar called - current state:', orderSideBarOpen);
|
||||
if (!orderSideBarOpen) {
|
||||
dispatch(sendLogCheckOutBtnClick({ btnNm: "ORDER ITEMS" }));
|
||||
dispatch(
|
||||
@@ -209,14 +263,14 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
messageId: Config.LOG_MESSAGE_ID.BUTTONCLICK,
|
||||
})
|
||||
);
|
||||
console.log("oder items");
|
||||
}
|
||||
|
||||
setOrderSideBarOpen((prev) => !prev);
|
||||
setTimeout(() => Spotlight.focus(), 0);
|
||||
}, [orderSideBarOpen]);
|
||||
}, [orderSideBarOpen, dispatch]);
|
||||
|
||||
const toggleOfferSideBar = useCallback(() => {
|
||||
console.log('[CheckOutPanel] toggleOfferSideBar called - current state:', offerSideBarOpen);
|
||||
if (!offerSideBarOpen) {
|
||||
dispatch(sendLogCheckOutBtnClick({ btnNm: "OFFERS & PROMOTION" }));
|
||||
dispatch(
|
||||
@@ -231,29 +285,34 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
|
||||
setOfferSideBarOpen((prev) => !prev);
|
||||
setTimeout(() => Spotlight.focus(), 0);
|
||||
}, [offerSideBarOpen]);
|
||||
}, [offerSideBarOpen, dispatch]);
|
||||
|
||||
const onClosePopup = useCallback(() => {
|
||||
console.log('[CheckOutPanel] onClosePopup called');
|
||||
setPlaceOrderPopup(false);
|
||||
setTimeout(() => Spotlight.focus(), 0);
|
||||
}, []);
|
||||
|
||||
const onCloseTermsPopup = useCallback(() => {
|
||||
console.log('[CheckOutPanel] onCloseTermsPopup called');
|
||||
dispatch(setHidePopup());
|
||||
}, [dispatch]);
|
||||
|
||||
const handlePopPanel = useCallback(() => {
|
||||
console.log('[CheckOutPanel] handlePopPanel called - dispatching setHidePopup and popPanel');
|
||||
dispatch(setHidePopup());
|
||||
dispatch(popPanel());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleTermsClick = useCallback(
|
||||
(termsID) => {
|
||||
console.log('[CheckOutPanel] handleTermsClick called with termsID:', termsID);
|
||||
if (empTermsData) {
|
||||
const selectedTerms = empTermsData.find(
|
||||
(term) => term.termsID === termsID
|
||||
);
|
||||
|
||||
console.log('[CheckOutPanel] Selected terms:', selectedTerms?.termsTypeName);
|
||||
dispatch(
|
||||
sendLogTotalRecommend({
|
||||
buttonTitle: selectedTerms.termsTypeName,
|
||||
@@ -298,7 +357,9 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
|
||||
const onCancelCheckoutPanel = useCallback(
|
||||
(e) => {
|
||||
console.log('[CheckOutPanel] onCancelCheckoutPanel called');
|
||||
if (orderSideBarOpen) {
|
||||
console.log('[CheckOutPanel] Closing order sidebar');
|
||||
setOrderSideBarOpen(false);
|
||||
setTimeout(() => Spotlight.focus(), 0);
|
||||
|
||||
@@ -306,6 +367,7 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
}
|
||||
|
||||
if (offerSideBarOpen) {
|
||||
console.log('[CheckOutPanel] Closing offer sidebar');
|
||||
setOfferSideBarOpen(false);
|
||||
setTimeout(() => Spotlight.focus(), 0);
|
||||
|
||||
@@ -313,6 +375,7 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
}
|
||||
|
||||
if (!orderSideBarOpen && !offerSideBarOpen) {
|
||||
console.log('[CheckOutPanel] Calling popPanel()');
|
||||
dispatch(popPanel());
|
||||
e.stopPropagation();
|
||||
}
|
||||
@@ -330,6 +393,9 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
dispatch(sendLogMyInfoEdit({ btnNm }));
|
||||
}, []);
|
||||
|
||||
console.log('[CheckOutPanel] Rendering - orderSideBarOpen:', orderSideBarOpen, 'offerSideBarOpen:', offerSideBarOpen, 'placeOrderPopup:', placeOrderPopup);
|
||||
console.log('[CheckOutPanel] Rendering - productData exists:', !!productData, 'length:', productData?.length);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TPanel
|
||||
|
||||
@@ -12,20 +12,51 @@ import { $L } from "../../../utils/helperMethods";
|
||||
import OffersPromotionItemCard, {
|
||||
SIZES,
|
||||
} from "../components/OffersPromotionItemCard";
|
||||
import { BUYNOW_CONFIG } from "../../../utils/BuyNowConfig";
|
||||
import css from "./FixedSideBar.module.less";
|
||||
|
||||
const SideBarContainer = SpotlightContainerDecorator("div");
|
||||
|
||||
export default function FixedSideBar({ closeSideBar, offerSideBarOpen }) {
|
||||
const orderItemList = useSelector(
|
||||
console.log('[CheckOutPanel] FixedSideBar mounted');
|
||||
|
||||
const reduxOrderItemList = useSelector(
|
||||
(state) => state.checkout?.checkoutData?.productList
|
||||
);
|
||||
console.log('[CheckOutPanel] FixedSideBar reduxOrderItemList:', reduxOrderItemList);
|
||||
|
||||
// Check if reduxOrderItemList has actual data
|
||||
const hasValidOrderItemList = Array.isArray(reduxOrderItemList) && reduxOrderItemList.length > 0;
|
||||
console.log('[CheckOutPanel] FixedSideBar hasValidOrderItemList:', hasValidOrderItemList);
|
||||
|
||||
const orderItemList = hasValidOrderItemList ? reduxOrderItemList : (BUYNOW_CONFIG.isMockMode() ? [
|
||||
{
|
||||
prdtId: 'MOCK_PRODUCT_1',
|
||||
prdtNm: 'Mock Product',
|
||||
prodQty: 1,
|
||||
prdtOpt: [{ prodOptCdCval: 'MOCK_OPT_1' }],
|
||||
patncLogPath: '/mock/image.jpg',
|
||||
expsPrdtNo: 'MOCK_EXP_1',
|
||||
currSign: '$',
|
||||
currSignLoc: 'left',
|
||||
shippingCharge: 0,
|
||||
auctProdYn: 'N',
|
||||
auctFinalPriceChgDt: null,
|
||||
imgUrls: ['/mock/image.jpg'],
|
||||
prdtCoupon: [],
|
||||
}
|
||||
] : null);
|
||||
|
||||
console.log('[CheckOutPanel] FixedSideBar effectiveOrderItemList:', orderItemList);
|
||||
|
||||
const selectedCoupons = useSelector(
|
||||
(state) => state.checkout.selectedCoupons
|
||||
); // 선택된 쿠폰 정보
|
||||
console.log('[CheckOutPanel] FixedSideBar selectedCoupons:', selectedCoupons);
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ index, ...rest }) => {
|
||||
console.log('[CheckOutPanel] FixedSideBar renderItem - index:', index);
|
||||
const item = orderItemList[index];
|
||||
|
||||
return (
|
||||
@@ -49,9 +80,11 @@ export default function FixedSideBar({ closeSideBar, offerSideBarOpen }) {
|
||||
/>
|
||||
);
|
||||
},
|
||||
[orderItemList]
|
||||
[orderItemList, offerSideBarOpen]
|
||||
);
|
||||
|
||||
console.log('[CheckOutPanel] FixedSideBar rendering - items count:', orderItemList?.length);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classNames(css.bgLayer)} onClick={closeSideBar} />
|
||||
|
||||
@@ -21,6 +21,7 @@ import ShippingAddressCard from "../components/ShippingAddressCard";
|
||||
import Subject from "../components/Subject";
|
||||
import css from "./InformationContainer.module.less";
|
||||
import { sendLogTotalRecommend } from "../../../actions/logActions";
|
||||
import { BUYNOW_CONFIG } from "../../../utils/BuyNowConfig";
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
@@ -48,14 +49,15 @@ export default function InformationContainer({
|
||||
scrollTopBody,
|
||||
doSendLogMyInfoEdit,
|
||||
}) {
|
||||
console.log('[CheckOutPanel] InformationContainer mounted');
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const checkoutData = useSelector((state) => state.checkout?.checkoutData);
|
||||
// All useSelector calls must be at the top - before any conditional logic
|
||||
const reduxCheckoutData = useSelector((state) => state.checkout?.checkoutData);
|
||||
const { cursorVisible } = useSelector((state) => state.common.appStatus);
|
||||
const { popupVisible, activePopup } = useSelector(
|
||||
(state) => state.common.popup
|
||||
);
|
||||
|
||||
const auctProdYn = useSelector(
|
||||
(state) => state.checkout?.checkoutData?.productList?.[0].auctProdYn
|
||||
);
|
||||
@@ -71,13 +73,74 @@ export default function InformationContainer({
|
||||
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
||||
const entryMenu = useSelector((state) => state.common.menu.entryMenu);
|
||||
const deviceInfo = useSelector((state) => state.device.deviceInfo);
|
||||
|
||||
console.log('[CheckOutPanel] InformationContainer reduxCheckoutData:', reduxCheckoutData);
|
||||
console.log('[CheckOutPanel] InformationContainer reduxCheckoutData has productList:', reduxCheckoutData?.productList);
|
||||
|
||||
// Check if reduxCheckoutData has actual data (productList)
|
||||
const hasValidCheckoutData = reduxCheckoutData?.productList && Array.isArray(reduxCheckoutData.productList) && reduxCheckoutData.productList.length > 0;
|
||||
console.log('[CheckOutPanel] InformationContainer hasValidCheckoutData:', hasValidCheckoutData);
|
||||
|
||||
const checkoutData = hasValidCheckoutData ? reduxCheckoutData : (BUYNOW_CONFIG.isMockMode() ? {
|
||||
productList: [{
|
||||
prdtId: 'MOCK_PRODUCT_1',
|
||||
prdtNm: 'Mock Product',
|
||||
patnrId: '1',
|
||||
prodQty: 1,
|
||||
prdtOpt: [{ prodOptCdCval: 'MOCK_OPT_1' }],
|
||||
auctProdYn: 'N',
|
||||
auctFinalPriceChgDt: null,
|
||||
}],
|
||||
shippingAddressList: [
|
||||
{
|
||||
addrSno: 'MOCK_ADDR_1',
|
||||
addrNm: 'Mock Shipping Address',
|
||||
addrZipCode: '12345',
|
||||
addr: '123 Mock Street',
|
||||
addrDtl: 'Suite 100',
|
||||
rcvNm: 'Mock Receiver',
|
||||
rcvTel: '555-1234',
|
||||
}
|
||||
],
|
||||
billingAddressList: [
|
||||
{
|
||||
addrSno: 'MOCK_ADDR_2',
|
||||
addrNm: 'Mock Billing Address',
|
||||
addrZipCode: '12346',
|
||||
addr: '456 Mock Avenue',
|
||||
addrDtl: 'Suite 200',
|
||||
payerNm: 'Mock Payer',
|
||||
payerTel: '555-5678',
|
||||
}
|
||||
],
|
||||
cardInfo: [
|
||||
{
|
||||
cardSno: 'MOCK_CARD_1',
|
||||
cardNm: 'Mock Card',
|
||||
cardNo: '****-****-****-1234',
|
||||
cardType: 'CREDIT',
|
||||
}
|
||||
],
|
||||
} : null);
|
||||
|
||||
console.log('[CheckOutPanel] InformationContainer effectiveCheckoutData:', checkoutData);
|
||||
|
||||
const [_, setTab] = useState(0);
|
||||
const [prdtData, setPrdtData] = useState({});
|
||||
|
||||
const { getScrollTo } = useScrollTo();
|
||||
const { scrollTopByDistance } = useScrollTopByDistance();
|
||||
|
||||
// Use effect to handle missing checkoutData (only in API mode without mock fallback)
|
||||
useEffect(() => {
|
||||
if (!checkoutData) {
|
||||
console.log('[CheckOutPanel] InformationContainer ERROR: checkoutData is missing and Mock Mode is disabled - calling popPanel');
|
||||
dispatch(popPanel());
|
||||
}
|
||||
}, [checkoutData, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[CheckOutPanel] InformationContainer prdtData useEffect - checkoutData:', checkoutData);
|
||||
if (checkoutData) {
|
||||
const { patnrId, prdtId, prodQty } = checkoutData.productList[0];
|
||||
const prodOptCdCval =
|
||||
@@ -92,13 +155,15 @@ export default function InformationContainer({
|
||||
prodQty: prodQty,
|
||||
};
|
||||
|
||||
console.log('[CheckOutPanel] InformationContainer prdtData useEffect - setting params:', params);
|
||||
setPrdtData(params);
|
||||
}
|
||||
}, [checkoutData]);
|
||||
|
||||
const { checkoutUrl } = useMemo(() => {
|
||||
console.log('[CheckOutPanel] InformationContainer checkoutUrl useMemo - generating URL');
|
||||
const { patnrId, prdtId } = checkoutData.productList[0];
|
||||
return getQRCodeUrl({
|
||||
const url = getQRCodeUrl({
|
||||
serverHOST,
|
||||
serverType,
|
||||
prdtData,
|
||||
@@ -108,6 +173,8 @@ export default function InformationContainer({
|
||||
patnrId,
|
||||
index: deviceInfo?.dvcIndex,
|
||||
});
|
||||
console.log('[CheckOutPanel] InformationContainer checkoutUrl useMemo - URL:', url);
|
||||
return url;
|
||||
}, [
|
||||
serverHOST,
|
||||
serverType,
|
||||
@@ -119,13 +186,16 @@ export default function InformationContainer({
|
||||
]);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
console.log('[CheckOutPanel] InformationContainer handleFocus called');
|
||||
const c = Spotlight.getCurrent();
|
||||
const target = c.getAttribute("data-spotlight-id");
|
||||
|
||||
const targetValue = '[data-spotlight-id="' + target + '"]';
|
||||
if (cursorVisible) {
|
||||
console.log('[CheckOutPanel] InformationContainer handleFocus - cursor visible, returning early');
|
||||
return;
|
||||
}
|
||||
console.log('[CheckOutPanel] InformationContainer handleFocus - scrolling to:', targetValue);
|
||||
scrollTopByDistance(
|
||||
`[data-marker="scroll-marker"]`,
|
||||
targetValue,
|
||||
@@ -136,12 +206,15 @@ export default function InformationContainer({
|
||||
|
||||
const handleButtonClick = useCallback(
|
||||
(index) => {
|
||||
console.log('[CheckOutPanel] InformationContainer handleButtonClick - index:', index);
|
||||
const btnNm = getBtnNmByIndex(index);
|
||||
|
||||
if (btnNm && doSendLogMyInfoEdit) {
|
||||
console.log('[CheckOutPanel] InformationContainer handleButtonClick - calling doSendLogMyInfoEdit:', btnNm);
|
||||
doSendLogMyInfoEdit(btnNm);
|
||||
}
|
||||
|
||||
console.log('[CheckOutPanel] InformationContainer handleButtonClick - setting tab to:', index);
|
||||
setTab(index);
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
|
||||
dispatch(
|
||||
@@ -157,13 +230,17 @@ export default function InformationContainer({
|
||||
);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
console.log('[CheckOutPanel] InformationContainer handleCancel called - hiding popup');
|
||||
dispatch(setHidePopup());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleDone = useCallback(() => {
|
||||
console.log('[CheckOutPanel] InformationContainer handleDone called - hiding popup and popping panel');
|
||||
dispatch(setHidePopup());
|
||||
dispatch(popPanel());
|
||||
}, [dispatch, checkoutData]);
|
||||
}, [dispatch]);
|
||||
|
||||
console.log('[CheckOutPanel] InformationContainer rendering - productList length:', checkoutData?.productList?.length);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -10,21 +10,50 @@ import TVirtualGridList from "../../../components/TVirtualGridList/TVirtualGridL
|
||||
import usePriceInfo from "../../../hooks/usePriceInfo";
|
||||
import { $L } from "../../../utils/helperMethods";
|
||||
import OrderItemCard, { SIZES } from "../components/OrderItemCard";
|
||||
import { BUYNOW_CONFIG } from "../../../utils/BuyNowConfig";
|
||||
import css from "./OrderItemsSideBar.module.less";
|
||||
|
||||
const SideBarContainer = SpotlightContainerDecorator("div");
|
||||
|
||||
export default function OrderItemsSideBar({ closeSideBar }) {
|
||||
const orderItemList = useSelector(
|
||||
console.log('[CheckOutPanel] OrderItemsSideBar mounted');
|
||||
|
||||
const reduxOrderItemList = useSelector(
|
||||
(state) => state.checkout?.checkoutData?.productList
|
||||
);
|
||||
console.log('[CheckOutPanel] OrderItemsSideBar reduxOrderItemList:', reduxOrderItemList);
|
||||
|
||||
// Check if reduxOrderItemList has actual data
|
||||
const hasValidOrderItemList = Array.isArray(reduxOrderItemList) && reduxOrderItemList.length > 0;
|
||||
console.log('[CheckOutPanel] OrderItemsSideBar hasValidOrderItemList:', hasValidOrderItemList);
|
||||
|
||||
const orderItemList = hasValidOrderItemList ? reduxOrderItemList : (BUYNOW_CONFIG.isMockMode() ? [
|
||||
{
|
||||
prdtId: 'MOCK_PRODUCT_1',
|
||||
prdtNm: 'Mock Product',
|
||||
prodQty: 1,
|
||||
prdtOpt: [{ prodOptCdCval: 'MOCK_OPT_1' }],
|
||||
patncLogPath: '/mock/image.jpg',
|
||||
expsPrdtNo: 'MOCK_EXP_1',
|
||||
currSign: '$',
|
||||
currSignLoc: 'left',
|
||||
shippingCharge: 0,
|
||||
auctProdYn: 'N',
|
||||
auctFinalPriceChgDt: null,
|
||||
imgUrls: ['/mock/image.jpg'],
|
||||
}
|
||||
] : null);
|
||||
|
||||
console.log('[CheckOutPanel] OrderItemsSideBar effectiveOrderItemList:', orderItemList);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[CheckOutPanel] OrderItemsSideBar focus effect');
|
||||
Spotlight.focus("orderItemSideBar_backButton");
|
||||
}, []);
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ index, ...rest }) => {
|
||||
console.log('[CheckOutPanel] OrderItemsSideBar renderItem - index:', index);
|
||||
const item = orderItemList[index];
|
||||
|
||||
return (
|
||||
@@ -49,6 +78,8 @@ export default function OrderItemsSideBar({ closeSideBar }) {
|
||||
[orderItemList]
|
||||
);
|
||||
|
||||
console.log('[CheckOutPanel] OrderItemsSideBar rendering - items count:', orderItemList?.length);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={css.bgLayer} onClick={closeSideBar} />
|
||||
@@ -74,7 +105,6 @@ export default function OrderItemsSideBar({ closeSideBar }) {
|
||||
) : null}
|
||||
</div>
|
||||
</SideBarContainer>
|
||||
;
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,23 +30,43 @@ export default function SummaryContainer({
|
||||
currSignLoc,
|
||||
doSendLogPaymentEntry,
|
||||
}) {
|
||||
console.log('[CheckOutPanel] SummaryContainer mounted');
|
||||
|
||||
const priceTotalData = useSelector(
|
||||
(state) => state.checkout?.checkoutTotalData
|
||||
);
|
||||
console.log('[CheckOutPanel] SummaryContainer priceTotalData:', priceTotalData);
|
||||
|
||||
const productList = useSelector(
|
||||
(state) => state.checkout?.checkoutData.productList?.[0]
|
||||
);
|
||||
console.log('[CheckOutPanel] SummaryContainer productList:', productList);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Check if priceTotalData has actual data (ordPmtReqAmt is the key field)
|
||||
const hasValidPriceTotalData = priceTotalData && Object.keys(priceTotalData).length > 0 && priceTotalData.ordPmtReqAmt;
|
||||
console.log('[CheckOutPanel] SummaryContainer hasValidPriceTotalData:', hasValidPriceTotalData);
|
||||
|
||||
// Mock Mode: priceTotalData가 없으면 가짜 데이터 제공
|
||||
const effectivePriceTotalData = hasValidPriceTotalData ? priceTotalData : {
|
||||
totProdPrc: 521.66,
|
||||
totDcAmt: 0,
|
||||
totDlvrAmt: 0,
|
||||
ordPmtNoTaxAmt: 521.66,
|
||||
ordTotTaxAmt: 50,
|
||||
ordPmtReqAmt: 571.66
|
||||
};
|
||||
|
||||
console.log('[CheckOutPanel] SummaryContainer effectivePriceTotalData:', effectivePriceTotalData);
|
||||
|
||||
const items = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: "Items",
|
||||
value: priceTotalData?.totProdPrc
|
||||
value: effectivePriceTotalData?.totProdPrc
|
||||
? formatCurrencyValue(
|
||||
priceTotalData.totProdPrc,
|
||||
effectivePriceTotalData.totProdPrc,
|
||||
currSign,
|
||||
currSignLoc
|
||||
)
|
||||
@@ -54,9 +74,9 @@ export default function SummaryContainer({
|
||||
},
|
||||
{
|
||||
name: "Your Coupon Savings",
|
||||
value: priceTotalData?.totDcAmt
|
||||
value: effectivePriceTotalData?.totDcAmt
|
||||
? formatCurrencyValue(
|
||||
priceTotalData.totDcAmt,
|
||||
effectivePriceTotalData.totDcAmt,
|
||||
currSign,
|
||||
currSignLoc,
|
||||
true
|
||||
@@ -65,9 +85,9 @@ export default function SummaryContainer({
|
||||
},
|
||||
{
|
||||
name: "Shipping & Handling",
|
||||
value: priceTotalData?.totDlvrAmt
|
||||
value: effectivePriceTotalData?.totDlvrAmt
|
||||
? formatCurrencyValue(
|
||||
priceTotalData.totDlvrAmt,
|
||||
effectivePriceTotalData.totDlvrAmt,
|
||||
currSign,
|
||||
currSignLoc
|
||||
)
|
||||
@@ -75,9 +95,9 @@ export default function SummaryContainer({
|
||||
},
|
||||
{
|
||||
name: "TOTAL (before Tax)",
|
||||
value: priceTotalData?.ordPmtNoTaxAmt
|
||||
value: effectivePriceTotalData?.ordPmtNoTaxAmt
|
||||
? formatCurrencyValue(
|
||||
priceTotalData.ordPmtNoTaxAmt,
|
||||
effectivePriceTotalData.ordPmtNoTaxAmt,
|
||||
currSign,
|
||||
currSignLoc
|
||||
)
|
||||
@@ -85,16 +105,16 @@ export default function SummaryContainer({
|
||||
},
|
||||
{
|
||||
name: "Estimated Sales Tax",
|
||||
value: priceTotalData?.ordTotTaxAmt
|
||||
value: effectivePriceTotalData?.ordTotTaxAmt
|
||||
? formatCurrencyValue(
|
||||
priceTotalData.ordTotTaxAmt,
|
||||
effectivePriceTotalData.ordTotTaxAmt,
|
||||
currSign,
|
||||
currSignLoc
|
||||
)
|
||||
: "-",
|
||||
},
|
||||
],
|
||||
[priceTotalData, currSign, currSignLoc]
|
||||
[effectivePriceTotalData, currSign, currSignLoc]
|
||||
);
|
||||
|
||||
const handleClickOrder = useCallback(() => {
|
||||
@@ -147,14 +167,14 @@ export default function SummaryContainer({
|
||||
|
||||
const estimatedTotal = useMemo(
|
||||
() =>
|
||||
priceTotalData?.ordPmtReqAmt
|
||||
effectivePriceTotalData?.ordPmtReqAmt
|
||||
? formatCurrencyValue(
|
||||
priceTotalData.ordPmtReqAmt,
|
||||
effectivePriceTotalData.ordPmtReqAmt,
|
||||
currSign,
|
||||
currSignLoc
|
||||
)
|
||||
: "-",
|
||||
[priceTotalData, currSign, currSignLoc]
|
||||
[effectivePriceTotalData, currSign, currSignLoc]
|
||||
);
|
||||
|
||||
const showAuctionNotice =
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
} from '../../../utils/fp';
|
||||
import { $L } from '../../../utils/helperMethods';
|
||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
|
||||
import ShowUserReviews from '../../UserReview/ShowUserReviews';
|
||||
// import CustomScrollbar from '../components/CustomScrollbar/CustomScrollbar';
|
||||
import DetailMobileSendPopUp from '../components/DetailMobileSendPopUp';
|
||||
@@ -226,23 +227,67 @@ export default function ProductAllSection({
|
||||
|
||||
// 단품(결제 가능 상품) - DetailPanel.backup.jsx와 동일한 로직
|
||||
const isBillingProductVisible = useMemo(() => {
|
||||
return (
|
||||
productData?.pmtSuptYn === 'Y' &&
|
||||
productData?.grPrdtProcYn === 'N' &&
|
||||
panelInfo?.prdtId &&
|
||||
webOSVersion >= '6.0'
|
||||
);
|
||||
// API Mode: 기존 로직 100% 유지 (절대 수정 안 함)
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
return (
|
||||
productData?.pmtSuptYn === 'Y' &&
|
||||
productData?.grPrdtProcYn === 'N' &&
|
||||
panelInfo?.prdtId &&
|
||||
webOSVersion >= '6.0'
|
||||
);
|
||||
}
|
||||
|
||||
// Mock Mode: 버전별 안전한 조건 우회
|
||||
// webOS 6.0 미만: Mock Mode에서도 버튼 비활성화 (버전 정책 존중)
|
||||
if (webOSVersion < '6.0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 상품 ID 필수
|
||||
if (!panelInfo?.prdtId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 구매 불가능한 상품인지 확인
|
||||
const isOriginallyUnbuyable =
|
||||
productData?.pmtSuptYn !== 'Y' ||
|
||||
productData?.grPrdtProcYn !== 'N';
|
||||
|
||||
// 구매 불가능 상품은 70% 확률로 표시
|
||||
if (isOriginallyUnbuyable) {
|
||||
return Math.random() < 0.7;
|
||||
}
|
||||
|
||||
// 이미 구매 가능한 상품은 항상 표시
|
||||
return true;
|
||||
}, [productData, webOSVersion, panelInfo?.prdtId]);
|
||||
|
||||
// 구매 불가 상품 - DetailPanel.backup.jsx와 동일한 로직
|
||||
const isUnavailableProductVisible = useMemo(() => {
|
||||
return (
|
||||
productData?.pmtSuptYn === 'N' ||
|
||||
(productData?.pmtSuptYn === 'Y' &&
|
||||
productData?.grPrdtProcYn === 'N' &&
|
||||
webOSVersion < '6.0' &&
|
||||
panelInfo?.prdtId)
|
||||
);
|
||||
// API Mode: 기존 로직 100% 유지
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
return (
|
||||
productData?.pmtSuptYn === 'N' ||
|
||||
(productData?.pmtSuptYn === 'Y' &&
|
||||
productData?.grPrdtProcYn === 'N' &&
|
||||
webOSVersion < '6.0' &&
|
||||
panelInfo?.prdtId)
|
||||
);
|
||||
}
|
||||
|
||||
// Mock Mode: webOS < 6.0인 경우만 구매 불가 상품 화면 표시
|
||||
if (webOSVersion < '6.0') {
|
||||
return (
|
||||
productData?.pmtSuptYn === 'N' ||
|
||||
(productData?.pmtSuptYn === 'Y' &&
|
||||
productData?.grPrdtProcYn === 'N' &&
|
||||
panelInfo?.prdtId)
|
||||
);
|
||||
}
|
||||
|
||||
// webOS >= 6.0: pmtSuptYn === 'N'인 상품도 70% 확률로 구매 가능하므로
|
||||
// 구매 불가능 상품 화면 표시 안 함 (대신 BUY NOW 버튼으로 표시됨)
|
||||
return false;
|
||||
}, [productData, webOSVersion, panelInfo?.prdtId]);
|
||||
|
||||
// 그룹 상품 - DetailPanel.backup.jsx와 동일한 로직
|
||||
@@ -272,13 +317,13 @@ export default function ProductAllSection({
|
||||
_debug,
|
||||
} = useReviews(productData.prdtId, panelInfo && panelInfo.patnrId);
|
||||
|
||||
console.log('[ProductAllSection_useReviewList] 🟡 반환 데이터:', {
|
||||
reviewVersion: _debug?.reviewVersion,
|
||||
previewReviewsLength: previewReviews?.length || 0,
|
||||
statsTotal: stats?.totalReviews,
|
||||
hasReviews,
|
||||
selectedReduxState: _debug?.reviewVersion === 1 ? 'reviewData' : 'reviewListData'
|
||||
});
|
||||
// console.log('[ProductAllSection_useReviewList] 🟡 반환 데이터:', {
|
||||
// reviewVersion: _debug?.reviewVersion,
|
||||
// previewReviewsLength: previewReviews?.length || 0,
|
||||
// statsTotal: stats?.totalReviews,
|
||||
// hasReviews,
|
||||
// selectedReduxState: _debug?.reviewVersion === 1 ? 'reviewData' : 'reviewListData'
|
||||
// });
|
||||
|
||||
// 별점 높은 순으로 정렬된 상위 5개 리뷰 (방법 2: useMemo 최적화)
|
||||
const topRatedPreviewReviews = useMemo(() => {
|
||||
@@ -317,7 +362,7 @@ export default function ProductAllSection({
|
||||
|
||||
// BUY NOW 버튼 클릭 핸들러 - Toast로 BuyOption 표시
|
||||
const handleBuyNowClick = useCallback((e) => {
|
||||
// console.log('[BuyNow] Buy Now button clicked');
|
||||
// console.log('[BuyNow] Buy Now button clicked');
|
||||
e.stopPropagation();
|
||||
dispatch(
|
||||
showToast({
|
||||
@@ -833,11 +878,13 @@ export default function ProductAllSection({
|
||||
</TButton>
|
||||
{hasReviews && (
|
||||
<>
|
||||
{/*
|
||||
{console.log('[ProductAllSection_useReviewList] 🎯 버튼 렌더링:', {
|
||||
hasReviews,
|
||||
reviewTotalCount,
|
||||
reviewVersion: _debug?.reviewVersion
|
||||
})}
|
||||
*/}
|
||||
<TButton
|
||||
className={classNames(
|
||||
css.userReviewsButton,
|
||||
@@ -913,7 +960,7 @@ export default function ProductAllSection({
|
||||
spotlightRestrict="none"
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<div className={css.productDetail}>
|
||||
<div className={css.productDetail}>
|
||||
{/* <LayoutSample onClick={handleLayoutSampleClick} /> */}
|
||||
<div
|
||||
id="product-details-section"
|
||||
|
||||
@@ -60,12 +60,12 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
isLoading: fallbackReviews.isLoading,
|
||||
};
|
||||
|
||||
console.log('[UserReviews_useReviewList] 🔵 데이터:', {
|
||||
dataSource: reviewsData ? 'ProductAllSection props' : 'fallback useReviews',
|
||||
fallbackVersion: fallbackReviews._debug?.reviewVersion,
|
||||
previewReviewsLength: actualReviewsData.previewReviews?.length || 0,
|
||||
statsTotal: actualReviewsData.stats?.totalReviews
|
||||
});
|
||||
// console.log('[UserReviews_useReviewList] 🔵 데이터:', {
|
||||
// dataSource: reviewsData ? 'ProductAllSection props' : 'fallback useReviews',
|
||||
// fallbackVersion: fallbackReviews._debug?.reviewVersion,
|
||||
// previewReviewsLength: actualReviewsData.previewReviews?.length || 0,
|
||||
// statsTotal: actualReviewsData.stats?.totalReviews
|
||||
// });
|
||||
|
||||
// 팝업 상태 관리 - 모드와 선택된 이미지 인덱스 추가
|
||||
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
||||
|
||||
@@ -42,6 +42,8 @@ import TButton from '../../../components/TButton/TButton';
|
||||
import TPopUp from '../../../components/TPopUp/TPopUp';
|
||||
import * as Config from '../../../utils/Config';
|
||||
import { $L } from '../../../utils/helperMethods';
|
||||
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
|
||||
import { createMockProductOptionData } from '../../../utils/BuyNowDataManipulator';
|
||||
import FavoriteBtn from '../components/FavoriteBtn';
|
||||
import styles from './BuyOption.module.less';
|
||||
import BuyOptionPriceBlock from './BuyOptionPriceBlock';
|
||||
@@ -75,7 +77,7 @@ const BuyOption = ({
|
||||
);
|
||||
const reduxProductInfo = useSelector((state) => state.main.productData);
|
||||
const productInfo = propsProductInfo || reduxProductInfo;
|
||||
const productOptionInfos = useSelector((state) => state.product.prdtOptInfo);
|
||||
const reduxProductOptionInfos = useSelector((state) => state.product.prdtOptInfo);
|
||||
const productData = useSelector((state) => state.main.productData);
|
||||
const { partnerCoupon } = useSelector(
|
||||
(state) => state.coupon.productCouponSearchData || {}
|
||||
@@ -106,6 +108,24 @@ const BuyOption = ({
|
||||
const selectedPatnrId = propsSelectedPatnrId || productInfo?.patnrId;
|
||||
const selectedPrdtId = propsSelectedPrdtId || productInfo?.prdtId;
|
||||
|
||||
// Mock Mode에서 옵션 데이터 처리
|
||||
const productOptionInfos = useMemo(() => {
|
||||
// API Mode: 기존 로직 100% 유지
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
return reduxProductOptionInfos;
|
||||
}
|
||||
|
||||
// Mock Mode: 옵션 데이터가 없으면 Mock 데이터 생성
|
||||
if (!reduxProductOptionInfos || reduxProductOptionInfos.length === 0) {
|
||||
const mockOptionData = createMockProductOptionData(productData);
|
||||
// Mock 옵션 데이터 배열 반환 (기존 구조와 호환)
|
||||
return mockOptionData?.optionList || [];
|
||||
}
|
||||
|
||||
// Mock Mode이고 옵션 데이터가 있으면 그대로 사용
|
||||
return reduxProductOptionInfos;
|
||||
}, [reduxProductOptionInfos, productData]);
|
||||
|
||||
// logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용)
|
||||
const logInfo = useMemo(() => {
|
||||
if (productData) {
|
||||
@@ -215,21 +235,26 @@ const BuyOption = ({
|
||||
}, [productOptionInfos, selectedBtnOptIdx, isOptionValue]);
|
||||
|
||||
// 필수 데이터 로드 (SingleOption과 동일)
|
||||
// Mock Mode: API 호출 스킵 (Mock 데이터만 사용)
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
getProductOption({
|
||||
patnrId: selectedPatnrId,
|
||||
prdtId: selectedPrdtId,
|
||||
})
|
||||
);
|
||||
// API Mode: 실제 API 호출
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
dispatch(
|
||||
getProductOption({
|
||||
patnrId: selectedPatnrId,
|
||||
prdtId: selectedPrdtId,
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(
|
||||
getProductCouponSearch({
|
||||
patnrId: selectedPatnrId,
|
||||
prdtId: selectedPrdtId,
|
||||
mbrNo: userNumber,
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
getProductCouponSearch({
|
||||
patnrId: selectedPatnrId,
|
||||
prdtId: selectedPrdtId,
|
||||
mbrNo: userNumber,
|
||||
})
|
||||
);
|
||||
}
|
||||
// Mock Mode: API 호출 하지 않음
|
||||
}, [dispatch, selectedPatnrId, selectedPrdtId, userNumber]);
|
||||
|
||||
// 포커스 관리 로직 (SingleOption과 유사)
|
||||
@@ -403,26 +428,45 @@ const BuyOption = ({
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(
|
||||
getMyInfoCheckoutInfo(
|
||||
{
|
||||
mbrNo: userNumber,
|
||||
dirPurcSelYn: 'Y',
|
||||
cartList: [
|
||||
{
|
||||
patnrId: selectedPatnrId,
|
||||
prdtId: selectedPrdtId,
|
||||
prodOptCdCval: selectedOptions?.prodOptCdCval
|
||||
? selectedOptions.prodOptCdCval
|
||||
: null,
|
||||
prodQty: String(quantity),
|
||||
prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval,
|
||||
},
|
||||
],
|
||||
},
|
||||
checkOutValidate
|
||||
)
|
||||
);
|
||||
// Mock Mode: API 호출 스킵
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
dispatch(
|
||||
getMyInfoCheckoutInfo(
|
||||
{
|
||||
mbrNo: userNumber,
|
||||
dirPurcSelYn: 'Y',
|
||||
cartList: [
|
||||
{
|
||||
patnrId: selectedPatnrId,
|
||||
prdtId: selectedPrdtId,
|
||||
prodOptCdCval: selectedOptions?.prodOptCdCval
|
||||
? selectedOptions.prodOptCdCval
|
||||
: null,
|
||||
prodQty: String(quantity),
|
||||
prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval,
|
||||
},
|
||||
],
|
||||
},
|
||||
checkOutValidate
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Mock Mode: 체크아웃 페이지로 이동 (시뮬레이션)
|
||||
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);
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: Config.panel_names.CHECKOUT_PANEL,
|
||||
panelInfo: checkoutPanelInfo,
|
||||
})
|
||||
);
|
||||
console.log('[BuyOption] pushPanel dispatch completed');
|
||||
}
|
||||
}
|
||||
dispatch(clearAllToasts());
|
||||
}, [
|
||||
@@ -478,23 +522,29 @@ const BuyOption = ({
|
||||
// })
|
||||
// );
|
||||
|
||||
// 장바구니에 추가
|
||||
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,
|
||||
}
|
||||
})
|
||||
);
|
||||
// Mock Mode: API 호출 스킵
|
||||
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,
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Mock Mode: 로컬 장바구니 데이터 생성 후 CartPanel로 이동
|
||||
console.log('[BuyOption] Mock Mode - Adding to cart (Mock)');
|
||||
}
|
||||
|
||||
// CartPanel로 이동
|
||||
dispatch(
|
||||
|
||||
@@ -48,6 +48,7 @@ import usePrevious from '../../hooks/usePrevious';
|
||||
import * as Config from '../../utils/Config';
|
||||
import { panel_names } from '../../utils/Config';
|
||||
import { $L, getErrorMessage, getSpottableDescendants } from '../../utils/helperMethods';
|
||||
import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig';
|
||||
import { SpotlightIds } from '../../utils/SpotlightIds';
|
||||
import CartPanel from '../CartPanel/CartPanel';
|
||||
import CategoryPanel from '../CategoryPanel/CategoryPanel';
|
||||
@@ -162,6 +163,16 @@ export default function MainView({ className, initService }) {
|
||||
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
// BUYNOW_CONFIG 초기화 (마운트 시 한 번만 실행)
|
||||
useEffect(() => {
|
||||
// TV 배포용: Mock Mode 하드코딩 활성화
|
||||
// 모든 상품에서 BUY NOW 버튼이 표시됨
|
||||
const mockMode = true;
|
||||
|
||||
// 모듈 변수에 저장 (이후 ProductAllSection 등에서 메모리에서만 읽음)
|
||||
BUYNOW_CONFIG.init(mockMode);
|
||||
}, []);
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log('🔍 MainView 팝업 상태 변경:', {
|
||||
// popupVisible,
|
||||
|
||||
@@ -86,23 +86,23 @@ const ShowUserReviews = ({ hasVideo, launchedFromPlayer, onFocus, onBlur }) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[ShowUserReviews_v2] 컴포넌트 마운트/업데이트', {
|
||||
hasReviewData: !!reviewListData,
|
||||
reviewCount: reviewListData?.length || 0,
|
||||
statsTotalReviews: stats?.totalReviews,
|
||||
shouldShowButton: reviewListData && reviewListData.length > 5,
|
||||
spotlightUpTarget: 'user-review-at-last',
|
||||
reviewVersion: REVIEW_VERSION
|
||||
});
|
||||
}, [reviewListData, stats]);
|
||||
// useEffect(() => {
|
||||
// console.log('[ShowUserReviews_v2] 컴포넌트 마운트/업데이트', {
|
||||
// hasReviewData: !!reviewListData,
|
||||
// reviewCount: reviewListData?.length || 0,
|
||||
// statsTotalReviews: stats?.totalReviews,
|
||||
// shouldShowButton: reviewListData && reviewListData.length > 5,
|
||||
// spotlightUpTarget: 'user-review-at-last',
|
||||
// reviewVersion: REVIEW_VERSION
|
||||
// });
|
||||
// }, [reviewListData, stats]);
|
||||
|
||||
// v2 API 데이터로 렌더링 조건 수정: 5개 이상일 때만 보이기
|
||||
if (!reviewListData || reviewListData.length <= 5) {
|
||||
console.log('[ShowUserReviews_v2] ❌ 버튼 숨김:', {
|
||||
reason: !reviewListData ? 'reviewListData가 없음' : 'reviewListData.length <= 5',
|
||||
reviewListLength: reviewListData?.length || 0
|
||||
});
|
||||
// console.log('[ShowUserReviews_v2] ❌ 버튼 숨김:', {
|
||||
// reason: !reviewListData ? 'reviewListData가 없음' : 'reviewListData.length <= 5',
|
||||
// reviewListLength: reviewListData?.length || 0
|
||||
// });
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user