[251102] fix: CartPanel mock-2
🕐 커밋 시간: 2025. 11. 02. 11:04:08 📊 변경 통계: • 총 파일: 10개 • 추가: +453줄 • 삭제: -162줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/mockCartActions.js ~ com.twin.app.shoptime/src/reducers/mockCartReducer.js ~ com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js ~ com.twin.app.shoptime/src/views/CartPanel/CartPanel.jsx ~ com.twin.app.shoptime/src/views/CartPanel/CartProduct.jsx ~ com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx ~ com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/SummaryContainerMock.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx ~ com.twin.app.shoptime/src/views/MainView/MainView.jsx 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • 공통 유틸리티 함수 최적화 • UI 컴포넌트 아키텍처 개선 • 대규모 기능 개발 • 모듈 구조 개선
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { BUYNOW_CONFIG } from '../utils/BuyNowConfig';
|
||||
import { createMockCartListData, addMockCartItem, removeMockCartItem, updateMockCartItemQuantity } from '../utils/BuyNowDataManipulator';
|
||||
import { createMockCartListData, createMockCartData, addMockCartItem, removeMockCartItem, updateMockCartItemQuantity } from '../utils/BuyNowDataManipulator';
|
||||
|
||||
// Mock Cart Action Types
|
||||
export const MOCK_CART_TYPES = {
|
||||
@@ -9,11 +9,12 @@ export const MOCK_CART_TYPES = {
|
||||
UPDATE_MOCK_CART_ITEM: 'mockCart/UPDATE_MOCK_CART_ITEM',
|
||||
CLEAR_MOCK_CART: 'mockCart/CLEAR_MOCK_CART',
|
||||
SET_MOCK_CART_QUANTITY: 'mockCart/SET_MOCK_CART_QUANTITY',
|
||||
UPDATE_SELECTED_ITEMS: 'mockCart/UPDATE_SELECTED_ITEMS',
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock 장바구니 초기화
|
||||
* BuyOption에서 ADD TO CART 시 호출
|
||||
* BuyOption에서 ADD TO CART 시 호출 - 기존 장바구니에 상품 추가
|
||||
*/
|
||||
export const initializeMockCart = (productData, optionInfo = {}, quantity = 1) => (dispatch, getState) => {
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
@@ -22,20 +23,19 @@ export const initializeMockCart = (productData, optionInfo = {}, quantity = 1) =
|
||||
|
||||
console.log('[MockCartActions] initializeMockCart - productData:', productData);
|
||||
|
||||
// 초기 Mock 장바구니 데이터 생성
|
||||
const mockCartData = createMockCartListData(productData, optionInfo, quantity);
|
||||
// 기존 장바구니 데이터 확인
|
||||
const currentCart = getState().mockCart.cartInfo || [];
|
||||
console.log('[MockCartActions] initializeMockCart - current cart items:', currentCart.length);
|
||||
|
||||
dispatch({
|
||||
type: MOCK_CART_TYPES.INIT_MOCK_CART,
|
||||
payload: {
|
||||
items: mockCartData,
|
||||
lastAction: {
|
||||
type: 'init',
|
||||
data: productData,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
},
|
||||
});
|
||||
// 새 상품 데이터 생성
|
||||
const newCartItem = createMockCartData(productData, optionInfo, quantity);
|
||||
|
||||
if (newCartItem) {
|
||||
// addToMockCart를 사용하여 기존 장바구니에 상품 추가 (덮어쓰기 방지)
|
||||
dispatch(addToMockCart(productData, optionInfo, quantity));
|
||||
} else {
|
||||
console.log('[MockCartActions] initializeMockCart - Failed to create cart item');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -177,26 +177,47 @@ export const clearMockCart = () => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock 장바구니 데이터 재설정 (초기 상태로 복원)
|
||||
* Mock 장바구니 데이터 재설정 (빈 장바구니로 복원)
|
||||
*/
|
||||
export const resetMockCart = () => (dispatch, getState) => {
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[MockCartActions] resetMockCart');
|
||||
|
||||
// 기본 Mock 장바구니 데이터로 재설정
|
||||
const defaultMockCartData = createMockCartListData();
|
||||
console.log('[MockCartActions] resetMockCart - Clearing cart to empty');
|
||||
|
||||
// 빈 장바구니로 재설정 (기본 Mock 상품 없음)
|
||||
dispatch({
|
||||
type: MOCK_CART_TYPES.INIT_MOCK_CART,
|
||||
payload: {
|
||||
items: defaultMockCartData,
|
||||
items: [], // 빈 배열로 재설정
|
||||
lastAction: {
|
||||
type: 'reset',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 선택된 상품들 업데이트
|
||||
* @param {Array} selectedItems - 선택된 상품들의 prodSno 배열
|
||||
*/
|
||||
export const updateSelectedItems = (selectedItems) => (dispatch, getState) => {
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[MockCartActions] updateSelectedItems - selectedItems:', selectedItems);
|
||||
|
||||
dispatch({
|
||||
type: MOCK_CART_TYPES.UPDATE_SELECTED_ITEMS,
|
||||
payload: {
|
||||
selectedItems,
|
||||
lastAction: {
|
||||
type: 'update_selected',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -10,6 +10,8 @@ const isBrowser = typeof window !== 'undefined' && typeof window.localStorage !=
|
||||
const initialState = {
|
||||
// Mock 장바구니 목록
|
||||
cartInfo: [],
|
||||
// 선택된 상품들
|
||||
selectedItems: [],
|
||||
// 마지막 실행된 액션 정보
|
||||
lastAction: null,
|
||||
// 에러 정보
|
||||
@@ -34,6 +36,7 @@ const saveToLocalStorage = (state) => {
|
||||
try {
|
||||
const dataToSave = {
|
||||
cartInfo: state.cartInfo,
|
||||
selectedItems: state.selectedItems,
|
||||
totalQuantity: state.totalQuantity,
|
||||
totalPrice: state.totalPrice,
|
||||
lastUpdated: state.lastUpdated,
|
||||
@@ -57,6 +60,7 @@ const loadFromLocalStorage = () => {
|
||||
return {
|
||||
...initialState,
|
||||
cartInfo: parsedData.cartInfo || [],
|
||||
selectedItems: parsedData.selectedItems || [],
|
||||
totalQuantity: parsedData.totalQuantity || 0,
|
||||
totalPrice: parsedData.totalPrice || 0,
|
||||
lastUpdated: parsedData.lastUpdated || Date.now(),
|
||||
@@ -254,6 +258,7 @@ export const mockCartReducer = (state = loadFromLocalStorage(), action) => {
|
||||
const newState = {
|
||||
...state,
|
||||
cartInfo: [],
|
||||
selectedItems: [], // 선택된 상품들도 초기화
|
||||
lastAction: action.payload.lastAction,
|
||||
error: null,
|
||||
lastUpdated: Date.now(),
|
||||
@@ -266,6 +271,21 @@ export const mockCartReducer = (state = loadFromLocalStorage(), action) => {
|
||||
return newState;
|
||||
}
|
||||
|
||||
case MOCK_CART_TYPES.UPDATE_SELECTED_ITEMS: {
|
||||
const { selectedItems } = action.payload;
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
selectedItems,
|
||||
lastAction: action.payload.lastAction,
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
|
||||
// localStorage에 저장
|
||||
saveToLocalStorage(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -107,82 +107,18 @@ export const createMockCartListData = (productData, optionInfo = {}, quantity =
|
||||
return [];
|
||||
}
|
||||
|
||||
// ✅ 기본 Mock 장바구니 데이터 (CartPanel 구조에 맞춰 개선 - 모든 이미지 필드 포함)
|
||||
const defaultMockItems = [
|
||||
// QVC 상품
|
||||
{
|
||||
prodSno: 'MOCK_CART_1',
|
||||
prdtId: 'MOCK_QVC_001',
|
||||
prdtNm: 'Mock Premium Wireless Headphones',
|
||||
patnrId: 'QVC',
|
||||
patncNm: 'QVC',
|
||||
patncLogPath: '/assets/images/ic-partners-qvc@3x.png',
|
||||
imgUrl: '/assets/images/img-thumb-empty-144@3x.png',
|
||||
thumbnailUrl: '/assets/images/img-thumb-empty-144@3x.png',
|
||||
imgList: [{ imgUrl: '/assets/images/img-thumb-empty-144@3x.png' }],
|
||||
imgUrls: [{ imgUrl: '/assets/images/img-thumb-empty-144@3x.png' }], // CheckOutPanel 호환성
|
||||
price2: '299.99', // 원가
|
||||
price3: '199.99', // 할인가
|
||||
price5: '29.99', // 옵션 할인가
|
||||
optPrc: '29.99', // 옵션 가격
|
||||
shippingCharge: '12.99',
|
||||
prodQty: 1,
|
||||
optNm: 'Color: Black, Warranty: 2 Years',
|
||||
addedAt: new Date().toISOString()
|
||||
},
|
||||
// HSN 상품
|
||||
{
|
||||
prodSno: 'MOCK_CART_2',
|
||||
prdtId: 'MOCK_HSN_001',
|
||||
prdtNm: 'Mock Smart Watch Pro',
|
||||
patnrId: 'HSN',
|
||||
patncNm: 'HSN',
|
||||
patncLogPath: '/assets/images/ic-partners-hsn@3x.png',
|
||||
imgUrl: '/assets/images/img-thumb-empty-144@3x.png',
|
||||
thumbnailUrl: '/assets/images/img-thumb-empty-144@3x.png',
|
||||
imgList: [{ imgUrl: '/assets/images/img-thumb-empty-144@3x.png' }],
|
||||
imgUrls: [{ imgUrl: '/assets/images/img-thumb-empty-144@3x.png' }], // CheckOutPanel 호환성
|
||||
price2: '399.99',
|
||||
price3: '299.99',
|
||||
price5: '49.99',
|
||||
optPrc: '49.99',
|
||||
shippingCharge: '9.99',
|
||||
prodQty: 2,
|
||||
optNm: 'Color: Silver, Size: 42mm',
|
||||
addedAt: new Date().toISOString()
|
||||
},
|
||||
// 다른 파트너사 상품
|
||||
{
|
||||
prodSno: 'MOCK_CART_3',
|
||||
prdtId: 'MOCK_EVINE_001',
|
||||
prdtNm: 'Mock Luxury Skincare Set',
|
||||
patnrId: 'EVINE',
|
||||
patncNm: 'EVINE',
|
||||
patncLogPath: '/assets/images/ic-partners-evine@3x.png',
|
||||
imgUrl: '/assets/images/img-thumb-empty-144@3x.png',
|
||||
thumbnailUrl: '/assets/images/img-thumb-empty-144@3x.png',
|
||||
imgList: [{ imgUrl: '/assets/images/img-thumb-empty-144@3x.png' }],
|
||||
imgUrls: [{ imgUrl: '/assets/images/img-thumb-empty-144@3x.png' }], // CheckOutPanel 호환성
|
||||
price2: '149.99',
|
||||
price3: '99.99',
|
||||
price5: '19.99',
|
||||
optPrc: '19.99',
|
||||
shippingCharge: '7.99',
|
||||
prodQty: 1,
|
||||
optNm: 'Size: Full Set, Type: Anti-Aging',
|
||||
addedAt: new Date().toISOString()
|
||||
}
|
||||
];
|
||||
|
||||
// 초기 상품 데이터가 있는 경우
|
||||
// 초기 상품 데이터가 있는 경우에만 해당 상품 추가
|
||||
if (productData) {
|
||||
const newCartItem = createMockCartData(productData, optionInfo, quantity);
|
||||
if (newCartItem) {
|
||||
return [newCartItem, ...defaultMockItems];
|
||||
console.log('[BuyNowDataManipulator] createMockCartListData - Adding only user product:', newCartItem.prdtNm);
|
||||
return [newCartItem];
|
||||
}
|
||||
}
|
||||
|
||||
return defaultMockItems;
|
||||
// 기본 Mock 상품은 추가하지 않음
|
||||
console.log('[BuyNowDataManipulator] createMockCartListData - No product data, returning empty array');
|
||||
return [];
|
||||
};
|
||||
|
||||
// Mock 장바구니 데이터를 저장할 전역 변수
|
||||
|
||||
@@ -86,22 +86,26 @@ export default function CartPanel({ spotlightId, scrollOptions = [], panelInfo }
|
||||
console.log('[CartPanel] Component mounted - isMockMode:', isMockMode, 'panelInfo:', panelInfo);
|
||||
|
||||
if (isMockMode) {
|
||||
// Mock Mode: panelInfo가 있으면 해당 상품 추가, 없으면 기본 Mock 데이터 설정
|
||||
// Mock Mode: panelInfo가 있으면 해당 상품 추가
|
||||
if (panelInfo?.productInfo) {
|
||||
console.log('[CartPanel] Mock Mode - Adding product from panelInfo:', panelInfo.productInfo);
|
||||
console.log('[CartPanel] Mock Mode - Option info:', panelInfo.optionInfo);
|
||||
console.log('[CartPanel] Mock Mode - Quantity:', panelInfo.quantity);
|
||||
|
||||
// panelInfo의 상품 정보로 Mock 장바구니 초기화 (기존 데이터 유지)
|
||||
// panelInfo의 상품 정보로 Mock 장바구니에 상품 추가 (기존 데이터 유지)
|
||||
console.log('[CartPanel] Mock Mode - Adding product to existing cart:', panelInfo.productInfo.prdtNm);
|
||||
dispatch(initializeMockCart(
|
||||
panelInfo.productInfo,
|
||||
panelInfo.optionInfo || {},
|
||||
panelInfo.quantity || 1
|
||||
));
|
||||
} else {
|
||||
// panelInfo가 없으면 기본 Mock 장바구니 데이터로 설정
|
||||
console.log('[CartPanel] Mock Mode - Initializing with default mock data');
|
||||
} else if (!mockCartData || mockCartData.length === 0) {
|
||||
// localStorage에 데이터가 없을 때만 기본 Mock 데이터로 설정
|
||||
console.log('[CartPanel] Mock Mode - No existing cart data, initializing with default mock data');
|
||||
dispatch(resetMockCart());
|
||||
} else {
|
||||
// localStorage에 데이터가 있으면 그대로 사용
|
||||
console.log('[CartPanel] Mock Mode - Using existing cart data from localStorage:', mockCartData.length, 'items');
|
||||
}
|
||||
} else if (userNumber) {
|
||||
// API Mode: 실제 API 호출
|
||||
@@ -110,6 +114,17 @@ export default function CartPanel({ spotlightId, scrollOptions = [], panelInfo }
|
||||
}
|
||||
}, [dispatch, userNumber, isMockMode, panelInfo]);
|
||||
|
||||
// Mock 장바구니 데이터 변경 감지 (디버깅용)
|
||||
useEffect(() => {
|
||||
console.log('[CartPanel] mockCartData changed:', mockCartData?.length, 'items');
|
||||
console.log('[CartPanel] mockCartData:', JSON.stringify(mockCartData));
|
||||
}, [mockCartData]);
|
||||
|
||||
// displayCartData 변경 감지 (디버깅용)
|
||||
useEffect(() => {
|
||||
console.log('[CartPanel] displayCartData changed:', displayCartData?.length, 'items');
|
||||
}, [displayCartData]);
|
||||
|
||||
const {
|
||||
getScrollTo,
|
||||
getScrollTo: getScrollToBody,
|
||||
|
||||
@@ -8,6 +8,8 @@ import React, {
|
||||
import classNames from 'classnames';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import { updateSelectedItems } from '../../actions/mockCartActions';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
@@ -30,6 +32,7 @@ const CartProduct = ({ cartInfo }) => {
|
||||
|
||||
// 항상 호출되어야 하는 Hook들
|
||||
const fallbackCartData = useSelector((state) => state.cart.getMyinfoCartSearch.cartInfo);
|
||||
const selectedItems = useSelector((state) => state.mockCart.selectedItems || []);
|
||||
|
||||
// 실제 장바구니 데이터와 Mock 데이터 중 선택
|
||||
const cartData = cartInfo || fallbackCartData;
|
||||
@@ -129,6 +132,27 @@ const CartProduct = ({ cartInfo }) => {
|
||||
}
|
||||
}, [dispatch, isMockMode]);
|
||||
|
||||
// 체크박스 선택 핸들러
|
||||
const handleCheckboxChange = useCallback((prodSno, isChecked) => {
|
||||
if (isMockMode) {
|
||||
let newSelectedItems;
|
||||
if (isChecked) {
|
||||
// 상품 선택
|
||||
newSelectedItems = [...selectedItems, prodSno];
|
||||
} else {
|
||||
// 상품 선택 해제
|
||||
newSelectedItems = selectedItems.filter(id => id !== prodSno);
|
||||
}
|
||||
dispatch(updateSelectedItems(newSelectedItems));
|
||||
console.log('[CartProduct] Checkbox changed - prodSno:', prodSno, 'isChecked:', isChecked, 'selectedItems:', newSelectedItems);
|
||||
}
|
||||
}, [dispatch, isMockMode, selectedItems]);
|
||||
|
||||
// 상품이 선택되었는지 확인
|
||||
const isItemSelected = useCallback((prodSno) => {
|
||||
return selectedItems.includes(prodSno);
|
||||
}, [selectedItems]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.entries(groupedCartData).map(([partnerKey, group]) => {
|
||||
@@ -196,7 +220,9 @@ const CartProduct = ({ cartInfo }) => {
|
||||
<div className={css.checkBox}>
|
||||
<TCheckBoxSquare
|
||||
className={css.customeCheckbox}
|
||||
spotlightId="productCheckbox"
|
||||
spotlightId={`productCheckbox-${item.prodSno}`}
|
||||
checked={isItemSelected(item.prodSno)}
|
||||
onChange={(isChecked) => handleCheckboxChange(item.prodSno, isChecked)}
|
||||
/>
|
||||
<span className={css.productId}>
|
||||
ID : {item.prdtId}
|
||||
|
||||
@@ -5,6 +5,7 @@ import TButton from '../../components/TButton/TButton';
|
||||
import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig';
|
||||
import { pushPanel } from '../../actions/panelActions';
|
||||
import Config from '../../utils/Config';
|
||||
import { calculateOrderSummaryFromProductInfo } from '../../utils/mockDataSafetyUtils';
|
||||
import css from './CartSidebar.module.less';
|
||||
|
||||
const CartSidebar = ({ cartInfo }) => {
|
||||
@@ -15,21 +16,118 @@ const CartSidebar = ({ cartInfo }) => {
|
||||
|
||||
// 실제 장바구니 데이터 (API 모드일 때만 사용)
|
||||
const fallbackCartInfo = useSelector((state) => state.cart.getMyinfoCartSearch.cartInfo);
|
||||
const selectedItems = useSelector((state) => state.mockCart.selectedItems || []);
|
||||
|
||||
// 사용할 장바구니 데이터 결정
|
||||
const displayCartInfo = cartInfo || (isMockMode ? null : fallbackCartInfo);
|
||||
|
||||
// Mock 데이터 또는 실제 데이터 계산
|
||||
// 선택된 상품들만 필터링 - 항상 선택된 상품들만 반환
|
||||
const getSelectedItems = useCallback((items) => {
|
||||
console.log('[CartSidebar] getSelectedItems called - isMockMode:', isMockMode, 'selectedItems:', selectedItems);
|
||||
|
||||
if (!items || !Array.isArray(items)) {
|
||||
console.log('[CartSidebar] No items provided, returning empty array');
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isMockMode) {
|
||||
console.log('[CartSidebar] API Mode - returning all items');
|
||||
return items;
|
||||
}
|
||||
|
||||
if (selectedItems.length === 0) {
|
||||
console.log('[CartSidebar] No items selected, returning empty array');
|
||||
return []; // 선택된 상품이 없으면 빈 배열 반환
|
||||
}
|
||||
|
||||
const filtered = items.filter(item => {
|
||||
const itemId = item.prodSno || item.cartId;
|
||||
const isSelected = selectedItems.includes(itemId);
|
||||
console.log('[CartSidebar] Item filter:', {
|
||||
itemName: item.prdtNm,
|
||||
itemId,
|
||||
isSelected
|
||||
});
|
||||
return isSelected;
|
||||
});
|
||||
|
||||
console.log('[CartSidebar] Filtered selected items:', filtered.length, 'out of', items.length);
|
||||
return filtered;
|
||||
}, [isMockMode, selectedItems]);
|
||||
|
||||
// Mock 데이터 또는 실제 데이터 계산 (선택된 상품만) - CheckOutPanel 방식 적용
|
||||
const calculatedData = useMemo(() => {
|
||||
if (isMockMode) {
|
||||
// Mock Mode: 기본 mockData 사용
|
||||
return {
|
||||
itemCount: 3,
|
||||
subtotal: 199.97,
|
||||
optionTotal: 29.99,
|
||||
shippingHandling: 19.99,
|
||||
orderTotalBeforeTax: 249.95,
|
||||
};
|
||||
// Mock Mode: 선택된 상품들로 개별 가격 계산
|
||||
if (displayCartInfo && Array.isArray(displayCartInfo) && displayCartInfo.length > 0) {
|
||||
const selectedCartItems = getSelectedItems(displayCartInfo);
|
||||
console.log('[CartSidebar] Selected items for calculation:', selectedCartItems);
|
||||
|
||||
// CheckOutPanel 방식: 각 상품의 정확한 가격 추출
|
||||
let totalItems = 0;
|
||||
let totalCoupon = 0;
|
||||
let totalShipping = 0;
|
||||
let totalQuantity = 0;
|
||||
|
||||
selectedCartItems.forEach((item) => {
|
||||
console.log('[CartSidebar] Processing item:', item.prdtNm, 'price fields:', {
|
||||
price2: item.price2,
|
||||
price3: item.price3,
|
||||
price5: item.price5,
|
||||
finalPrice: item.finalPrice,
|
||||
discountPrice: item.discountPrice,
|
||||
});
|
||||
|
||||
// CheckOutPanel의 calculateOrderSummaryFromProductInfo 로직 사용
|
||||
const orderSummary = calculateOrderSummaryFromProductInfo(item);
|
||||
|
||||
const qty = item.prodQty || item.qty || 1;
|
||||
const itemPrice = orderSummary.items; // 개별 상품 가격
|
||||
const itemCoupon = orderSummary.couponSavings; // 개별 상품 쿠폰
|
||||
const itemShipping = orderSummary.shipping; // 개별 상품 배송비
|
||||
|
||||
totalItems += itemPrice * qty;
|
||||
totalCoupon += itemCoupon * qty;
|
||||
totalShipping += itemShipping * qty; // 배송비도 수량만큼 계산
|
||||
totalQuantity += qty;
|
||||
|
||||
console.log('[CartSidebar] Item calculation:', {
|
||||
name: item.prdtNm,
|
||||
qty,
|
||||
itemPrice,
|
||||
itemCoupon,
|
||||
itemShipping,
|
||||
runningTotal: totalItems
|
||||
});
|
||||
});
|
||||
|
||||
const subtotal = Math.max(0, totalItems - totalCoupon + totalShipping);
|
||||
|
||||
console.log('[CartSidebar] Final calculation for selected items:', {
|
||||
totalQuantity,
|
||||
totalItems,
|
||||
totalCoupon,
|
||||
totalShipping,
|
||||
subtotal,
|
||||
});
|
||||
|
||||
return {
|
||||
itemCount: totalQuantity,
|
||||
subtotal: totalItems,
|
||||
optionTotal: totalCoupon,
|
||||
shippingHandling: totalShipping,
|
||||
orderTotalBeforeTax: subtotal,
|
||||
};
|
||||
} else {
|
||||
// Mock Mode에 데이터가 없는 경우
|
||||
return {
|
||||
itemCount: 0,
|
||||
subtotal: 0,
|
||||
optionTotal: 0,
|
||||
shippingHandling: 0,
|
||||
orderTotalBeforeTax: 0,
|
||||
};
|
||||
}
|
||||
} else if (displayCartInfo && Array.isArray(displayCartInfo) && displayCartInfo.length > 0) {
|
||||
// API Mode: 실제 장바구니 데이터로 계산
|
||||
const itemCount = displayCartInfo.reduce((sum, item) => sum + (item.prodQty || 1), 0);
|
||||
@@ -62,21 +160,36 @@ const CartSidebar = ({ cartInfo }) => {
|
||||
orderTotalBeforeTax: 0,
|
||||
};
|
||||
}
|
||||
}, [isMockMode, displayCartInfo]);
|
||||
}, [isMockMode, displayCartInfo, getSelectedItems]);
|
||||
|
||||
// 체크아웃 버튼 클릭 핸들러
|
||||
const handleCheckoutClick = useCallback(() => {
|
||||
const handleCheckoutClick = useCallback(() => {
|
||||
|
||||
if (isMockMode) {
|
||||
// Mock Mode: 바로 체크아웃 패널로 이동
|
||||
console.log('[CartSidebar] Mock Mode - Going to Checkout');
|
||||
dispatch(pushPanel({
|
||||
name: Config.panel_names.CHECKOUT_PANEL,
|
||||
}));
|
||||
console.log('[CartSidebar] Mock Mode - cartInfo:', JSON.stringify(cartInfo));
|
||||
|
||||
// ✅ panelInfo에 cartInfo를 포함하여 전달
|
||||
// CheckOutPanel에서 productList로 사용할 수 있도록 구성
|
||||
const cartItems = cartInfo && Array.isArray(cartInfo) ? cartInfo : [];
|
||||
|
||||
console.log('[CartSidebar] Mock Mode - cartItems:', JSON.stringify(cartItems));
|
||||
|
||||
// dispatch(pushPanel({
|
||||
// name: Config.panel_names.CHECKOUT_PANEL,
|
||||
// panelInfo: {
|
||||
// productInfo: cartItems.length > 0 ? cartItems[0] : null, // 첫 번째 상품
|
||||
// cartItems: cartItems, // 전체 카트 아이템
|
||||
// isFromCart: true, // CartPanel에서 왔음을 표시
|
||||
// }
|
||||
// }));
|
||||
|
||||
} else {
|
||||
// API Mode: 실제 로직 (향후 구현)
|
||||
console.log('[CartSidebar] API Mode - Checkout (to be implemented)');
|
||||
}
|
||||
}, [dispatch, isMockMode]);
|
||||
}, [dispatch, isMockMode, cartInfo]);
|
||||
|
||||
const { itemCount, subtotal, optionTotal, shippingHandling, orderTotalBeforeTax } = calculatedData;
|
||||
|
||||
|
||||
@@ -115,20 +115,28 @@ export default function CheckOutPanel({ panelInfo }) {
|
||||
? (() => {
|
||||
console.log('[BuyOption][CheckOutPanel] Mock Mode - panelInfo:', panelInfo);
|
||||
console.log('[BuyOption][CheckOutPanel] Mock Mode - panelInfo.productInfo:', panelInfo?.productInfo);
|
||||
console.log('[BuyOption][CheckOutPanel] Mock Mode - panelInfo.cartItems:', panelInfo?.cartItems);
|
||||
console.log('[BuyOption][CheckOutPanel] Mock Mode - panelInfo.isFromCart:', panelInfo?.isFromCart);
|
||||
|
||||
// 1순위: BuyOption에서 전달된 productInfo (Mock Mode에서는 검증 최소화)
|
||||
// ✅ 1순위: CartPanel에서 전달된 cartItems (전체 카트 데이터)
|
||||
if (panelInfo?.isFromCart && panelInfo?.cartItems && Array.isArray(panelInfo.cartItems) && panelInfo.cartItems.length > 0) {
|
||||
console.log('%c[BuyOption][CheckOutPanel] ✅ Mock Mode - Using cartItems from CartPanel (multi-product):', 'background: blue; color: white; font-weight: bold; padding: 5px;', panelInfo.cartItems);
|
||||
return panelInfo.cartItems;
|
||||
}
|
||||
|
||||
// 2순위: BuyOption에서 전달된 productInfo (단일 상품)
|
||||
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);
|
||||
console.log('%c[BuyOption][CheckOutPanel] ✅ Mock Mode - Using panelInfo.productInfo (single product):', 'background: green; color: white; font-weight: bold; padding: 5px;', panelInfo.productInfo);
|
||||
return [panelInfo.productInfo];
|
||||
}
|
||||
|
||||
// 2순위: Redux에서 가져온 상품 데이터
|
||||
// 3순위: Redux에서 가져온 상품 데이터
|
||||
if (reduxProductData && Array.isArray(reduxProductData) && reduxProductData.length > 0) {
|
||||
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 데이터 (최후의 fallback)
|
||||
// 4순위: 기본 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 [
|
||||
{
|
||||
|
||||
@@ -25,47 +25,88 @@ export default function SummaryContainerMock({
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - productInfo.price2:', productInfo?.price2);
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - productInfo.price5:', productInfo?.price5);
|
||||
|
||||
// Mock Mode: productInfo로부터 ORDER SUMMARY용 가격 데이터 계산
|
||||
// ✅ Mock Mode: productData 배열 전체를 합산하거나 productInfo 사용
|
||||
const orderSummaryData = useMemo(() => {
|
||||
if (productInfo) {
|
||||
// productInfo가 있으면 직접 계산
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - Using calculateOrderSummaryFromProductInfo');
|
||||
return calculateOrderSummaryFromProductInfo(productInfo);
|
||||
} else {
|
||||
// productInfo가 없으면 기존 방식으로 fallback
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - Using fallback calculation (no productInfo)');
|
||||
const selectedProduct = getSafeFirstProduct(productData);
|
||||
// ✅ 1순위: CartPanel의 여러 상품 데이터 (productData 배열)
|
||||
if (Array.isArray(productData) && productData.length > 0) {
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - Using calculateOrderSummaryFromProductData (multiple items)');
|
||||
|
||||
// 최소한 하나의 가격이라도 찾기
|
||||
const anyPrice = parseFloat(
|
||||
defaultPrice ||
|
||||
selectedProduct?.finalPrice ||
|
||||
selectedProduct?.discountPrice ||
|
||||
selectedProduct?.price ||
|
||||
0
|
||||
);
|
||||
// 전체 상품의 가격 합산
|
||||
let totalItems = 0;
|
||||
let totalCoupon = 0;
|
||||
let totalShipping = 0;
|
||||
|
||||
const productPrice = anyPrice > 0 ? anyPrice : 0;
|
||||
const productDiscount = parseFloat(selectedProduct?.discount || 0);
|
||||
const tax = Math.round((productPrice * 0.1) * 100) / 100;
|
||||
productData.forEach((product) => {
|
||||
const price = parseFloat(product.price3 || product.price2 || product.finalPrice || product.discountPrice || 0);
|
||||
const qty = product.prodQty || 1;
|
||||
const coupon = parseFloat(product.discount || product.price5 || 0);
|
||||
const shipping = parseFloat(product.shippingCharge || 0);
|
||||
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - Fallback calculation:', {
|
||||
productPrice,
|
||||
productDiscount,
|
||||
tax,
|
||||
selectedProduct
|
||||
totalItems += price * qty;
|
||||
totalCoupon += coupon * qty;
|
||||
totalShipping += shipping;
|
||||
});
|
||||
|
||||
const subtotal = Math.max(0, totalItems - totalCoupon + totalShipping);
|
||||
const tax = Math.round((subtotal * 0.1) * 100) / 100;
|
||||
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - Multi-product calculation:', {
|
||||
totalItems,
|
||||
totalCoupon,
|
||||
totalShipping,
|
||||
subtotal,
|
||||
tax
|
||||
});
|
||||
|
||||
return {
|
||||
items: productPrice,
|
||||
couponSavings: productDiscount,
|
||||
shipping: 0,
|
||||
subtotal: Math.max(0, productPrice - productDiscount),
|
||||
items: totalItems,
|
||||
couponSavings: totalCoupon,
|
||||
shipping: totalShipping,
|
||||
subtotal: subtotal,
|
||||
tax: tax,
|
||||
total: Math.max(0, productPrice - productDiscount) + tax,
|
||||
total: subtotal + tax,
|
||||
currency: { currSign, currSignLoc }
|
||||
};
|
||||
}
|
||||
|
||||
// 2순위: productInfo (단일 상품)
|
||||
if (productInfo) {
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - Using calculateOrderSummaryFromProductInfo (single product)');
|
||||
return calculateOrderSummaryFromProductInfo(productInfo);
|
||||
}
|
||||
|
||||
// 3순위: 기존 방식으로 fallback
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - Using fallback calculation (no data)');
|
||||
const selectedProduct = getSafeFirstProduct(productData);
|
||||
|
||||
const anyPrice = parseFloat(
|
||||
defaultPrice ||
|
||||
selectedProduct?.finalPrice ||
|
||||
selectedProduct?.discountPrice ||
|
||||
selectedProduct?.price ||
|
||||
0
|
||||
);
|
||||
|
||||
const productPrice = anyPrice > 0 ? anyPrice : 0;
|
||||
const productDiscount = parseFloat(selectedProduct?.discount || 0);
|
||||
const tax = Math.round((productPrice * 0.1) * 100) / 100;
|
||||
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - Fallback calculation:', {
|
||||
productPrice,
|
||||
productDiscount,
|
||||
tax,
|
||||
selectedProduct
|
||||
});
|
||||
|
||||
return {
|
||||
items: productPrice,
|
||||
couponSavings: productDiscount,
|
||||
shipping: 0,
|
||||
subtotal: Math.max(0, productPrice - productDiscount),
|
||||
tax: tax,
|
||||
total: Math.max(0, productPrice - productDiscount) + tax,
|
||||
currency: { currSign, currSignLoc }
|
||||
};
|
||||
}, [productInfo, productData, defaultPrice, currSign, currSignLoc]);
|
||||
|
||||
console.log('[BuyOption][CheckOutPanel] SummaryContainerMock - orderSummaryData:', orderSummaryData);
|
||||
|
||||
@@ -823,9 +823,51 @@ const BuyOption = ({
|
||||
productInfo?.patncLogPath ||
|
||||
'/assets/images/img-thumb-empty-144@3x.png';
|
||||
|
||||
// 가격 정보 구성
|
||||
const regularPrice = productInfo?.regularPrice || 299.99;
|
||||
const discountPrice = productInfo?.discountPrice || regularPrice;
|
||||
// ✅ 정확한 가격 추출 (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 numMatch = value.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 = {
|
||||
// 기본 정보
|
||||
@@ -843,9 +885,15 @@ const BuyOption = ({
|
||||
imgUrls: productInfo?.imgUrls || [{ imgUrl: imgUrl }], // imgUrls 배열 구조 추가 (CheckOutPanel 호환성)
|
||||
imgUrls600: productInfo?.imgUrls600, // ✅ 고품질 이미지 배열 포함
|
||||
|
||||
// 가격 정보 (문자열 형식으로)
|
||||
price2: regularPrice.toFixed(2), // 원가
|
||||
price3: discountPrice.toFixed(2), // 할인가/판매가
|
||||
// 가격 정보 (정확한 가격 정보 - 문자열 형식으로)
|
||||
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,
|
||||
@@ -865,6 +913,11 @@ const BuyOption = ({
|
||||
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,
|
||||
@@ -887,9 +940,51 @@ const BuyOption = ({
|
||||
productInfo?.patncLogPath ||
|
||||
'/assets/images/img-thumb-empty-144@3x.png';
|
||||
|
||||
// 가격 정보 구성 (CheckOutPanel과 동일한 방식)
|
||||
const regularPrice = productInfo?.regularPrice || 299.99;
|
||||
const discountPrice = productInfo?.discountPrice || regularPrice;
|
||||
// ✅ 정확한 가격 추출 (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 numMatch = value.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 = {
|
||||
@@ -908,9 +1003,15 @@ const BuyOption = ({
|
||||
imgUrls: productInfo?.imgUrls || [{ imgUrl: imgUrl }], // imgUrls 배열 구조 추가 (CheckOutPanel 호환성)
|
||||
imgUrls600: productInfo?.imgUrls600, // ✅ 고품질 이미지 배열 포함
|
||||
|
||||
// 가격 정보 (문자열 형식으로)
|
||||
price2: regularPrice.toFixed(2), // 원가
|
||||
price3: discountPrice.toFixed(2), // 할인가/판매가
|
||||
// 가격 정보 (정확한 가격 정보 - 문자열 형식으로)
|
||||
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,
|
||||
@@ -933,6 +1034,11 @@ const BuyOption = ({
|
||||
};
|
||||
|
||||
// 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,
|
||||
|
||||
@@ -201,8 +201,13 @@ export default function MainView({ className, initService }) {
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
// 단독 패널 체크 - CheckOutPanel, CartPanel 등 단독으로 렌더링되어야 하는 패널들
|
||||
console.log(`[MainView] 🔍 Top panel name: ${topPanel?.name}`);
|
||||
console.log(`[MainView] 🔍 isStandalonePanel check:`, isStandalonePanel(topPanel?.name));
|
||||
console.log(`[MainView] 🔍 STANDALONE_PANELS:`, STANDALONE_PANELS);
|
||||
console.log(`[MainView] 🔍 All panels:`, panels.map(p => ({ name: p.name, hasModal: !!p.panelInfo?.modal })));
|
||||
|
||||
if (isStandalonePanel(topPanel?.name)) {
|
||||
console.log(`[MainView] Standalone panel detected: ${topPanel?.name} - rendering independently`);
|
||||
console.log(`[MainView] ✅ Standalone panel detected: ${topPanel?.name} - rendering independently`);
|
||||
renderingPanels = [topPanel]; // 단독 패널만 단독으로 렌더링
|
||||
}
|
||||
// 기존 3-layer 구조 체크: PlayerPanel + DetailPanel + MediaPanel(modal)
|
||||
|
||||
Reference in New Issue
Block a user