403 lines
14 KiB
JavaScript
403 lines
14 KiB
JavaScript
import React, {
|
||
memo,
|
||
useCallback,
|
||
useMemo,
|
||
} from 'react';
|
||
|
||
import {
|
||
useDispatch,
|
||
useSelector,
|
||
} from 'react-redux';
|
||
|
||
import { getMyInfoCheckoutInfo } from '../../actions/checkoutActions';
|
||
import {
|
||
changeAppStatus,
|
||
setShowPopup,
|
||
showError,
|
||
} from '../../actions/commonActions';
|
||
import {
|
||
popPanel,
|
||
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);
|
||
// ✅ Redux에서 체크된 상품 정보 가져오기
|
||
const checkedItems = useSelector((state) => state.cart.selectCart.checkedItems || []);
|
||
|
||
const { userNumber } = useSelector(
|
||
(state) => state.common.appStatus.loginUserData
|
||
);
|
||
|
||
// 사용할 장바구니 데이터 결정
|
||
const displayCartInfo = cartInfo || (isMockMode ? null : fallbackCartInfo);
|
||
|
||
// ✅ 계산할 상품 결정 (체크된 상품이 있으면 체크된 상품, 없으면 전체 상품)
|
||
const itemsToCalculate = useMemo(() => {
|
||
if (checkedItems && checkedItems.length > 0) {
|
||
// 체크된 상품이 있으면 체크된 상품만 사용
|
||
console.log('[CartSidebar] Using checked items for calculation:', checkedItems.length);
|
||
return checkedItems;
|
||
} else if (displayCartInfo && Array.isArray(displayCartInfo)) {
|
||
// 체크된 상품이 없으면 전체 장바구니 상품 사용
|
||
console.log('[CartSidebar] No checked items, using all cart items:', displayCartInfo.length);
|
||
return displayCartInfo;
|
||
}
|
||
return [];
|
||
}, [checkedItems, displayCartInfo]);
|
||
|
||
// checkOutValidate 콜백 함수 (SingleOption과 동일한 로직)
|
||
function checkOutValidate(response) {
|
||
console.log('%c[BuyOption] 🔴 checkOutValidate CALLED', 'background: red; color: white; font-weight: bold; padding: 5px;', {
|
||
hasResponse: !!response,
|
||
retCode: response?.retCode,
|
||
retMsg: response?.retMsg,
|
||
hasCardInfo: !!response?.data?.cardInfo,
|
||
hasBillingAddress: response?.data?.billingAddressList?.length > 0,
|
||
hasShippingAddress: response?.data?.shippingAddressList?.length > 0,
|
||
hasProductList: response?.data?.productList?.length > 0,
|
||
});
|
||
|
||
if (response) {
|
||
if (response.retCode === 0) {
|
||
// 🔍 조건 체크
|
||
const isCardInfoNull = response.data.cardInfo === null;
|
||
const isBillingAddressEmpty = response.data.billingAddressList.length === 0;
|
||
const isShippingAddressEmpty = response.data.shippingAddressList.length === 0;
|
||
|
||
console.log('%c[BuyOption] 🔍 Address & Card Validation:', 'background: blue; color: white; font-weight: bold; padding: 5px;', {
|
||
isCardInfoNull,
|
||
isBillingAddressEmpty,
|
||
isShippingAddressEmpty,
|
||
needsQRPopup: isCardInfoNull || isBillingAddressEmpty || isShippingAddressEmpty,
|
||
});
|
||
|
||
if (
|
||
isCardInfoNull ||
|
||
isBillingAddressEmpty ||
|
||
isShippingAddressEmpty
|
||
) {
|
||
console.log('%c[BuyOption] 🟡 Missing card/address - Showing QR Popup', 'background: orange; color: white; font-weight: bold; padding: 5px;');
|
||
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
|
||
dispatch(changeAppStatus({ isLoading: false }));
|
||
return;
|
||
} else {
|
||
console.log('%c[BuyOption] ✅ All address & card data present - Proceeding to CheckOutPanel', 'background: green; color: white; font-weight: bold; padding: 5px;');
|
||
const { mbrId, prdtId, prodSno } = response.data.productList[0];
|
||
const cartTpSno = `${mbrId}_${prdtId}_${prodSno}`;
|
||
|
||
|
||
// 🔴 CRITICAL: 기존 CheckOutPanel 있으면 제거 후 새로 push (API Mode)
|
||
dispatch((dispatchFn, getState) => {
|
||
const panels = getState().panels?.panels || [];
|
||
const checkoutPanelExists = panels.some(p => p.name === Config.panel_names.CHECKOUT_PANEL);
|
||
|
||
console.log('[BuyOption] 📊 API Mode - Current panels:', panels.map(p => p.name));
|
||
|
||
// 1️⃣ DetailPanel 제거 (STANDALONE_PANEL이므로 다른 패널 제거 필수)
|
||
console.log('[BuyOption] 🗑️ API Mode - Removing DetailPanel');
|
||
dispatchFn(popPanel(Config.panel_names.DETAIL_PANEL));
|
||
|
||
// 2️⃣ 기존 CheckOutPanel 제거 (있으면)
|
||
if (checkoutPanelExists) {
|
||
console.log('[BuyOption] 🗑️ API Mode - Removing existing CheckOutPanel');
|
||
dispatchFn(popPanel(Config.panel_names.CHECKOUT_PANEL));
|
||
}
|
||
|
||
// 3️⃣ 새로운 CheckOutPanel push
|
||
console.log('[BuyOption] ➕ API Mode - Pushing new CheckOutPanel');
|
||
dispatchFn(pushPanel({
|
||
name: Config.panel_names.CHECKOUT_PANEL,
|
||
// panelInfo: { logInfo: { ...logInfo, cartTpSno } },
|
||
}));
|
||
|
||
});
|
||
|
||
}
|
||
} else if (response.retCode === 1001) {
|
||
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
|
||
dispatch(changeAppStatus({ isLoading: false }));
|
||
} else {
|
||
dispatch(
|
||
showError(
|
||
response.retCode,
|
||
response.retMsg,
|
||
false,
|
||
response.retDetailCode,
|
||
response.returnBindStrings
|
||
)
|
||
);
|
||
dispatch(changeAppStatus({ isLoading: false }));
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ✅ Mock 데이터 또는 실제 데이터 계산 - 최적화 버전
|
||
const calculatedData = useMemo(() => {
|
||
const DEBUG_LOG = false;
|
||
|
||
if (itemsToCalculate && itemsToCalculate.length > 0) {
|
||
let totalItems = 0;
|
||
let totalOption = 0;
|
||
let totalShipping = 0;
|
||
let totalQuantity = 0;
|
||
|
||
itemsToCalculate.forEach((item) => {
|
||
// API 모드: 실제 가격 필드 사용
|
||
const productPrice = parseFloat(Number(item.price3) !== 0 ? Number(item.price3) : Number(item.price2) !== 0 ? Number(item.price2) : 0);
|
||
const optionPrice = parseFloat(Number(item.price3) !== Number(item.optPrc) && (Number(item.price3) !== Number(item.optPrc)) ? Number(item.optPrc) : 0 || 0);
|
||
const shippingPrice = parseFloat(Number(item.shippingCharge) || 0);
|
||
|
||
const qty = item.prodQty || item.qty || 1;
|
||
totalItems += productPrice * qty;
|
||
totalOption += optionPrice * qty;
|
||
totalShipping += shippingPrice;
|
||
totalQuantity += qty;
|
||
|
||
if (DEBUG_LOG) {
|
||
console.log('[CartSidebar] Item calculation:', {
|
||
name: item.prdtNm,
|
||
qty,
|
||
itemPrice: productPrice,
|
||
itemOption: optionPrice,
|
||
itemShipping: shippingPrice,
|
||
runningTotal: totalItems
|
||
});
|
||
}
|
||
});
|
||
|
||
const subtotal = Math.max(0, totalItems + totalOption + totalShipping);
|
||
|
||
if (DEBUG_LOG) {
|
||
console.log('[CartSidebar] Final calculation:', {
|
||
isCheckedMode: checkedItems && checkedItems.length > 0,
|
||
itemCount: itemsToCalculate.length,
|
||
totalQuantity,
|
||
totalItems,
|
||
totalOption,
|
||
totalShipping,
|
||
subtotal,
|
||
});
|
||
}
|
||
|
||
return {
|
||
itemCount: totalQuantity,
|
||
subtotal: totalItems,
|
||
optionTotal: totalOption,
|
||
shippingHandling: totalShipping,
|
||
orderTotalBeforeTax: subtotal,
|
||
};
|
||
} else {
|
||
// 상품이 없는 경우
|
||
return {
|
||
itemCount: 0,
|
||
subtotal: 0,
|
||
optionTotal: 0,
|
||
shippingHandling: 0,
|
||
orderTotalBeforeTax: 0,
|
||
};
|
||
}
|
||
}, [itemsToCalculate, checkedItems]);
|
||
|
||
// 체크아웃 버튼 클릭 핸들러
|
||
const handleCheckoutClick = useCallback(() => {
|
||
const DEBUG_LOG = true; // 수동 설정: true로 활성화 (디버깅용)
|
||
|
||
console.log('[CartSidebar] 🎯 Checkout button clicked! Starting checkout process...');
|
||
console.log('[CartSidebar] 📊 Current state:', {
|
||
isMockMode,
|
||
checkedItemsCount: checkedItems.length,
|
||
itemsToCalculateCount: itemsToCalculate.length
|
||
});
|
||
|
||
// ✅ 계산할 상품이 없는 경우
|
||
if (itemsToCalculate.length === 0) {
|
||
console.log('[CartSidebar] ❌ No items to checkout');
|
||
return;
|
||
}
|
||
|
||
if (DEBUG_LOG) {
|
||
console.log('[CartSidebar] Items to checkout:', JSON.stringify(itemsToCalculate));
|
||
}
|
||
|
||
// ✅ CheckOutPanel 전송용 데이터 구성
|
||
const { itemCount, subtotal, optionTotal, shippingHandling, orderTotalBeforeTax } = calculatedData;
|
||
|
||
const checkoutData = {
|
||
// 하위 호환성: 첫 번째 상품을 productInfo로
|
||
productInfo: itemsToCalculate[0],
|
||
|
||
// ✅ CheckOutPanel이 기대하는 cartItems 필드
|
||
cartItems: itemsToCalculate.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,
|
||
prodSno: item.prodSno,
|
||
})),
|
||
|
||
// 메타데이터
|
||
isFromCart: true,
|
||
fromCartPanel: true,
|
||
isCheckedMode: checkedItems && checkedItems.length > 0, // ✅ 체크 모드 여부
|
||
cartTotal: orderTotalBeforeTax,
|
||
itemCount: itemCount,
|
||
subtotal: subtotal,
|
||
optionTotal: optionTotal,
|
||
shippingHandling: shippingHandling
|
||
|
||
|
||
};
|
||
|
||
if (DEBUG_LOG) {
|
||
console.log('%c🚨🚨🚨 CART SIDEBAR CHECKOUT DATA 🚨🚨🚨', 'background: yellow; color: black; font-weight: bold; font-size: 14px; padding: 5px;');
|
||
console.log('%cMode:', 'background: yellow; color: black; padding: 3px;', checkedItems.length > 0 ? 'CHECKED ITEMS MODE' : 'ALL ITEMS MODE');
|
||
console.log('%cItems to checkout:', 'background: yellow; color: black; padding: 3px;', itemsToCalculate);
|
||
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('%cCalculated totals:', 'background: yellow; color: black; padding: 3px;', {
|
||
itemCount,
|
||
subtotal,
|
||
optionTotal,
|
||
shippingHandling,
|
||
orderTotalBeforeTax
|
||
});
|
||
}
|
||
|
||
// ✅ CheckOutPanel로 이동
|
||
console.log('[CartSidebar] 🚀 Executing pushPanel to CHECKOUT_PANEL');
|
||
|
||
try {
|
||
dispatch(
|
||
getMyInfoCheckoutInfo(
|
||
{
|
||
mbrNo: userNumber,
|
||
dirPurcSelYn: 'Y',
|
||
cartList:
|
||
checkedItems && checkedItems.map((item) => ({
|
||
patnrId: item.patnrId,
|
||
prdtId: item.prdtId,
|
||
prodOptCdCval: item.prodOptCdCval ? item.prodOptCdCval : null,
|
||
prodQty: String(item.prodQty),
|
||
prodOptTpCdCval: item.prodOptTpCdCval,
|
||
}))
|
||
|
||
},
|
||
checkOutValidate
|
||
)
|
||
);
|
||
|
||
// dispatch(pushPanel({
|
||
// name: Config.panel_names.CHECKOUT_PANEL,
|
||
// panelInfo: checkoutData
|
||
// }));
|
||
console.log('[CartSidebar] ✅ pushPanel executed successfully');
|
||
} catch (error) {
|
||
console.error('[CartSidebar] ❌ pushPanel failed:', error);
|
||
}
|
||
|
||
}, [dispatch, checkedItems, itemsToCalculate, calculatedData]);
|
||
|
||
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}>{userNumber ? itemCount : 0} 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}>{userNumber ? formatPrice(subtotal) : 0}</span>
|
||
</div>
|
||
<div className={css.priceItem}>
|
||
<span className={css.label}>Option</span>
|
||
<span className={css.value}>
|
||
{userNumber ? formatPrice(optionTotal) : 0}
|
||
</span>
|
||
</div>
|
||
<div className={css.priceItem}>
|
||
<span className={css.label}>S&H</span>
|
||
<span className={css.value}>
|
||
{userNumber ? formatPrice(shippingHandling) : 0}
|
||
</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}>
|
||
{userNumber ? formatPrice(orderTotalBeforeTax) : 0}
|
||
</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}
|
||
disabled={checkedItems.length === 0 || (itemsToCalculate.length === 0 || !userNumber)}
|
||
>
|
||
Checkout
|
||
</TButton>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default CartSidebar; |