From efeb45823e67bc326fd929b5b64aa63bd4795ad7 Mon Sep 17 00:00:00 2001 From: optrader Date: Sun, 2 Nov 2025 08:49:51 +0900 Subject: [PATCH] [251102] fix: CartPanel mock-1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🕐 커밋 시간: 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 컴포넌트 아키텍처 개선 • 핵심 비즈니스 로직 개선 • 공통 유틸리티 함수 최적화 • 대규모 기능 개발 • 모듈 구조 개선 --- .../components/VideoPlayer/TReactPlayer.jsx | 1 + .../src/reducers/mockCartReducer.js | 272 ++++++++++++++++++ com.twin.app.shoptime/src/store/store.js | 2 + .../src/utils/BuyNowDataManipulator.js | 257 ++++++++++++++++- com.twin.app.shoptime/src/utils/Config.js | 13 + .../src/utils/mockDataSafetyUtils.js | 51 ++-- .../src/views/CartPanel/CartPanel.jsx | 86 +++++- .../src/views/CartPanel/CartProduct.jsx | 109 +++++-- .../src/views/CartPanel/CartProductBar.jsx | 9 +- .../src/views/CartPanel/CartSidebar.jsx | 98 ++++++- .../DetailPanel/components/BuyOption.jsx | 144 +++++++++- .../src/views/MainView/MainView.jsx | 16 +- 12 files changed, 964 insertions(+), 94 deletions(-) create mode 100644 com.twin.app.shoptime/src/reducers/mockCartReducer.js diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx b/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx index f61dba6e..79c1e6b1 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx +++ b/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx @@ -22,6 +22,7 @@ export default function TReactPlayer({ mediaEventsMap = handledMediaEventsMap, videoRef, url, + dispatch, ...rest }) { const playerRef = useRef(null); diff --git a/com.twin.app.shoptime/src/reducers/mockCartReducer.js b/com.twin.app.shoptime/src/reducers/mockCartReducer.js new file mode 100644 index 00000000..32ab3ac2 --- /dev/null +++ b/com.twin.app.shoptime/src/reducers/mockCartReducer.js @@ -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; + } +}; \ No newline at end of file diff --git a/com.twin.app.shoptime/src/store/store.js b/com.twin.app.shoptime/src/store/store.js index 612591ed..7c5c7728 100644 --- a/com.twin.app.shoptime/src/store/store.js +++ b/com.twin.app.shoptime/src/store/store.js @@ -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, diff --git a/com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js b/com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js index 5a82ebbf..0bbb7aa7 100644 --- a/com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js +++ b/com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js @@ -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") diff --git a/com.twin.app.shoptime/src/utils/Config.js b/com.twin.app.shoptime/src/utils/Config.js index 22bec291..26a19760 100644 --- a/com.twin.app.shoptime/src/utils/Config.js +++ b/com.twin.app.shoptime/src/utils/Config.js @@ -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; diff --git a/com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js b/com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js index b9014391..f779645f 100644 --- a/com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js +++ b/com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js @@ -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'; }; diff --git a/com.twin.app.shoptime/src/views/CartPanel/CartPanel.jsx b/com.twin.app.shoptime/src/views/CartPanel/CartPanel.jsx index 2f32a6f7..90b3047e 100644 --- a/com.twin.app.shoptime/src/views/CartPanel/CartPanel.jsx +++ b/com.twin.app.shoptime/src/views/CartPanel/CartPanel.jsx @@ -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 = [] }) { />
- +
{/* 오른쪽 상품 영역 */} - {cartData && cartData?.length > 0 ? ( + {displayCartData && displayCartData?.length > 0 ? ( - + ) : ( diff --git a/com.twin.app.shoptime/src/views/CartPanel/CartProduct.jsx b/com.twin.app.shoptime/src/views/CartPanel/CartProduct.jsx index fc872dea..484036b7 100644 --- a/com.twin.app.shoptime/src/views/CartPanel/CartProduct.jsx +++ b/com.twin.app.shoptime/src/views/CartPanel/CartProduct.jsx @@ -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]); - - const handleIncreseClick = useCallback(() => { - setPdEa(pdEa + 1); - }, [pdEa]); + }, [dispatch, isMockMode]); - useEffect(() => { - if (pdEa === 1) { - Spotlight.focus("pd_ea_increse"); + const handleIncreseClick = useCallback((prodSno, currentQty) => { + const newQty = currentQty + 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 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 ( - -
+ +
{/* 파트너사 정보 - 한 번만 표시 */}
@@ -135,7 +175,22 @@ const CartProduct = () => {
{/* 해당 파트너사의 상품 리스트 - 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 (
@@ -151,8 +206,9 @@ const CartProduct = () => {
@@ -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} />
{item.prodQty}
handleIncreseClick(item.prodSno)} + onClick={() => handleIncreseClick(item.prodSno, item.prodQty)} className={css.plusBox} spotlightId={"pd_ea_increse"} size="cartEa" @@ -207,14 +263,15 @@ const CartProduct = () => {
- handleDeleteClick(item.prodSno)} + onClick={() => handleDeleteClick(item.prodSno)} />
- ))} + ); + })}
); diff --git a/com.twin.app.shoptime/src/views/CartPanel/CartProductBar.jsx b/com.twin.app.shoptime/src/views/CartPanel/CartProductBar.jsx index 051e9219..8f3d836c 100644 --- a/com.twin.app.shoptime/src/views/CartPanel/CartProductBar.jsx +++ b/com.twin.app.shoptime/src/views/CartPanel/CartProductBar.jsx @@ -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 ( - - {/* {Array.from({ length: randomCount }, (_, index) => ( */} - {/* */} - {/* ))} */} + ); }; diff --git a/com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx b/com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx index 53049efc..3fcdfd9b 100644 --- a/com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx +++ b/com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx @@ -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 = () => {
Subtotal
- {mockData.itemCount} Items + {itemCount} Items
Subtotal - {formatPrice(mockData.subtotal)} + {formatPrice(subtotal)}
Option - {formatPrice(mockData.optionTotal)} + {formatPrice(optionTotal)}
S&H - {formatPrice(mockData.shippingHandling)} + {formatPrice(shippingHandling)}
@@ -49,7 +117,7 @@ const CartSidebar = () => { (Before Tax) - {formatPrice(mockData.orderTotalBeforeTax)} + {formatPrice(orderTotalBeforeTax)}
@@ -70,7 +138,7 @@ const CartSidebar = () => { console.log("Checkout clicked")} + onClick={handleCheckoutClick} > Checkout diff --git a/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx b/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx index d54378e0..df7c0712 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx @@ -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()); diff --git a/com.twin.app.shoptime/src/views/MainView/MainView.jsx b/com.twin.app.shoptime/src/views/MainView/MainView.jsx index b63f5c8e..9fa64d2b 100644 --- a/com.twin.app.shoptime/src/views/MainView/MainView.jsx +++ b/com.twin.app.shoptime/src/views/MainView/MainView.jsx @@ -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) {