Files
shoptime/com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx
junghoon86.park 37574c0794 [장바구니]
- 하단 checkout버튼 조건 추가.
2025-12-04 13:25:49 +09:00

403 lines
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;