[251102] fix: CartPanel mock-1
🕐 커밋 시간: 2025. 11. 02. 08:49:45 📊 변경 통계: • 총 파일: 12개 • 추가: +686줄 • 삭제: -88줄 📁 추가된 파일: + com.twin.app.shoptime/src/reducers/mockCartReducer.js 📝 수정된 파일: ~ com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx ~ com.twin.app.shoptime/src/store/store.js ~ com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js ~ com.twin.app.shoptime/src/utils/Config.js ~ com.twin.app.shoptime/src/utils/mockDataSafetyUtils.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/CartProductBar.jsx ~ com.twin.app.shoptime/src/views/CartPanel/CartSidebar.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:
@@ -22,6 +22,7 @@ export default function TReactPlayer({
|
||||
mediaEventsMap = handledMediaEventsMap,
|
||||
videoRef,
|
||||
url,
|
||||
dispatch,
|
||||
...rest
|
||||
}) {
|
||||
const playerRef = useRef(null);
|
||||
|
||||
272
com.twin.app.shoptime/src/reducers/mockCartReducer.js
Normal file
272
com.twin.app.shoptime/src/reducers/mockCartReducer.js
Normal file
@@ -0,0 +1,272 @@
|
||||
import { MOCK_CART_TYPES } from '../actions/mockCartActions';
|
||||
|
||||
// 브라우저 환경 확인
|
||||
const isBrowser = typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
|
||||
|
||||
/**
|
||||
* Mock Cart Reducer 초기 상태
|
||||
* 실제 cartReducer와 유사한 구조 유지
|
||||
*/
|
||||
const initialState = {
|
||||
// Mock 장바구니 목록
|
||||
cartInfo: [],
|
||||
// 마지막 실행된 액션 정보
|
||||
lastAction: null,
|
||||
// 에러 정보
|
||||
error: null,
|
||||
// 총 상품 수량
|
||||
totalQuantity: 0,
|
||||
// 총 가격
|
||||
totalPrice: 0,
|
||||
// 마지막 업데이트 시간
|
||||
lastUpdated: null,
|
||||
};
|
||||
|
||||
// localStorage 관련 유틸리티
|
||||
const MOCK_CART_STORAGE_KEY = 'mockCartData';
|
||||
|
||||
/**
|
||||
* Mock 장바구니 데이터를 localStorage에 저장
|
||||
*/
|
||||
const saveToLocalStorage = (state) => {
|
||||
if (!isBrowser) return;
|
||||
|
||||
try {
|
||||
const dataToSave = {
|
||||
cartInfo: state.cartInfo,
|
||||
totalQuantity: state.totalQuantity,
|
||||
totalPrice: state.totalPrice,
|
||||
lastUpdated: state.lastUpdated,
|
||||
};
|
||||
window.localStorage.setItem(MOCK_CART_STORAGE_KEY, JSON.stringify(dataToSave));
|
||||
} catch (error) {
|
||||
console.error('[MockCartReducer] localStorage 저장 실패:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* localStorage에서 Mock 장바구니 데이터 불러오기
|
||||
*/
|
||||
const loadFromLocalStorage = () => {
|
||||
if (!isBrowser) return initialState;
|
||||
|
||||
try {
|
||||
const savedData = window.localStorage.getItem(MOCK_CART_STORAGE_KEY);
|
||||
if (savedData) {
|
||||
const parsedData = JSON.parse(savedData);
|
||||
return {
|
||||
...initialState,
|
||||
cartInfo: parsedData.cartInfo || [],
|
||||
totalQuantity: parsedData.totalQuantity || 0,
|
||||
totalPrice: parsedData.totalPrice || 0,
|
||||
lastUpdated: parsedData.lastUpdated || Date.now(),
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MockCartReducer] localStorage 로드 실패:', error);
|
||||
}
|
||||
return initialState;
|
||||
};
|
||||
|
||||
/**
|
||||
* 장바구니 아이템 중복 확인
|
||||
* @param {Array} cartItems - 현재 장바구니 아이템 목록
|
||||
* @param {Object} newItem - 추가할 상품
|
||||
* @returns {Object} 중복 여부와 기존 아이템 인덱스
|
||||
*/
|
||||
const findDuplicateItem = (cartItems, newItem) => {
|
||||
const index = cartItems.findIndex(item =>
|
||||
item.prdtId === newItem.prdtId &&
|
||||
item.optNm === newItem.optNm
|
||||
);
|
||||
return {
|
||||
isDuplicate: index !== -1,
|
||||
index
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 총 수량 및 총 가격 계산
|
||||
* @param {Array} cartItems - 장바구니 아이템 목록
|
||||
* @returns {Object} 총 수량과 총 가격
|
||||
*/
|
||||
const calculateTotals = (cartItems) => {
|
||||
const totalQuantity = cartItems.reduce((sum, item) => sum + (item.prodQty || 1), 0);
|
||||
const totalPrice = cartItems.reduce((sum, item) => {
|
||||
const itemPrice = parseFloat(item.price3 || item.price2 || 0);
|
||||
const optionPrice = parseFloat(item.price5 || 0);
|
||||
const shippingPrice = parseFloat(item.shippingCharge || 0);
|
||||
return sum + ((itemPrice + optionPrice) * (item.prodQty || 1) + shippingPrice);
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
totalQuantity,
|
||||
totalPrice: parseFloat(totalPrice.toFixed(2))
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock Cart Reducer
|
||||
* Mock Mode에서 장바구니 데이터를 관리합니다.
|
||||
*/
|
||||
export const mockCartReducer = (state = loadFromLocalStorage(), action) => {
|
||||
switch (action.type) {
|
||||
case MOCK_CART_TYPES.INIT_MOCK_CART: {
|
||||
const { items = [] } = action.payload;
|
||||
const newState = {
|
||||
...state,
|
||||
cartInfo: items,
|
||||
lastAction: action.payload.lastAction,
|
||||
error: null,
|
||||
lastUpdated: Date.now(),
|
||||
...calculateTotals(items),
|
||||
};
|
||||
|
||||
// localStorage에 저장
|
||||
saveToLocalStorage(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
case MOCK_CART_TYPES.ADD_TO_MOCK_CART: {
|
||||
const { item } = action.payload;
|
||||
const currentItems = [...state.cartInfo];
|
||||
|
||||
// 중복 상품 확인
|
||||
const { isDuplicate, index } = findDuplicateItem(currentItems, item);
|
||||
|
||||
let updatedItems;
|
||||
if (isDuplicate) {
|
||||
// 중복 상품이면 수량 증가
|
||||
updatedItems = [...currentItems];
|
||||
updatedItems[index] = {
|
||||
...updatedItems[index],
|
||||
prodQty: (updatedItems[index].prodQty || 1) + (item.prodQty || 1)
|
||||
};
|
||||
} else {
|
||||
// 새 상품 추가
|
||||
updatedItems = [...currentItems, item];
|
||||
}
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
cartInfo: updatedItems,
|
||||
lastAction: action.payload.lastAction,
|
||||
error: null,
|
||||
lastUpdated: Date.now(),
|
||||
...calculateTotals(updatedItems),
|
||||
};
|
||||
|
||||
// localStorage에 저장
|
||||
saveToLocalStorage(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
case MOCK_CART_TYPES.REMOVE_FROM_MOCK_CART: {
|
||||
const { prodSno } = action.payload;
|
||||
const updatedItems = state.cartInfo.filter(item => item.prodSno !== prodSno);
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
cartInfo: updatedItems,
|
||||
lastAction: action.payload.lastAction,
|
||||
error: null,
|
||||
lastUpdated: Date.now(),
|
||||
...calculateTotals(updatedItems),
|
||||
};
|
||||
|
||||
// localStorage에 저장
|
||||
saveToLocalStorage(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
case MOCK_CART_TYPES.UPDATE_MOCK_CART_ITEM: {
|
||||
const { prodSno, quantity } = action.payload;
|
||||
const updatedItems = state.cartInfo.map(item => {
|
||||
if (item.prodSno === prodSno) {
|
||||
return {
|
||||
...item,
|
||||
prodQty: Math.max(1, quantity) // 최소 1개 보장
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
cartInfo: updatedItems,
|
||||
lastAction: action.payload.lastAction,
|
||||
error: null,
|
||||
lastUpdated: Date.now(),
|
||||
...calculateTotals(updatedItems),
|
||||
};
|
||||
|
||||
// localStorage에 저장
|
||||
saveToLocalStorage(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
case MOCK_CART_TYPES.SET_MOCK_CART_QUANTITY: {
|
||||
const { prodSno, quantity } = action.payload;
|
||||
|
||||
if (quantity <= 0) {
|
||||
// 수량이 0이면 상품 제거
|
||||
const updatedItems = state.cartInfo.filter(item => item.prodSno !== prodSno);
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
cartInfo: updatedItems,
|
||||
lastAction: action.payload.lastAction,
|
||||
error: null,
|
||||
lastUpdated: Date.now(),
|
||||
...calculateTotals(updatedItems),
|
||||
};
|
||||
|
||||
// localStorage에 저장
|
||||
saveToLocalStorage(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
const updatedItems = state.cartInfo.map(item => {
|
||||
if (item.prodSno === prodSno) {
|
||||
return {
|
||||
...item,
|
||||
prodQty: quantity
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
cartInfo: updatedItems,
|
||||
lastAction: action.payload.lastAction,
|
||||
error: null,
|
||||
lastUpdated: Date.now(),
|
||||
...calculateTotals(updatedItems),
|
||||
};
|
||||
|
||||
// localStorage에 저장
|
||||
saveToLocalStorage(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
case MOCK_CART_TYPES.CLEAR_MOCK_CART: {
|
||||
const newState = {
|
||||
...state,
|
||||
cartInfo: [],
|
||||
lastAction: action.payload.lastAction,
|
||||
error: null,
|
||||
lastUpdated: Date.now(),
|
||||
totalQuantity: 0,
|
||||
totalPrice: 0,
|
||||
};
|
||||
|
||||
// localStorage에 저장
|
||||
saveToLocalStorage(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
@@ -22,6 +22,7 @@ import { foryouReducer } from '../reducers/forYouReducer';
|
||||
import { homeReducer } from '../reducers/homeReducer';
|
||||
import { localSettingsReducer } from '../reducers/localSettingsReducer';
|
||||
import { mainReducer } from '../reducers/mainReducer';
|
||||
import { mockCartReducer } from '../reducers/mockCartReducer';
|
||||
import { myPageReducer } from '../reducers/myPageReducer';
|
||||
import { onSaleReducer } from '../reducers/onSaleReducer';
|
||||
import { orderReducer } from '../reducers/orderReducer';
|
||||
@@ -47,6 +48,7 @@ const rootReducer = combineReducers({
|
||||
home: homeReducer,
|
||||
brand: brandReducer,
|
||||
main: mainReducer,
|
||||
mockCart: mockCartReducer,
|
||||
myPage: myPageReducer,
|
||||
onSale: onSaleReducer,
|
||||
product: productReducer,
|
||||
|
||||
@@ -63,6 +63,15 @@ export const createMockCartData = (productData, optionInfo = {}, quantity = 1) =
|
||||
return null;
|
||||
}
|
||||
|
||||
// ✅ 이미지 URL 처리 (ProductAllSection의 고품질 이미지 우선)
|
||||
const imgUrl = productData.imgUrls600?.[0] || // ✅ ProductAllSection의 고품질 이미지
|
||||
productData.thumbnailUrl960 || // ✅ 960px 썸네일
|
||||
productData.imgUrl ||
|
||||
productData.thumbnailUrl ||
|
||||
productData.imgList?.[0]?.imgUrl ||
|
||||
productData.imgUrls?.[0]?.imgUrl ||
|
||||
'/assets/images/img-thumb-empty-144@3x.png';
|
||||
|
||||
return {
|
||||
cartId: `MOCK_CART_${productData.prdtId}_${Date.now()}`,
|
||||
prdtId: productData.prdtId,
|
||||
@@ -71,12 +80,258 @@ export const createMockCartData = (productData, optionInfo = {}, quantity = 1) =
|
||||
patncNm: productData.patncNm,
|
||||
qty: quantity,
|
||||
price: productData.prdtPrice || 0,
|
||||
thumbnailUrl: productData.thumbnailUrl || null,
|
||||
|
||||
// ✅ 모든 이미지 필드 보존 (CartProduct 호환성 유지 + ProductAllSection 고품질 이미지)
|
||||
imgUrl: imgUrl,
|
||||
thumbnailUrl: productData.thumbnailUrl960 || productData.thumbnailUrl || imgUrl,
|
||||
thumbnailUrl960: productData.thumbnailUrl960,
|
||||
imgList: productData.imgList || [{ imgUrl: imgUrl }],
|
||||
imgUrls: productData.imgUrls || [{ imgUrl: imgUrl }],
|
||||
imgUrls600: productData.imgUrls600, // ✅ ProductAllSection의 고품질 이미지 배열
|
||||
|
||||
optionInfo: optionInfo,
|
||||
addedAt: new Date().toISOString()
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock Mode에서 초기 Mock 장바구니 목록 생성
|
||||
*
|
||||
* @param {Object} productData - 초기 상품 데이터 (可选)
|
||||
* @param {Object} optionInfo - 옵션 정보 (可选)
|
||||
* @param {number} quantity - 수량 (可选)
|
||||
* @returns {Array} Mock 장바구니 데이터 목록
|
||||
*/
|
||||
export const createMockCartListData = (productData, optionInfo = {}, quantity = 1) => {
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
return defaultMockItems;
|
||||
};
|
||||
|
||||
// Mock 장바구니 데이터를 저장할 전역 변수
|
||||
let mockCartItems = [];
|
||||
|
||||
/**
|
||||
* Mock Mode에 따라 장바구니 데이터 설정
|
||||
*
|
||||
* @param {Array} cartData - 장바구니 데이터
|
||||
*/
|
||||
export const setMockCartData = (cartData) => {
|
||||
if (BUYNOW_CONFIG.isMockMode()) {
|
||||
mockCartItems = cartData || [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 현재 Mock 장바구니 데이터 가져오기
|
||||
*
|
||||
* @returns {Array} 현재 Mock 장바구니 데이터
|
||||
*/
|
||||
export const getMockCartData = () => {
|
||||
return BUYNOW_CONFIG.isMockMode() ? mockCartItems : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock 장바구니에 상품 추가
|
||||
*
|
||||
* @param {Object} productData - 상품 데이터
|
||||
* @param {Object} optionInfo - 옵션 정보
|
||||
* @param {number} quantity - 수량
|
||||
* @returns {Object} 추가된 장바구니 아이템
|
||||
*/
|
||||
export const addMockCartItem = (productData, optionInfo = {}, quantity = 1) => {
|
||||
if (!BUYNOW_CONFIG.isMockMode() || !productData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ✅ 이미지 URL 처리 (ProductAllSection의 고품질 이미지 우선)
|
||||
const imgUrl = productData.imgUrls600?.[0] || // ✅ ProductAllSection의 고품질 이미지
|
||||
productData.thumbnailUrl960 || // ✅ 960px 썸네일
|
||||
productData.imgUrl ||
|
||||
productData.thumbnailUrl ||
|
||||
productData.imgList?.[0]?.imgUrl ||
|
||||
productData.imgUrls?.[0]?.imgUrl ||
|
||||
'/assets/images/img-thumb-empty-144@3x.png';
|
||||
|
||||
const cartItem = {
|
||||
prodSno: `MOCK_CART_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
prdtId: productData.prdtId || 'MOCK_PRODUCT',
|
||||
prdtNm: productData.prdtNm || 'Mock Product',
|
||||
patnrId: productData.patnrId || 'MOCK_PARTNER',
|
||||
patncNm: productData.patncNm || 'Mock Partner',
|
||||
patncLogPath: productData.patncLogPath || '/assets/images/ic-partners-qvc@3x.png',
|
||||
|
||||
// ✅ 이미지 정보 (모든 필드명 지원 - CartProduct/CheckOutPanel 호환성 + ProductAllSection 고품질 이미지)
|
||||
imgUrl: imgUrl,
|
||||
thumbnailUrl: productData.thumbnailUrl960 || productData.thumbnailUrl || imgUrl,
|
||||
thumbnailUrl960: productData.thumbnailUrl960,
|
||||
imgList: productData.imgList || [{ imgUrl: imgUrl }],
|
||||
imgUrls: productData.imgUrls || [{ imgUrl: imgUrl }], // CheckOutPanel 호환성
|
||||
imgUrls600: productData.imgUrls600, // ✅ ProductAllSection의 고품질 이미지 배열
|
||||
|
||||
// ✅ 가격 정보 (CartProduct의 calculatePartnerTotal에서 사용하는 필드들 모두 포함)
|
||||
price2: productData.price2 || productData.originalPrice || '299.99', // 원가
|
||||
price3: productData.price3 || productData.salePrice || productData.discountPrice || '199.99', // 할인가
|
||||
price5: optionInfo.price || productData.price5 || '29.99', // 옵션 할인가
|
||||
optPrc: optionInfo.price || productData.optPrc || productData.price5 || '29.99', // 옵션 가격
|
||||
|
||||
// 배송비
|
||||
shippingCharge: productData.shippingCharge || productData.shippingFee || '12.99',
|
||||
|
||||
// 수량
|
||||
prodQty: quantity || productData.prodQty || 1,
|
||||
|
||||
// 옵션명
|
||||
optNm: optionInfo.name || productData.optNm || '',
|
||||
|
||||
// 기타 정보
|
||||
addedAt: new Date().toISOString(),
|
||||
soldoutFlag: productData.soldoutFlag || 'N',
|
||||
};
|
||||
|
||||
// 중복 확인
|
||||
const existingIndex = mockCartItems.findIndex(item =>
|
||||
item.prdtId === cartItem.prdtId && item.optNm === cartItem.optNm
|
||||
);
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
// 중복이면 수량 증가
|
||||
mockCartItems[existingIndex].prodQty += cartItem.prodQty;
|
||||
return mockCartItems[existingIndex];
|
||||
} else {
|
||||
// 새 상품 추가
|
||||
mockCartItems.push(cartItem);
|
||||
return cartItem;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock 장바구니에서 상품 제거
|
||||
*
|
||||
* @param {string} prodSno - 상품 고유번호
|
||||
* @returns {Object|null} 제거된 상품 정보
|
||||
*/
|
||||
export const removeMockCartItem = (prodSno) => {
|
||||
if (!BUYNOW_CONFIG.isMockMode() || !prodSno) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = mockCartItems.findIndex(item => item.prodSno === prodSno);
|
||||
if (index !== -1) {
|
||||
const removedItem = mockCartItems.splice(index, 1)[0];
|
||||
return removedItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock 장바구니 상품 수량 업데이트
|
||||
*
|
||||
* @param {string} prodSno - 상품 고유번호
|
||||
* @param {number} quantity - 새로운 수량
|
||||
* @returns {Object|null} 업데이트된 상품 정보
|
||||
*/
|
||||
export const updateMockCartItemQuantity = (prodSno, quantity) => {
|
||||
if (!BUYNOW_CONFIG.isMockMode() || !prodSno || quantity <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const item = mockCartItems.find(item => item.prodSno === prodSno);
|
||||
if (item) {
|
||||
item.prodQty = quantity;
|
||||
return { ...item };
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock 장바구니 초기화
|
||||
*/
|
||||
export const clearMockCartData = () => {
|
||||
if (BUYNOW_CONFIG.isMockMode()) {
|
||||
mockCartItems = [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock Mode에서 가격 정보 포맷팅
|
||||
* priceInfo는 "원가|할인가" 형식 (예: "100000|80000")
|
||||
|
||||
@@ -43,6 +43,19 @@ export const panel_names = {
|
||||
USER_REVIEW_PANEL: 'userreviewpanel',
|
||||
};
|
||||
|
||||
// 단독으로 렌더링되어야 하는 패널 목록
|
||||
// 이 패널들은 항상 isOnTop=true로 설정되고 다른 패널들과 함께 표시되지 않음
|
||||
export const STANDALONE_PANELS = [
|
||||
panel_names.CHECKOUT_PANEL,
|
||||
panel_names.CART_PANEL,
|
||||
// 향후 추가될 다른 단독 패널들 여기에 추가
|
||||
];
|
||||
|
||||
// 단독 패널인지 확인하는 유틸리티 함수
|
||||
export const isStandalonePanel = (panelName) => {
|
||||
return STANDALONE_PANELS.includes(panelName);
|
||||
};
|
||||
|
||||
//button
|
||||
export const TBUTTON_PRESS_DELAY = 100;
|
||||
|
||||
|
||||
@@ -129,33 +129,46 @@ export const getSafeProductOptions = (product) => {
|
||||
* @returns {string} 이미지 URL 또는 기본값
|
||||
*/
|
||||
export const getSafeImageUrl = (product) => {
|
||||
// 1순위: imgUrls 배열 (productInfo의 imgUrls)
|
||||
const imgUrls = product?.imgUrls;
|
||||
if (Array.isArray(imgUrls) && imgUrls.length > 0) {
|
||||
return imgUrls[0];
|
||||
// ✅ 1순위: imgUrls600 배열 (ProductAllSection의 고품질 이미지)
|
||||
const imgUrls600 = product?.imgUrls600;
|
||||
if (Array.isArray(imgUrls600) && imgUrls600.length > 0) {
|
||||
return imgUrls600[0];
|
||||
}
|
||||
// 2순위: imgUrl 직접 필드
|
||||
if (product?.imgUrl) {
|
||||
return product.imgUrl;
|
||||
}
|
||||
// 3순위: thumbnailUrl (productInfo의 썸네일)
|
||||
if (product?.thumbnailUrl) {
|
||||
return product.thumbnailUrl;
|
||||
}
|
||||
// 4순위: thumbnailUrl960 (productInfo의 960px 썸네일)
|
||||
|
||||
// ✅ 2순위: thumbnailUrl960 (ProductAllSection의 960px 썸네일)
|
||||
if (product?.thumbnailUrl960) {
|
||||
return product.thumbnailUrl960;
|
||||
}
|
||||
// 5순위: imgUrls 배열의 imgUrl 필드 (기존 방식)
|
||||
const imgUrlsWithImgUrl = product?.imgUrls;
|
||||
if (Array.isArray(imgUrlsWithImgUrl) && imgUrlsWithImgUrl.length > 0) {
|
||||
return imgUrlsWithImgUrl[0]?.imgUrl || '/mock/image.jpg';
|
||||
|
||||
// 3순위: imgUrl 직접 필드
|
||||
if (product?.imgUrl) {
|
||||
return product.imgUrl;
|
||||
}
|
||||
// 6순위: patncLogPath (파트너 로고)
|
||||
|
||||
// 4순위: thumbnailUrl (productInfo의 썸네일)
|
||||
if (product?.thumbnailUrl) {
|
||||
return product.thumbnailUrl;
|
||||
}
|
||||
|
||||
// 5순위: imgUrls 배열 (productInfo의 imgUrls)
|
||||
const imgUrls = product?.imgUrls;
|
||||
if (Array.isArray(imgUrls) && imgUrls.length > 0) {
|
||||
// imgUrls[0]이 객체면 imgUrl 필드 추출, 문자열이면 직접 사용
|
||||
return typeof imgUrls[0] === 'string' ? imgUrls[0] : imgUrls[0]?.imgUrl;
|
||||
}
|
||||
|
||||
// 6순위: imgList 배열의 imgUrl 필드
|
||||
const imgList = product?.imgList;
|
||||
if (Array.isArray(imgList) && imgList.length > 0) {
|
||||
return imgList[0]?.imgUrl || '/mock/image.jpg';
|
||||
}
|
||||
|
||||
// 7순위: patncLogPath (파트너 로고)
|
||||
if (product?.patncLogPath) {
|
||||
return product.patncLogPath;
|
||||
}
|
||||
// 7순위: 기본 이미지
|
||||
|
||||
// 8순위: 기본 이미지
|
||||
return '/mock/image.jpg';
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
@@ -9,7 +10,10 @@ import {
|
||||
} from 'react-redux';
|
||||
|
||||
import { getMyInfoCartSearch } from '../../actions/cartActions';
|
||||
import { initializeMockCart, resetMockCart } from '../../actions/mockCartActions';
|
||||
import { popPanel } from '../../actions/panelActions';
|
||||
import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig';
|
||||
import * as Config from '../../utils/Config';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
import THeader from '../../components/THeader/THeader';
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
@@ -20,12 +24,58 @@ import css from './CartPanel.module.less';
|
||||
import CartProductBar from './CartProductBar';
|
||||
import CartSidebar from './CartSidebar';
|
||||
|
||||
export default function CartPanel({ spotlightId, scrollOptions = [] }) {
|
||||
export default function CartPanel({ spotlightId, scrollOptions = [], panelInfo }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// 실제 장바구니 데이터
|
||||
const cartData = useSelector((state) => state.cart.getMyinfoCartSearch.cartInfo);
|
||||
// Mock 장바구니 데이터
|
||||
const mockCartData = useSelector((state) => state.mockCart.cartInfo);
|
||||
|
||||
// 패널 상태 확인 (충돌 방지용)
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
|
||||
const { userNumber } = useSelector(
|
||||
(state) => state.common.appStatus.loginUserData
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Mock Mode 여부 확인 및 적절한 데이터 선택
|
||||
const isMockMode = BUYNOW_CONFIG.isMockMode();
|
||||
const displayCartData = useMemo(() => {
|
||||
return isMockMode ? mockCartData : cartData;
|
||||
}, [isMockMode, mockCartData, cartData]);
|
||||
|
||||
// PlayerPanel/MediaPanel 충돌 방지 로직
|
||||
useEffect(() => {
|
||||
console.log('[CartPanel] Component mounted - checking for panel conflicts');
|
||||
console.log('[CartPanel] Current panels:', panels?.map((p) => ({ name: p.name, hasModal: !!p.panelInfo?.modal })));
|
||||
|
||||
// PlayerPanel 충돌 방지: PlayerPanel이 있고 modal 상태면 비활성화
|
||||
const playerPanelIndex = panels?.findIndex(p =>
|
||||
p.name === Config.panel_names.PLAYER_PANEL ||
|
||||
p.name === Config.panel_names.PLAYER_PANEL_NEW ||
|
||||
p.name === Config.panel_names.MEDIA_PANEL
|
||||
);
|
||||
|
||||
if (playerPanelIndex >= 0) {
|
||||
console.log('[CartPanel] 🚨 PlayerPanel/MediaPanel detected at index:', playerPanelIndex);
|
||||
console.log('[CartPanel] PlayerPanel info:', panels[playerPanelIndex]);
|
||||
|
||||
// PlayerPanel/MediaPanel 상태를 비활성화하여 CartPanel과의 충돌 방지
|
||||
if (panels[playerPanelIndex].panelInfo?.modal) {
|
||||
console.log('[CartPanel] 🔄 Disabling modal PlayerPanel to prevent conflicts');
|
||||
// 필요하다면 여기서 PlayerPanel 상태를 비활성화하는 액션을 디스패치할 수 있음
|
||||
// dispatch(updatePanel({
|
||||
// name: panels[playerPanelIndex].name,
|
||||
// panelInfo: { ...panels[playerPanelIndex].panelInfo, isActive: false }
|
||||
// }));
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
console.log('[CartPanel] 🔄 Component unmounting - cleaning up');
|
||||
};
|
||||
}, [panels]);
|
||||
|
||||
const onBackClick = useCallback(() => {
|
||||
dispatch(popPanel());
|
||||
@@ -33,10 +83,32 @@ export default function CartPanel({ spotlightId, scrollOptions = [] }) {
|
||||
|
||||
// 장바구니 데이터 로드
|
||||
useEffect(() => {
|
||||
if (userNumber) {
|
||||
console.log('[CartPanel] Component mounted - isMockMode:', isMockMode, 'panelInfo:', panelInfo);
|
||||
|
||||
if (isMockMode) {
|
||||
// Mock Mode: panelInfo가 있으면 해당 상품 추가, 없으면 기본 Mock 데이터 설정
|
||||
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 장바구니 초기화 (기존 데이터 유지)
|
||||
dispatch(initializeMockCart(
|
||||
panelInfo.productInfo,
|
||||
panelInfo.optionInfo || {},
|
||||
panelInfo.quantity || 1
|
||||
));
|
||||
} else {
|
||||
// panelInfo가 없으면 기본 Mock 장바구니 데이터로 설정
|
||||
console.log('[CartPanel] Mock Mode - Initializing with default mock data');
|
||||
dispatch(resetMockCart());
|
||||
}
|
||||
} else if (userNumber) {
|
||||
// API Mode: 실제 API 호출
|
||||
console.log('[CartPanel] API Mode - Loading cart from API for user:', userNumber);
|
||||
dispatch(getMyInfoCartSearch({ mbrNo: userNumber }));
|
||||
}
|
||||
}, [dispatch, userNumber]);
|
||||
}, [dispatch, userNumber, isMockMode, panelInfo]);
|
||||
|
||||
const {
|
||||
getScrollTo,
|
||||
@@ -56,17 +128,17 @@ export default function CartPanel({ spotlightId, scrollOptions = [] }) {
|
||||
/>
|
||||
<div className={css.Wrap}>
|
||||
<div className={css.leftSection}>
|
||||
<CartSidebar cartInfo={cartData}/>
|
||||
<CartSidebar cartInfo={displayCartData}/>
|
||||
</div>
|
||||
<div className={css.rightSection} cbScrollTo={getScrollToBody}>
|
||||
{/* 오른쪽 상품 영역 */}
|
||||
{cartData && cartData?.length > 0 ? (
|
||||
{displayCartData && displayCartData?.length > 0 ? (
|
||||
<TScroller
|
||||
className={css.tScroller}
|
||||
scrollTopBody={scrollTopBody}
|
||||
{...scrollOptions}
|
||||
>
|
||||
<CartProductBar cartInfo={cartData} />
|
||||
<CartProductBar cartInfo={displayCartData} />
|
||||
</TScroller>
|
||||
) : (
|
||||
<CartEmpty />
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, {
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator
|
||||
@@ -17,11 +17,25 @@ import defaultImage from '../../../assets/images/img-thumb-empty-144@3x.png';
|
||||
import CustomImage from '../../components/CustomImage/CustomImage';
|
||||
import TButton from '../../components/TButton/TButton';
|
||||
import TCheckBoxSquare from '../../components/TCheckBox/TCheckBoxSquare';
|
||||
import { removeFromCart, updateCartItem } from '../../actions/cartActions';
|
||||
import { removeFromMockCart, setMockCartItemQuantity } from '../../actions/mockCartActions';
|
||||
import store from '../../store/store';
|
||||
import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig';
|
||||
import { normalizeProductDataForDisplay } from '../../utils/mockDataSafetyUtils';
|
||||
import css from './CartProduct.module.less';
|
||||
|
||||
const Container = SpotlightContainerDecorator("div");
|
||||
const CartProduct = () => {
|
||||
const cartData = useSelector((state) => state.cart.getMyinfoCartSearch.cartInfo);
|
||||
const CartProduct = ({ cartInfo }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// 항상 호출되어야 하는 Hook들
|
||||
const fallbackCartData = useSelector((state) => state.cart.getMyinfoCartSearch.cartInfo);
|
||||
|
||||
// 실제 장바구니 데이터와 Mock 데이터 중 선택
|
||||
const cartData = cartInfo || fallbackCartData;
|
||||
|
||||
// Mock Mode 확인
|
||||
const isMockMode = BUYNOW_CONFIG.isMockMode();
|
||||
|
||||
//카트 데이타 그룹화 - 수정된 부분
|
||||
const groupedCartData = useMemo(() => {
|
||||
@@ -71,23 +85,49 @@ const CartProduct = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const [pdEa, setPdEa] = useState(1);
|
||||
// 수량 조절 핸들러
|
||||
const handleDecreseClick = useCallback((prodSno, currentQty) => {
|
||||
if (currentQty > 1) {
|
||||
const newQty = currentQty - 1;
|
||||
|
||||
const handleDecreseClick = useCallback(() => {
|
||||
if (pdEa > 1) {
|
||||
setPdEa(pdEa - 1);
|
||||
if (isMockMode) {
|
||||
dispatch(setMockCartItemQuantity(prodSno, newQty));
|
||||
} else {
|
||||
// 실제 API 호출을 위한 사용자 정보 필요
|
||||
const { userNumber } = store.getState().common.appStatus.loginUserData;
|
||||
if (userNumber) {
|
||||
dispatch(updateCartItem({ mbrNo: userNumber, cartSno: prodSno, prodQty: newQty }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [pdEa]);
|
||||
}, [dispatch, isMockMode]);
|
||||
|
||||
const handleIncreseClick = useCallback(() => {
|
||||
setPdEa(pdEa + 1);
|
||||
}, [pdEa]);
|
||||
const handleIncreseClick = useCallback((prodSno, currentQty) => {
|
||||
const newQty = currentQty + 1;
|
||||
|
||||
useEffect(() => {
|
||||
if (pdEa === 1) {
|
||||
Spotlight.focus("pd_ea_increse");
|
||||
if (isMockMode) {
|
||||
dispatch(setMockCartItemQuantity(prodSno, newQty));
|
||||
} else {
|
||||
// 실제 API 호출을 위한 사용자 정보 필요
|
||||
const { userNumber } = store.getState().common.appStatus.loginUserData;
|
||||
if (userNumber) {
|
||||
dispatch(updateCartItem({ mbrNo: userNumber, cartSno: prodSno, prodQty: newQty }));
|
||||
}
|
||||
}
|
||||
}, [pdEa]);
|
||||
}, [dispatch, isMockMode]);
|
||||
|
||||
// 상품 삭제 핸들러
|
||||
const handleDeleteClick = useCallback((prodSno) => {
|
||||
if (isMockMode) {
|
||||
dispatch(removeFromMockCart(prodSno));
|
||||
} else {
|
||||
// 실제 API 호출을 위한 사용자 정보 필요
|
||||
const { userNumber } = store.getState().common.appStatus.loginUserData;
|
||||
if (userNumber) {
|
||||
dispatch(removeFromCart({ mbrNo: userNumber, cartSno: prodSno }));
|
||||
}
|
||||
}
|
||||
}, [dispatch, isMockMode]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -95,8 +135,8 @@ const CartProduct = () => {
|
||||
const totals = calculatePartnerTotal(group.items);
|
||||
|
||||
return (
|
||||
<Container className={css.productBox}>
|
||||
<div key={partnerKey} className={css.partnerSection}>
|
||||
<Container key={partnerKey} className={css.productBox}>
|
||||
<div className={css.partnerSection}>
|
||||
{/* 파트너사 정보 - 한 번만 표시 */}
|
||||
<div className={css.productCompany}>
|
||||
<div className={css.logo}>
|
||||
@@ -135,7 +175,22 @@ const CartProduct = () => {
|
||||
</div>
|
||||
|
||||
{/* 해당 파트너사의 상품 리스트 - map으로 반복 */}
|
||||
{group.items.map((item) => (
|
||||
{group.items.map((item) => {
|
||||
// CheckOutPanel과 동일한 이미지 처리 방식 적용
|
||||
const normalizedItem = normalizeProductDataForDisplay(item);
|
||||
|
||||
// ✅ 이미지 우선순위: ProductAllSection 고품질 이미지 → 기타 모든 이미지 필드
|
||||
const imageSrc = (item.imgUrls600 && item.imgUrls600[0]) || // ✅ ProductAllSection의 고품질 이미지
|
||||
(item.thumbnailUrl960) || // ✅ 960px 썸네일
|
||||
(normalizedItem.imgUrls && normalizedItem.imgUrls[0]?.imgUrl) ||
|
||||
(item.imgUrls && item.imgUrls[0]?.imgUrl) ||
|
||||
(item.imgList && item.imgList[0]?.imgUrl) ||
|
||||
normalizedItem.imgUrl ||
|
||||
item.imgUrl ||
|
||||
item.thumbnailUrl ||
|
||||
defaultImage;
|
||||
|
||||
return (
|
||||
<div key={item.prodSno} className={css.product}>
|
||||
<div className={css.leftBox}>
|
||||
<div className={css.checkBox}>
|
||||
@@ -151,8 +206,9 @@ const CartProduct = () => {
|
||||
<div className={css.leftSection}>
|
||||
<CustomImage
|
||||
className={css.productImage}
|
||||
src={item.imgUrl}
|
||||
src={imageSrc}
|
||||
fallbackSrc={defaultImage}
|
||||
alt={item.prdtNm || 'Product Image'}
|
||||
/>
|
||||
</div>
|
||||
<div className={css.rightSection}>
|
||||
@@ -191,13 +247,13 @@ const CartProduct = () => {
|
||||
item.prodQty === 1 ? css.dimm : ""
|
||||
)}
|
||||
size="cartEa"
|
||||
onClick={() => handleDecreseClick(item.prodSno)}
|
||||
onClick={() => handleDecreseClick(item.prodSno, item.prodQty)}
|
||||
spotlightId={"pd_ea_decrese"}
|
||||
spotlightDisabled={item.prodQty === 1}
|
||||
/>
|
||||
<div className={css.ea}>{item.prodQty}</div>
|
||||
<TButton
|
||||
onClick={() => handleIncreseClick(item.prodSno)}
|
||||
onClick={() => handleIncreseClick(item.prodSno, item.prodQty)}
|
||||
className={css.plusBox}
|
||||
spotlightId={"pd_ea_increse"}
|
||||
size="cartEa"
|
||||
@@ -210,11 +266,12 @@ const CartProduct = () => {
|
||||
<TButton
|
||||
className={css.trashImg}
|
||||
size="cartTrash"
|
||||
// onClick={() => handleDeleteClick(item.prodSno)}
|
||||
onClick={() => handleDeleteClick(item.prodSno)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -6,17 +6,12 @@ import SpotlightContainerDecorator
|
||||
import CartProduct from './CartProduct';
|
||||
import css from './CartProductBar.module.less';
|
||||
|
||||
const CartProductBar = ({ scrollOptions = {} }) => {
|
||||
const CartProductBar = ({ cartInfo, scrollOptions = {} }) => {
|
||||
const Container = SpotlightContainerDecorator("div");
|
||||
|
||||
// const randomCount = useMemo(() => Math.floor(Math.random() * 3) + 1, []);
|
||||
|
||||
return (
|
||||
<Container className={css.productContainer}>
|
||||
<CartProduct />
|
||||
{/* {Array.from({ length: randomCount }, (_, index) => ( */}
|
||||
{/* <CartProduct key={index} /> */}
|
||||
{/* ))} */}
|
||||
<CartProduct cartInfo={cartInfo} />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,84 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import TButton from '../../components/TButton/TButton';
|
||||
import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig';
|
||||
import { pushPanel } from '../../actions/panelActions';
|
||||
import Config from '../../utils/Config';
|
||||
import css from './CartSidebar.module.less';
|
||||
|
||||
const CartSidebar = () => {
|
||||
const mockData = {
|
||||
itemCount: 3,
|
||||
subtotal: 199.97,
|
||||
optionTotal: 29.99,
|
||||
shippingHandling: 19.99,
|
||||
orderTotalBeforeTax: 249.95,
|
||||
};
|
||||
const CartSidebar = ({ cartInfo }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Mock Mode 확인
|
||||
const isMockMode = BUYNOW_CONFIG.isMockMode();
|
||||
|
||||
// 실제 장바구니 데이터 (API 모드일 때만 사용)
|
||||
const fallbackCartInfo = useSelector((state) => state.cart.getMyinfoCartSearch.cartInfo);
|
||||
|
||||
// 사용할 장바구니 데이터 결정
|
||||
const displayCartInfo = cartInfo || (isMockMode ? null : fallbackCartInfo);
|
||||
|
||||
// Mock 데이터 또는 실제 데이터 계산
|
||||
const calculatedData = useMemo(() => {
|
||||
if (isMockMode) {
|
||||
// Mock Mode: 기본 mockData 사용
|
||||
return {
|
||||
itemCount: 3,
|
||||
subtotal: 199.97,
|
||||
optionTotal: 29.99,
|
||||
shippingHandling: 19.99,
|
||||
orderTotalBeforeTax: 249.95,
|
||||
};
|
||||
} 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(item.price3 || item.price2 || 0);
|
||||
return sum + (price * (item.prodQty || 1));
|
||||
}, 0);
|
||||
const optionTotal = displayCartInfo.reduce((sum, item) => {
|
||||
const optionPrice = parseFloat(item.price5 || item.optPrc || 0);
|
||||
return sum + (optionPrice * (item.prodQty || 1));
|
||||
}, 0);
|
||||
const shippingHandling = displayCartInfo.reduce((sum, item) =>
|
||||
sum + parseFloat(item.shippingCharge || 0), 0
|
||||
);
|
||||
|
||||
return {
|
||||
itemCount,
|
||||
subtotal,
|
||||
optionTotal,
|
||||
shippingHandling,
|
||||
orderTotalBeforeTax: subtotal + optionTotal + shippingHandling,
|
||||
};
|
||||
} else {
|
||||
// 데이터가 없는 경우
|
||||
return {
|
||||
itemCount: 0,
|
||||
subtotal: 0,
|
||||
optionTotal: 0,
|
||||
shippingHandling: 0,
|
||||
orderTotalBeforeTax: 0,
|
||||
};
|
||||
}
|
||||
}, [isMockMode, displayCartInfo]);
|
||||
|
||||
// 체크아웃 버튼 클릭 핸들러
|
||||
const handleCheckoutClick = useCallback(() => {
|
||||
if (isMockMode) {
|
||||
// Mock Mode: 바로 체크아웃 패널로 이동
|
||||
console.log('[CartSidebar] Mock Mode - Going to Checkout');
|
||||
dispatch(pushPanel({
|
||||
name: Config.panel_names.CHECKOUT_PANEL,
|
||||
}));
|
||||
} else {
|
||||
// API Mode: 실제 로직 (향후 구현)
|
||||
console.log('[CartSidebar] API Mode - Checkout (to be implemented)');
|
||||
}
|
||||
}, [dispatch, isMockMode]);
|
||||
|
||||
const { itemCount, subtotal, optionTotal, shippingHandling, orderTotalBeforeTax } = calculatedData;
|
||||
|
||||
const formatPrice = (price) => {
|
||||
return `$${price.toFixed(2)}`;
|
||||
@@ -21,24 +89,24 @@ const CartSidebar = () => {
|
||||
<div className={css.summarySection}>
|
||||
<div className={css.header}>
|
||||
<div className={css.title}>Subtotal</div>
|
||||
<span className={css.itemCount}>{mockData.itemCount} Items</span>
|
||||
<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(mockData.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(mockData.optionTotal)}
|
||||
{formatPrice(optionTotal)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={css.priceItem}>
|
||||
<span className={css.label}>S&H</span>
|
||||
<span className={css.value}>
|
||||
{formatPrice(mockData.shippingHandling)}
|
||||
{formatPrice(shippingHandling)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,7 +117,7 @@ const CartSidebar = () => {
|
||||
<span className={css.totalLabelSub}>(Before Tax)</span>
|
||||
</span>
|
||||
<span className={css.totalValue}>
|
||||
{formatPrice(mockData.orderTotalBeforeTax)}
|
||||
{formatPrice(orderTotalBeforeTax)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,7 +138,7 @@ const CartSidebar = () => {
|
||||
<TButton
|
||||
className={css.checkoutButton}
|
||||
spotlightId="cart-checkout-button"
|
||||
onClick={() => console.log("Checkout clicked")}
|
||||
onClick={handleCheckoutClick}
|
||||
>
|
||||
Checkout
|
||||
</TButton>
|
||||
|
||||
@@ -16,6 +16,7 @@ import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
|
||||
import { addToCart } from '../../../actions/cartActions';
|
||||
import { addToMockCart } from '../../../actions/mockCartActions';
|
||||
import { getMyInfoCheckoutInfo } from '../../../actions/checkoutActions';
|
||||
import {
|
||||
changeAppStatus,
|
||||
@@ -792,7 +793,7 @@ const BuyOption = ({
|
||||
// })
|
||||
// );
|
||||
|
||||
// Mock Mode: API 호출 스킵
|
||||
// API Mode: 실제 API 호출 후 CartPanel로 이동
|
||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||
// 장바구니에 추가
|
||||
dispatch(
|
||||
@@ -811,17 +812,138 @@ const BuyOption = ({
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Mock Mode: 로컬 장바구니 데이터 생성 후 CartPanel로 이동
|
||||
console.log('[BuyOption] Mock Mode - Adding to cart (Mock)');
|
||||
}
|
||||
|
||||
// CartPanel로 이동
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: Config.panel_names.CART_PANEL,
|
||||
})
|
||||
);
|
||||
// CartPanel로 이동 (productInfo 포함) - API에서는 이미 addToCart 호출됨
|
||||
// 이미지 URL 구성 (CheckOutPanel과 동일한 방식)
|
||||
// ✅ 이미지 URL 추출 (ProductAllSection의 고품질 이미지 우선)
|
||||
const imgUrl = productInfo?.imgUrls600?.[0] || // ✅ ProductAllSection에서 사용하는 고품질 이미지
|
||||
productInfo?.thumbnailUrl960 || // ✅ 960px 썸네일
|
||||
productInfo?.imgList?.[0]?.imgUrl ||
|
||||
productInfo?.thumbnailUrl ||
|
||||
productInfo?.patncLogPath ||
|
||||
'/assets/images/img-thumb-empty-144@3x.png';
|
||||
|
||||
// 가격 정보 구성
|
||||
const regularPrice = productInfo?.regularPrice || 299.99;
|
||||
const discountPrice = productInfo?.discountPrice || regularPrice;
|
||||
|
||||
const productInfoForCart = {
|
||||
// 기본 정보
|
||||
prdtId: selectedPrdtId,
|
||||
prdtNm: productInfo?.prdtNm || 'Product',
|
||||
patnrId: selectedPatnrId,
|
||||
patncNm: productInfo?.patncNm || 'Partner',
|
||||
patncLogPath: productInfo?.patncLogPath || '',
|
||||
|
||||
// ✅ 이미지 정보 (ProductAllSection의 고품질 이미지 포함)
|
||||
imgUrl: imgUrl,
|
||||
thumbnailUrl: productInfo?.thumbnailUrl960 || imgUrl,
|
||||
thumbnailUrl960: productInfo?.thumbnailUrl960,
|
||||
imgList: productInfo?.imgList || [{ imgUrl: imgUrl }],
|
||||
imgUrls: productInfo?.imgUrls || [{ imgUrl: imgUrl }], // imgUrls 배열 구조 추가 (CheckOutPanel 호환성)
|
||||
imgUrls600: productInfo?.imgUrls600, // ✅ 고품질 이미지 배열 포함
|
||||
|
||||
// 가격 정보 (문자열 형식으로)
|
||||
price2: regularPrice.toFixed(2), // 원가
|
||||
price3: discountPrice.toFixed(2), // 할인가/판매가
|
||||
|
||||
// 수량 정보
|
||||
prodQty: quantity,
|
||||
|
||||
// 옵션 정보
|
||||
optNm: productOptionInfos[0]?.optNm || '',
|
||||
|
||||
// 배송비 정보
|
||||
shippingCharge: productInfo?.shippingFee || '12.99',
|
||||
|
||||
// 기타 정보
|
||||
soldoutFlag: productInfo?.soldoutFlag || 'N',
|
||||
};
|
||||
|
||||
const optionInfoForCart = {
|
||||
name: productOptionInfos[0]?.optNm || '',
|
||||
price: productOptionInfos[0]?.prdtOptDtl[0]?.optPrc || '0.00'
|
||||
};
|
||||
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: Config.panel_names.CART_PANEL,
|
||||
panelInfo: {
|
||||
productInfo: productInfoForCart,
|
||||
optionInfo: optionInfoForCart,
|
||||
quantity: quantity
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Mock Mode: Mock 장바구니에 상품 추가 후 CartPanel로 이동
|
||||
console.log('[BuyOption] Mock Mode - Adding to cart (Mock)');
|
||||
|
||||
// ✅ 이미지 URL 구성 (ProductAllSection의 고품질 이미지 우선)
|
||||
const imgUrl = productInfo?.imgUrls600?.[0] || // ✅ ProductAllSection에서 사용하는 고품질 이미지
|
||||
productInfo?.thumbnailUrl960 || // ✅ 960px 썸네일
|
||||
productInfo?.imgList?.[0]?.imgUrl ||
|
||||
productInfo?.thumbnailUrl ||
|
||||
productInfo?.patncLogPath ||
|
||||
'/assets/images/img-thumb-empty-144@3x.png';
|
||||
|
||||
// 가격 정보 구성 (CheckOutPanel과 동일한 방식)
|
||||
const regularPrice = productInfo?.regularPrice || 299.99;
|
||||
const discountPrice = productInfo?.discountPrice || regularPrice;
|
||||
|
||||
// Mock 상품 정보 구성 (CheckOutPanel 구조 참고)
|
||||
const mockProductInfo = {
|
||||
// 기본 정보
|
||||
prdtId: selectedPrdtId,
|
||||
prdtNm: productInfo?.prdtNm || 'Mock Product',
|
||||
patnrId: selectedPatnrId,
|
||||
patncNm: productInfo?.patncNm || 'Mock Partner',
|
||||
patncLogPath: productInfo?.patncLogPath || '',
|
||||
|
||||
// ✅ 이미지 정보 (ProductAllSection의 고품질 이미지 포함)
|
||||
imgUrl: imgUrl,
|
||||
thumbnailUrl: productInfo?.thumbnailUrl960 || imgUrl,
|
||||
thumbnailUrl960: productInfo?.thumbnailUrl960,
|
||||
imgList: productInfo?.imgList || [{ imgUrl: imgUrl }], // imgList 배열 구조 추가
|
||||
imgUrls: productInfo?.imgUrls || [{ imgUrl: imgUrl }], // imgUrls 배열 구조 추가 (CheckOutPanel 호환성)
|
||||
imgUrls600: productInfo?.imgUrls600, // ✅ 고품질 이미지 배열 포함
|
||||
|
||||
// 가격 정보 (문자열 형식으로)
|
||||
price2: regularPrice.toFixed(2), // 원가
|
||||
price3: discountPrice.toFixed(2), // 할인가/판매가
|
||||
|
||||
// 수량 정보
|
||||
prodQty: quantity,
|
||||
|
||||
// 옵션 정보 (필요시)
|
||||
optNm: productOptionInfos[0]?.optNm || 'Default Option',
|
||||
|
||||
// 배송비 정보
|
||||
shippingCharge: productInfo?.shippingFee || '12.99',
|
||||
shippingFee: productInfo?.shippingFee || '12.99',
|
||||
|
||||
// 기타 정보
|
||||
soldoutFlag: productInfo?.soldoutFlag || 'N',
|
||||
};
|
||||
|
||||
// 옵션 정보 구성
|
||||
const optionInfo = {
|
||||
name: productOptionInfos[0]?.optNm || 'Default Option',
|
||||
price: productOptionInfos[0]?.prdtOptDtl[0]?.optPrc || '0.00'
|
||||
};
|
||||
|
||||
// CartPanel로 이동 (productInfo 포함) - CartPanel에서 직접 상품 추가
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: Config.panel_names.CART_PANEL,
|
||||
panelInfo: {
|
||||
productInfo: mockProductInfo,
|
||||
optionInfo: optionInfo,
|
||||
quantity: quantity
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(clearAllToasts());
|
||||
|
||||
@@ -46,7 +46,7 @@ import TNewPopUp from '../../components/TPopUp/TNewPopUp';
|
||||
import TPopUp from '../../components/TPopUp/TPopUp';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import * as Config from '../../utils/Config';
|
||||
import { panel_names } from '../../utils/Config';
|
||||
import { panel_names, STANDALONE_PANELS, isStandalonePanel } from '../../utils/Config';
|
||||
import { $L, getErrorMessage, getSpottableDescendants } from '../../utils/helperMethods';
|
||||
import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig';
|
||||
import { SpotlightIds } from '../../utils/SpotlightIds';
|
||||
@@ -200,10 +200,10 @@ export default function MainView({ className, initService }) {
|
||||
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
// CheckOutPanel은 독립적으로 항상 단독 렌더링
|
||||
if (topPanel?.name === Config.panel_names.CHECKOUT_PANEL) {
|
||||
console.log('[MainView] CheckOutPanel detected - rendering independently');
|
||||
renderingPanels = [topPanel]; // CheckOutPanel만 단독으로 렌더링
|
||||
// 단독 패널 체크 - CheckOutPanel, CartPanel 등 단독으로 렌더링되어야 하는 패널들
|
||||
if (isStandalonePanel(topPanel?.name)) {
|
||||
console.log(`[MainView] Standalone panel detected: ${topPanel?.name} - rendering independently`);
|
||||
renderingPanels = [topPanel]; // 단독 패널만 단독으로 렌더링
|
||||
}
|
||||
// 기존 3-layer 구조 체크: PlayerPanel + DetailPanel + MediaPanel(modal)
|
||||
else {
|
||||
@@ -248,10 +248,10 @@ export default function MainView({ className, initService }) {
|
||||
const Component = panelMap[panel.name];
|
||||
let isPanelOnTop = false;
|
||||
|
||||
// CheckOutPanel은 항상 onTop
|
||||
if (panel.name === Config.panel_names.CHECKOUT_PANEL) {
|
||||
// 단독 패널은 항상 onTop
|
||||
if (isStandalonePanel(panel.name)) {
|
||||
isPanelOnTop = true;
|
||||
console.log('[MainView] CheckOutPanel is always onTop');
|
||||
console.log(`[MainView] Standalone panel ${panel.name} is always onTop`);
|
||||
}
|
||||
// 3-layer 케이스: 중간 패널(DetailPanel)이 onTop
|
||||
else if (renderingPanels.length === 3) {
|
||||
|
||||
Reference in New Issue
Block a user