441 lines
16 KiB
JavaScript
441 lines
16 KiB
JavaScript
import React, {
|
|
memo,
|
|
useCallback,
|
|
useMemo,
|
|
} from 'react';
|
|
|
|
import {
|
|
useDispatch,
|
|
useSelector,
|
|
} from 'react-redux';
|
|
|
|
import { pushPanel } from '../../actions/panelActions';
|
|
import TButton from '../../components/TButton/TButton';
|
|
import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig';
|
|
import * as Config from '../../utils/Config';
|
|
import {
|
|
calculateOrderSummaryFromProductInfo,
|
|
} from '../../utils/mockDataSafetyUtils';
|
|
import css from './CartSidebar.module.less';
|
|
|
|
const CartSidebar = ({ cartInfo }) => {
|
|
const dispatch = useDispatch();
|
|
|
|
// Mock Mode 확인
|
|
const isMockMode = BUYNOW_CONFIG.isMockMode();
|
|
|
|
// 실제 장바구니 데이터 (API 모드일 때만 사용)
|
|
const fallbackCartInfo = useSelector((state) => state.cart.getMyinfoCartSearch.cartInfo);
|
|
const selectedItems = useSelector((state) => state.mockCart.selectedItems || []);
|
|
|
|
// 사용할 장바구니 데이터 결정
|
|
const displayCartInfo = cartInfo || (isMockMode ? null : fallbackCartInfo);
|
|
|
|
// 선택된 상품들만 필터링 - 항상 선택된 상품들만 반환
|
|
const getSelectedItems = useCallback((items) => {
|
|
const DEBUG_LOG = false; // 수동 설정: false로 비활성화
|
|
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] getSelectedItems called - isMockMode:', isMockMode, 'selectedItems:', selectedItems);
|
|
}
|
|
|
|
if (!items || !Array.isArray(items)) {
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] No items provided, returning empty array');
|
|
}
|
|
return [];
|
|
}
|
|
|
|
if (!isMockMode) {
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] API Mode - returning all items');
|
|
}
|
|
return items;
|
|
}
|
|
|
|
if (selectedItems.length === 0) {
|
|
if (DEBUG_LOG) {
|
|
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);
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] Item filter:', {
|
|
itemName: item.prdtNm,
|
|
itemId,
|
|
isSelected
|
|
});
|
|
}
|
|
return isSelected;
|
|
});
|
|
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] Filtered selected items:', filtered.length, 'out of', items.length);
|
|
}
|
|
return filtered;
|
|
}, [isMockMode, selectedItems]);
|
|
|
|
// 개별 상품 가격 캐싱 (성능 최적화)
|
|
const itemPriceCache = useMemo(() => {
|
|
const cache = new Map();
|
|
|
|
if (isMockMode && displayCartInfo) {
|
|
displayCartInfo.forEach(item => {
|
|
if (!cache.has(item.prodSno || item.cartId)) {
|
|
|
|
const orderSummary = calculateOrderSummaryFromProductInfo(item);
|
|
cache.set(item.prodSno || item.cartId, {
|
|
price: orderSummary.items,
|
|
coupon: 0,
|
|
shipping: orderSummary.shipping
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
return cache;
|
|
}, [isMockMode, displayCartInfo]);
|
|
|
|
// Mock 데이터 또는 실제 데이터 계산 (선택된 상품만) - 최적화 버전
|
|
const calculatedData = useMemo(() => {
|
|
const DEBUG_LOG = false; // 수동 설정: false로 비활성화
|
|
|
|
if (isMockMode) {
|
|
// Mock Mode: 선택된 상품들로 개별 가격 계산 (캐시 사용)
|
|
if (displayCartInfo && Array.isArray(displayCartInfo) && displayCartInfo.length > 0) {
|
|
const selectedCartItems = getSelectedItems(displayCartInfo);
|
|
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] Selected items for calculation:', selectedCartItems);
|
|
}
|
|
|
|
// 캐시된 가격 정보 사용
|
|
let totalItems = 0;
|
|
let totalCoupon = 0;
|
|
let totalShipping = 0;
|
|
let totalQuantity = 0;
|
|
|
|
selectedCartItems.forEach((item) => {
|
|
const itemId = item.prodSno || item.cartId;
|
|
const cachedPrice = itemPriceCache.get(itemId);
|
|
|
|
if (cachedPrice) {
|
|
const qty = item.prodQty || item.qty || 1;
|
|
totalItems += cachedPrice.price * qty;
|
|
totalCoupon += cachedPrice.coupon * qty;
|
|
totalShipping += cachedPrice.shipping * qty;
|
|
totalQuantity += qty;
|
|
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] Item calculation (cached):', {
|
|
name: item.prdtNm,
|
|
qty,
|
|
itemPrice: cachedPrice.price,
|
|
itemCoupon: cachedPrice.coupon,
|
|
itemShipping: cachedPrice.shipping,
|
|
runningTotal: totalItems
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
const subtotal = Math.max(0, totalItems - totalCoupon + totalShipping);
|
|
if (DEBUG_LOG) {
|
|
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);
|
|
const subtotal = displayCartInfo.reduce((sum, item) => {
|
|
const price = parseFloat(Number(item.price3) !== 0 ? Number(item.price3) : Number(item.price2) !== 0 ? Number(item.price2) : 0);
|
|
return sum + (price * (item.prodQty || 1));
|
|
}, 0);
|
|
const optionTotal = displayCartInfo.reduce((sum, item) => {
|
|
const optionPrice = parseFloat(Number(item.price5) || Number(item.optPrc) || 0);
|
|
return sum + (optionPrice * (item.prodQty || 1));
|
|
}, 0);
|
|
const shippingHandling = displayCartInfo.reduce((sum, item) => {
|
|
return sum + parseFloat(Number(item.shippingCharge) * (item.prodQty || 1))
|
|
}, 0);
|
|
|
|
return {
|
|
itemCount,
|
|
subtotal,
|
|
optionTotal,
|
|
shippingHandling,
|
|
orderTotalBeforeTax: subtotal + optionTotal + shippingHandling,
|
|
};
|
|
} else {
|
|
// 데이터가 없는 경우
|
|
return {
|
|
itemCount: 0,
|
|
subtotal: 0,
|
|
optionTotal: 0,
|
|
shippingHandling: 0,
|
|
orderTotalBeforeTax: 0,
|
|
};
|
|
}
|
|
}, [isMockMode, displayCartInfo, getSelectedItems, itemPriceCache]);
|
|
|
|
// 체크아웃 버튼 클릭 핸들러 - 완전한 데이터 전송
|
|
const handleCheckoutClick = useCallback(() => {
|
|
const DEBUG_LOG = true; // 수동 설정: true로 활성화 (디버깅용)
|
|
|
|
console.log('[CartSidebar] 🎯 Checkout button clicked! Starting checkout process...');
|
|
console.log('[CartSidebar] 📊 Current state:', {
|
|
isMockMode,
|
|
totalItems: cartInfo?.length || 0,
|
|
selectedItemsCount: selectedItems.length
|
|
});
|
|
|
|
if (isMockMode) {
|
|
// Mock Mode: 선택된 상품들로 CheckOutPanel로 이동
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] Mock Mode - Going to Checkout');
|
|
console.log('[CartSidebar] Mock Mode - cartInfo:', JSON.stringify(cartInfo));
|
|
}
|
|
|
|
// ✅ 선택된 상품들만 필터링 (체크박스 선택된 상품)
|
|
const allCartItems = cartInfo && Array.isArray(cartInfo) ? cartInfo : [];
|
|
const selectedCartItems = getSelectedItems(allCartItems);
|
|
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] Mock Mode - All cart items:', allCartItems.length);
|
|
console.log('[CartSidebar] Mock Mode - Selected cart items:', selectedCartItems.length);
|
|
console.log('[CartSidebar] Mock Mode - Selected items:', JSON.stringify(selectedCartItems));
|
|
}
|
|
|
|
// 선택된 상품이 없는 경우
|
|
if (selectedCartItems.length === 0) {
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] Mock Mode - No items selected, cannot proceed to checkout');
|
|
}
|
|
return; // 아무것도 하지 않음
|
|
}
|
|
|
|
// ✅ CheckOutPanel 전송용 데이터 구성 (CheckOutPanel 기대 구조에 맞춤)
|
|
const checkoutData = {
|
|
// 하위 호환성: 첫 번째 상품을 productInfo로
|
|
productInfo: selectedCartItems[0],
|
|
|
|
// ✅ CheckOutPanel이 기대하는 cartItems 필드 (isFromCart=true일 때 사용)
|
|
cartItems: selectedCartItems.map(item => ({
|
|
prdtId: item.prdtId,
|
|
prdtNm: item.prdtNm,
|
|
patnrId: item.patnrId,
|
|
patncNm: item.patncNm,
|
|
price2: item.price2, // 원가
|
|
price3: item.price3, // 할인가
|
|
price5: item.price5, // 쿠폰
|
|
prodQty: item.prodQty || item.qty || 1,
|
|
optNm: item.optNm,
|
|
shippingCharge: item.shippingCharge || '0',
|
|
// 추가 필드들
|
|
finalPrice: item.finalPrice,
|
|
discountPrice: item.discountPrice,
|
|
origPrice: item.origPrice,
|
|
priceInfo: item.priceInfo,
|
|
// 이미지 정보
|
|
imgUrl: item.imgUrl,
|
|
thumbnailUrl: item.thumbnailUrl,
|
|
imgUrls600: item.imgUrls600
|
|
})),
|
|
|
|
// 메타데이터
|
|
isFromCart: true,
|
|
fromCartPanel: true, // 추가 확인용
|
|
cartTotal: orderTotalBeforeTax,
|
|
itemCount: itemCount,
|
|
subtotal: subtotal,
|
|
optionTotal: optionTotal,
|
|
shippingHandling: shippingHandling
|
|
};
|
|
|
|
if (DEBUG_LOG) {
|
|
console.log('%c🚨🚨🚨 CART SIDEBAR CHECKOUT DATA ANALYSIS 🚨🚨🚨', 'background: yellow; color: black; font-weight: bold; font-size: 14px; padding: 5px;');
|
|
console.log('%cOriginal cartInfo:', 'background: yellow; color: black; padding: 3px;', cartInfo);
|
|
console.log('%c🔍 ORIGINAL CART ITEM 1 PRICE FIELDS:', 'background: magenta; color: white; padding: 3px;');
|
|
console.log('price2:', cartInfo?.[0]?.price2);
|
|
console.log('price3:', cartInfo?.[0]?.price3);
|
|
console.log('price5:', cartInfo?.[0]?.price5);
|
|
console.log('optPrc:', cartInfo?.[0]?.optPrc);
|
|
console.log('finalPrice:', cartInfo?.[0]?.finalPrice);
|
|
console.log('discountPrice:', cartInfo?.[0]?.discountPrice);
|
|
console.log('price:', cartInfo?.[0]?.price);
|
|
console.log('origPrice:', cartInfo?.[0]?.origPrice);
|
|
console.log('Full first item:', cartInfo?.[0]);
|
|
console.log('%cSelected items array:', 'background: yellow; color: black; padding: 3px;', selectedItems);
|
|
console.log('%cSelected cart items (filtered):', 'background: yellow; color: black; padding: 3px;', selectedCartItems);
|
|
console.log('%cCheckout data prepared:', 'background: yellow; color: black; padding: 3px;', checkoutData);
|
|
console.log('%cCartItems count:', 'background: yellow; color: black; padding: 3px;', checkoutData.cartItems.length);
|
|
console.log('%cCartItems details:', 'background: yellow; color: black; padding: 3px;', checkoutData.cartItems);
|
|
console.log('%cisFromCart value:', 'background: yellow; color: black; padding: 3px;', checkoutData.isFromCart);
|
|
console.log('%cAbout to pushPanel to CHECKOUT_PANEL', 'background: yellow; color: black; padding: 3px;');
|
|
}
|
|
|
|
// ✅ CheckOutPanel로 이동
|
|
console.log('[CartSidebar] 🚀 Executing pushPanel to CHECKOUT_PANEL');
|
|
console.log('[CartSidebar] 📦 Checkout data summary:', {
|
|
itemCount: checkoutData.cartItems?.length || 0,
|
|
cartTotal: checkoutData.cartTotal,
|
|
hasProductInfo: !!checkoutData.productInfo,
|
|
isFromCart: checkoutData.isFromCart,
|
|
panelName: Config.panel_names.CHECKOUT_PANEL
|
|
});
|
|
|
|
try {
|
|
dispatch(pushPanel({
|
|
name: Config.panel_names.CHECKOUT_PANEL,
|
|
panelInfo: checkoutData
|
|
}));
|
|
console.log('[CartSidebar] ✅ pushPanel executed successfully');
|
|
} catch (error) {
|
|
console.error('[CartSidebar] ❌ pushPanel failed:', error);
|
|
}
|
|
|
|
} else {
|
|
// API Mode: 실제 API 데이터로 처리
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] API Mode - Checkout (to be implemented)');
|
|
}
|
|
|
|
// API Mode에서도 동일한 로직 적용
|
|
const allCartItems = cartInfo && Array.isArray(cartInfo) ? cartInfo : [];
|
|
const selectedCartItems = getSelectedItems(allCartItems);
|
|
|
|
if (selectedCartItems.length === 0) {
|
|
if (DEBUG_LOG) {
|
|
console.log('[CartSidebar] API Mode - No items selected, cannot proceed to checkout');
|
|
}
|
|
return;
|
|
}
|
|
|
|
const checkoutData = {
|
|
productInfo: selectedCartItems[0],
|
|
cartItems: selectedCartItems.map(item => ({
|
|
prdtId: item.prdtId,
|
|
prdtNm: item.prdtNm,
|
|
patnrId: item.patnrId,
|
|
patncNm: item.patncNm,
|
|
price2: item.price2,
|
|
price3: item.price3,
|
|
price5: item.price5,
|
|
prodQty: item.prodQty || item.qty || 1,
|
|
optNm: item.optNm,
|
|
shippingCharge: item.shippingCharge || '0'
|
|
})),
|
|
isFromCart: true,
|
|
fromCartPanel: true, // 추가 확인용
|
|
cartTotal: orderTotalBeforeTax,
|
|
itemCount: itemCount
|
|
};
|
|
|
|
dispatch(pushPanel({
|
|
name: Config.panel_names.CHECKOUT_PANEL,
|
|
panelInfo: checkoutData
|
|
}));
|
|
}
|
|
}, [dispatch, isMockMode, cartInfo, getSelectedItems, orderTotalBeforeTax, itemCount, subtotal, optionTotal, shippingHandling]);
|
|
|
|
const { itemCount, subtotal, optionTotal, shippingHandling, orderTotalBeforeTax } = calculatedData;
|
|
|
|
const formatPrice = (price) => {
|
|
return `$${price.toLocaleString('en-US', {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
})}`;
|
|
};
|
|
|
|
return (
|
|
<div className={css.sidebar}>
|
|
<div className={css.summarySection}>
|
|
<div className={css.header}>
|
|
<div className={css.title}>Subtotal</div>
|
|
<span className={css.itemCount}>{itemCount} Items</span>
|
|
</div>
|
|
<div className={css.borderLine} />
|
|
<div className={css.priceList}>
|
|
<div className={css.priceItem}>
|
|
<span className={css.label}>Subtotal</span>
|
|
<span className={css.value}>{formatPrice(subtotal)}</span>
|
|
</div>
|
|
<div className={css.priceItem}>
|
|
<span className={css.label}>Option</span>
|
|
<span className={css.value}>
|
|
{formatPrice(optionTotal)}
|
|
</span>
|
|
</div>
|
|
<div className={css.priceItem}>
|
|
<span className={css.label}>S&H</span>
|
|
<span className={css.value}>
|
|
{formatPrice(shippingHandling)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={css.totalRow}>
|
|
<span className={css.totalLabel}>
|
|
Order Total <br />
|
|
<span className={css.totalLabelSub}>(Before Tax)</span>
|
|
</span>
|
|
<span className={css.totalValue}>
|
|
{formatPrice(orderTotalBeforeTax)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={css.notesSection}>
|
|
<p className={css.note}>
|
|
• Final costs will be available on your Order Review before you place
|
|
order
|
|
</p>
|
|
<p className={css.note}>
|
|
• By proceeding, you agree to ShopTime's
|
|
<a href="#none">General Terms of Use</a> and acknowledge the
|
|
<a href="#none">Privacy Statement</a>
|
|
</p>
|
|
</div>
|
|
|
|
<div className={css.buttonSection}>
|
|
<TButton
|
|
className={css.checkoutButton}
|
|
spotlightId="cart-checkout-button"
|
|
onClick={handleCheckoutClick}
|
|
>
|
|
Checkout
|
|
</TButton>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CartSidebar;
|