Files
shoptime/com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx
junghoon86.park b9bfd4b1b6 [장바구니] 금액 노출관련 수정
- 계산했을때 맞지않던 부분, 옵션가 노출관련 처리.
 - 사이드바 부분에서도 계산 맞지않던 부분에 대하여 수정
2025-11-11 10:54:33 +09:00

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;