From 6292e84052b8d2eb371a64bfd79c2539d8da1b1f Mon Sep 17 00:00:00 2001 From: optrader Date: Sat, 1 Nov 2025 21:01:11 +0900 Subject: [PATCH] [251101] fix: CheckOutPanel Mock-2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ• ์ปค๋ฐ‹ ์‹œ๊ฐ„: 2025. 11. 01. 21:01:06 ๐Ÿ“Š ๋ณ€๊ฒฝ ํ†ต๊ณ„: โ€ข ์ด ํŒŒ์ผ: 10๊ฐœ โ€ข ์ถ”๊ฐ€: +617์ค„ โ€ข ์‚ญ์ œ: -142์ค„ ๐Ÿ“ ์ถ”๊ฐ€๋œ ํŒŒ์ผ: + com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js ๐Ÿ“ ์ˆ˜์ •๋œ ํŒŒ์ผ: ~ com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js ~ com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/OrderItemsSideBar.jsx ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/SummaryContainerMock.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.module.less ~ com.twin.app.shoptime/src/views/ErrorBoundary.js ๐Ÿ”ง ์ฃผ์š” ๋ณ€๊ฒฝ ๋‚ด์šฉ: โ€ข ๊ณตํ†ต ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ์ตœ์ ํ™” โ€ข UI ์ปดํฌ๋„ŒํŠธ ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์„  โ€ข ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋กœ์ง ๊ฐœ์„  โ€ข ๋Œ€๊ทœ๋ชจ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ โ€ข ๋ชจ๋“ˆ ๊ตฌ์กฐ ๊ฐœ์„  --- .../src/utils/BuyNowDataManipulator.js | 49 +-- .../src/utils/mockDataSafetyUtils.js | 326 ++++++++++++++++++ .../src/views/CheckOutPanel/CheckOutPanel.jsx | 83 +++-- .../container/OrderItemsSideBar.jsx | 93 ++++- .../container/SummaryContainerMock.jsx | 70 +++- .../ProductAllSection.module.less | 19 + .../DetailPanel/components/BuyOption.jsx | 159 +++++++-- .../DetailPanel/components/FavoriteBtn.jsx | 7 +- .../components/FavoriteBtn.module.less | 3 +- .../src/views/ErrorBoundary.js | 274 +++++++++++++-- 10 files changed, 942 insertions(+), 141 deletions(-) create mode 100644 com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js diff --git a/com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js b/com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js index 81522d17..5a82ebbf 100644 --- a/com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js +++ b/com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js @@ -19,34 +19,35 @@ export const createMockProductOptionData = (originalProductData) => { } // ์›๋ณธ ๋ฐ์ดํ„ฐ์—์„œ ํ•„์š”ํ•œ ์ •๋ณด ์ถ”์ถœ - const basePrice = originalProductData.prdtPrice || 0; - const prdtId = originalProductData.prdtId; - const prdtNm = originalProductData.prdtNm; + const prdtId = originalProductData.prdtId || 'MOCK_PRODUCT'; + const prdtNm = originalProductData.prdtNm || 'Mock Product'; - // Mock ์˜ต์…˜ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ - return { - // ์˜ต์…˜ ID (๊ธฐ๋ณธ ๊ฐ’) - prodOptSno: `MOCK_OPT_${prdtId}_1`, + // ์‹ค์ œ ์ƒํ’ˆ ๊ฐ€๊ฒฉ ์ •๋ณด ์‚ฌ์šฉ (originalProductData์˜ priceInfo ๋˜๋Š” ๊ธฐ๋ณธ๊ฐ’) + // priceInfo ํฌ๋งท: "์›๊ฐ€|ํ• ์ธ๊ฐ€|ํ• ์ธ์•ก|ํ• ์ธ์œจ|ํ• ์ธ์œจ์ˆซ์ž" + const priceInfo = originalProductData.priceInfo || '99999|99999|0|0%|0'; - // ์˜ต์…˜ ๋ชฉ๋ก (๊ธฐ๋ณธ๊ฐ’ 1๊ฐœ) - optionList: [ - { - optId: `MOCK_OPT_${prdtId}_1`, - optNm: `${prdtNm} - ๊ธฐ๋ณธ ์˜ต์…˜`, - optPrice: basePrice, - optDscntPrice: basePrice, - stockCnt: 999, - soldOutYn: 'N', - optImgUrl: originalProductData.thumbnailUrl || null - } - ], + console.log('[BuyNowDataManipulator] createMockProductOptionData - priceInfo:', priceInfo); - // ์˜ต์…˜ ํƒ€์ž… ์ •๋ณด - optionTypeInfo: { - optTypeCd: 'BASIC', - optTypeNm: '๊ธฐ๋ณธ' - } + // ์˜ต์…˜ ์ƒ์„ธ ๊ฐ์ฒด + const optionDetail = { + prodOptCval: `${prdtNm}`, + priceInfo: priceInfo, + stockCnt: 999, + soldOutYn: 'N', }; + + // Mock ์˜ต์…˜ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ (BuyOption์—์„œ ๊ธฐ๋Œ€ํ•˜๋Š” ๊ตฌ์กฐ) + // ๋ฐฐ์—ด ๊ตฌ์กฐ๋กœ ๋ฐ˜ํ™˜ - BuyOption์—์„œ productOptionInfos[0]์— ์ ‘๊ทผ + return [ + { + // ์˜ต์…˜ ID (๊ธฐ๋ณธ ๊ฐ’) + prodOptSno: `MOCK_OPT_${prdtId}_1`, + prodOptTpCdCval: 'BASIC', + + // ์˜ต์…˜ ์ƒ์„ธ ๋ชฉ๋ก (๊ธฐ๋ณธ๊ฐ’ 1๊ฐœ) - prdtOptDtl ๋ฐฐ์—ด ๊ตฌ์กฐ + prdtOptDtl: [optionDetail], + } + ]; }; /** diff --git a/com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js b/com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js new file mode 100644 index 00000000..4949ae67 --- /dev/null +++ b/com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js @@ -0,0 +1,326 @@ +/** + * Mock ๋ชจ๋“œ์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๋ถˆ์™„์ „ํ•  ๋•Œ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ + * ๋ชฉ์ : ํ‘œ์‹œ๋งŒ ํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์šฐํšŒ + */ + +/** + * ์ƒํ’ˆ ์ •๋ณด์˜ ์œ ํšจ์„ฑ ์ฒดํฌ - ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ + * @param {Object} product - ์ƒํ’ˆ ๊ฐ์ฒด + * @returns {boolean} ํ‘œ์‹œ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ + */ +export const isProductDisplayable = (product) => { + return product && (product.prdtNm || product.prdtId); +}; + +/** + * ๊ฐ€๊ฒฉ ์ •๋ณด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ถ”์ถœ (์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜) + * @param {Object} product - ์ƒํ’ˆ ๊ฐ์ฒด + * @returns {Object} { price, originalPrice, discount, currSign, currSignLoc } + */ +export const getSafeProductPrice = (product) => { + if (!product) { + return { + price: 0, + originalPrice: 0, + discount: 0, + currSign: '$', + currSignLoc: 'left', + }; + } + + // priceInfo๊ฐ€ ์žˆ์œผ๋ฉด ํŒŒ์‹ฑํ•˜์—ฌ ์‚ฌ์šฉ (BuyOption์—์„œ ์ „๋‹ฌ๋œ ์›๋ณธ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ) + if (product.priceInfo) { + const priceParts = product.priceInfo.split('|'); + if (priceParts.length >= 5) { + const originalPrice = parseFloat(priceParts[0]) || 0; + const price = parseFloat(priceParts[1]) || originalPrice; // ํ• ์ธ๊ฐ€๊ฒฉ + const discountRate = parseFloat(priceParts[2]) || 0; + const discountAmount = parseFloat(priceParts[3]) || 0; + const currSign = priceParts[4] || '$'; + + return { + price, + originalPrice, + discount: discountAmount > 0 ? discountAmount : Math.max(0, originalPrice - price), + currSign, + currSignLoc: 'left', // ๊ธฐ๋ณธ๊ฐ’ + }; + } + } + + // fallback: finalPrice, discountPrice ๋“ฑ ๊ธฐ์กด ๋ฐฉ์‹ + const price = parseFloat(product.finalPrice || product.discountPrice || 0); + const originalPrice = parseFloat(product.origPrice || product.originalPrice || price); + const discount = Math.max(0, originalPrice - price); + + return { + price, + originalPrice, + discount, + currSign: product.currSign || '$', + currSignLoc: product.currSignLoc || 'left', + }; +}; + +/** + * ์ƒํ’ˆ๋ช…์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ถ”์ถœ + * @param {Object} product - ์ƒํ’ˆ ๊ฐ์ฒด + * @returns {string} ์ƒํ’ˆ๋ช… ๋˜๋Š” ๊ธฐ๋ณธ๊ฐ’ + */ +export const getSafeProductName = (product) => { + return product?.prdtNm || product?.prdtId || 'Product'; +}; + +/** + * ํŒŒํŠธ๋„ˆ๋ช…์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ถ”์ถœ + * @param {Object} product - ์ƒํ’ˆ ๊ฐ์ฒด + * @returns {string} ํŒŒํŠธ๋„ˆ๋ช… ๋˜๋Š” ๊ธฐ๋ณธ๊ฐ’ + */ +export const getSafePartnerName = (product) => { + return product?.patncNm || product?.patnrId || 'Partner'; +}; + +/** + * ์ˆ˜๋Ÿ‰์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ถ”์ถœ + * @param {Object} product - ์ƒํ’ˆ ๊ฐ์ฒด + * @returns {number} ์ˆ˜๋Ÿ‰ + */ +export const getSafeQuantity = (product) => { + const qty = product?.prodQty; + return typeof qty === 'number' && qty > 0 ? qty : 1; +}; + +/** + * ๋ฐฐ์†ก๋ฃŒ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ถ”์ถœ + * @param {Object} product - ์ƒํ’ˆ ๊ฐ์ฒด + * @returns {number} ๋ฐฐ์†ก๋ฃŒ + */ +export const getSafeShippingCharge = (product) => { + const shipping = product?.shippingCharge; + return typeof shipping === 'number' ? shipping : 0; +}; + +/** + * ํŠน์ • ํ•„๋“œ๊ฐ€ 'N'์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”์ง€ ์ฒดํฌ (๋น„ํ™œ์„ฑํ™” ํ•ญ๋ชฉ) + * @param {string} value - ๊ฐ’ + * @returns {boolean} 'N'์ธ ๊ฒฝ์šฐ true + */ +export const isDisabled = (value) => { + return value === 'N' || value === false || !value; +}; + +/** + * ์˜ต์…˜ ์ •๋ณด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ถ”์ถœ + * @param {Object} product - ์ƒํ’ˆ ๊ฐ์ฒด + * @returns {Array} ์˜ต์…˜ ๋ฐฐ์—ด + */ +export const getSafeProductOptions = (product) => { + const options = product?.prdtOpt; + return Array.isArray(options) && options.length > 0 + ? options + : [{ prodOptCdCval: 'N/A', optNm: 'No Options' }]; +}; + +/** + * ์ด๋ฏธ์ง€ URL์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ถ”์ถœ + * @param {Object} 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]; + } + // 2์ˆœ์œ„: imgUrl ์ง์ ‘ ํ•„๋“œ + if (product?.imgUrl) { + return product.imgUrl; + } + // 3์ˆœ์œ„: thumbnailUrl (productInfo์˜ ์ธ๋„ค์ผ) + if (product?.thumbnailUrl) { + return product.thumbnailUrl; + } + // 4์ˆœ์œ„: thumbnailUrl960 (productInfo์˜ 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'; + } + // 6์ˆœ์œ„: patncLogPath (ํŒŒํŠธ๋„ˆ ๋กœ๊ณ ) + if (product?.patncLogPath) { + return product.patncLogPath; + } + // 7์ˆœ์œ„: ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ + return '/mock/image.jpg'; +}; + +/** + * ๊ฒฝ๋งค ์ƒํ’ˆ ์—ฌ๋ถ€ ์•ˆ์ „ ํ™•์ธ + * @param {Object} product - ์ƒํ’ˆ ๊ฐ์ฒด + * @returns {boolean} ๊ฒฝ๋งค ์ƒํ’ˆ ์—ฌ๋ถ€ + */ +export const isAuctionProduct = (product) => { + return product?.auctProdYn === 'Y'; +}; + +/** + * ์ƒํ’ˆ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œ์šฉ์œผ๋กœ ์ •๊ทœํ™” (๋ชจ๋“  ํ•„๋“œ๋ฅผ ์•ˆ์ „ํ•œ ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜) + * @param {Object} product - ์ƒํ’ˆ ๊ฐ์ฒด + * @returns {Object} ์ •๊ทœํ™”๋œ ์ƒํ’ˆ ๊ฐ์ฒด + */ +export const normalizeProductDataForDisplay = (product) => { + if (!isProductDisplayable(product)) { + // ํ‘œ์‹œ ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ๋นˆ ์ œํ’ˆ์œผ๋กœ ๋ฐ˜ํ™˜ + return { + prdtId: 'N/A', + prdtNm: 'Product', + patnrId: 'N/A', + patncNm: 'Partner', + prodQty: 1, + price: 0, + originalPrice: 0, + discount: 0, + currSign: '$', + currSignLoc: 'left', + shippingCharge: 0, + imgUrl: '/mock/image.jpg', + options: [], + auctProdYn: 'N', + isValid: false, + }; + } + + const priceInfo = getSafeProductPrice(product); + + return { + prdtId: product.prdtId || 'N/A', + prdtNm: getSafeProductName(product), + patnrId: product.patnrId || 'N/A', + patncNm: getSafePartnerName(product), + prodQty: getSafeQuantity(product), + price: priceInfo.price, + originalPrice: priceInfo.originalPrice, + discount: priceInfo.discount, + currSign: priceInfo.currSign, + currSignLoc: priceInfo.currSignLoc, + shippingCharge: getSafeShippingCharge(product), + imgUrl: getSafeImageUrl(product), + options: getSafeProductOptions(product), + auctProdYn: product.auctProdYn || 'N', + isValid: true, + }; +}; + +/** + * ๋ฐฐ์—ด์˜ ์ฒซ ๋ฒˆ์งธ ์ƒํ’ˆ์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ถ”์ถœ + * @param {Array} productArray - ์ƒํ’ˆ ๋ฐฐ์—ด + * @returns {Object} ์ฒซ ๋ฒˆ์งธ ์ƒํ’ˆ ๋˜๋Š” ๋นˆ ๊ฐ์ฒด + */ +export const getSafeFirstProduct = (productArray) => { + if (Array.isArray(productArray) && productArray.length > 0) { + return productArray[0]; + } + return null; +}; + +/** + * ํ†ตํ™” ๊ธฐํ˜ธ ์ •๋ณด ์ถ”์ถœ + * @param {Object} product - ์ƒํ’ˆ ๊ฐ์ฒด + * @returns {Object} { currSign, currSignLoc } + */ +export const getSafeCurrencyInfo = (product) => { + return { + currSign: product?.currSign || '$', + currSignLoc: product?.currSignLoc || 'left', + }; +}; + +/** + * Mock Mode: productInfo์—์„œ ๊ฐ€๊ฒฉ ์ •๋ณด๋ฅผ ์ถ”์ถœํ•˜์—ฌ ORDER SUMMARY์šฉ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ + * @param {Object} productInfo - ์›๋ณธ productInfo ๊ฐ์ฒด + * @returns {Object} ORDER SUMMARY์šฉ ๊ฐ€๊ฒฉ ๋ฐ์ดํ„ฐ + */ +export const calculateOrderSummaryFromProductInfo = (productInfo) => { + console.log('[calculateOrderSummaryFromProductInfo] Input productInfo:', productInfo); + + if (!productInfo) { + console.log('[calculateOrderSummaryFromProductInfo] No productInfo, using defaults'); + return { + items: 0, + couponSavings: 0, + shipping: 0, + subtotal: 0, + tax: 0, + total: 0, + currency: { currSign: '$', currSignLoc: 'left' } + }; + } + + // 1. Items (์ƒํ’ˆ ๊ฐ€๊ฒฉ) - price2 ์‚ฌ์šฉ + const itemsPrice = parseFloat( + productInfo.price2?.replace(/[^\d.]/g, '') || + productInfo.finalPrice || + 0 + ); + + // 2. Your Coupon Savings (์ฟ ํฐ ํ• ์ธ) - price5๋ฅผ ํ• ์ธ์•ก์œผ๋กœ ์‚ฌ์šฉ + const couponSavings = parseFloat( + productInfo.price5?.replace(/[^\d.]/g, '') || + productInfo.discount || + 0 + ); + + // 3. Shipping & Handling (๋ฐฐ์†ก๋น„) - shippingCharge ์‚ฌ์šฉ + const shippingCharge = parseFloat( + productInfo.shippingCharge?.replace(/[^\d.]/g, '') || + 0 + ); + + // 4. Subtotal (์„ธ์ „ ์ด๊ณ„) = itemsPrice - couponSavings + shippingCharge + const subtotal = Math.max(0, itemsPrice - couponSavings + shippingCharge); + + // 5. Tax (์„ธ๊ธˆ) = subtotal์˜ 10% + const tax = Math.round((subtotal * 0.1) * 100) / 100; + + // 6. Total (์ด๊ณ„) = subtotal + tax + const total = Math.round((subtotal + tax) * 100) / 100; + + // ํ†ตํ™” ์ •๋ณด + const currency = { + currSign: productInfo.currSign || '$', + currSignLoc: productInfo.currSignLoc || 'left' + }; + + const result = { + items: itemsPrice, + couponSavings: couponSavings, + shipping: shippingCharge, + subtotal: subtotal, + tax: tax, + total: total, + currency: currency + }; + + console.log('[calculateOrderSummaryFromProductInfo] Calculated result:', result); + return result; +}; + +export default { + isProductDisplayable, + getSafeProductPrice, + getSafeProductName, + getSafePartnerName, + getSafeQuantity, + getSafeShippingCharge, + isDisabled, + getSafeProductOptions, + getSafeImageUrl, + isAuctionProduct, + normalizeProductDataForDisplay, + getSafeFirstProduct, + getSafeCurrencyInfo, + calculateOrderSummaryFromProductInfo, +}; diff --git a/com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx b/com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx index dca28175..b47d81ce 100644 --- a/com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx +++ b/com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx @@ -30,6 +30,11 @@ import * as Config from '../../utils/Config'; import { $L, scaleH, scaleW } from '../../utils/helperMethods'; import { SpotlightIds } from '../../utils/SpotlightIds'; import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig'; +import { + normalizeProductDataForDisplay, + getSafeFirstProduct, + getSafeCurrencyInfo, +} from '../../utils/mockDataSafetyUtils'; import css from './CheckOutPanel.module.less'; import PinCodeInput from './components/PinCodeInput'; import FixedSideBar from './container/FixedSideBar'; @@ -57,30 +62,58 @@ export default function CheckOutPanel({ panelInfo }) { const { popupVisible, activePopup } = useSelector((state) => state.common.popup); const popup = useSelector((state) => state.common.popup); - // Mock Mode: ๊ฐ€์งœ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ + // Mock Mode: panelInfo.mockProductInfo ๋˜๋Š” Redux์—์„œ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ const productData = BUYNOW_CONFIG.isMockMode() - ? reduxProductData || [ - { - prdtId: 'MOCK_PRODUCT_1', - prdtNm: 'Mock Product for Demonstration', - patnrId: '1', - patncNm: 'Mock Partner', - prodSno: 'MOCK_123', - prodQty: 1, - finalPrice: 99999, - origPrice: 99999, - discountPrice: 99999, - currSign: '$', - currSignLoc: 'left', - }, - ] + ? (() => { + // 1์ˆœ์œ„: BuyOption์—์„œ ์ „๋‹ฌ๋œ productInfo + if (panelInfo?.productInfo) { + console.log('[CheckOutPanel] Using panelInfo.productInfo:', panelInfo.productInfo); + return [panelInfo.productInfo]; + } + // 2์ˆœ์œ„: Redux์—์„œ ๊ฐ€์ ธ์˜จ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ + if (reduxProductData && reduxProductData.length > 0) { + console.log('[CheckOutPanel] Using reduxProductData:', reduxProductData); + return reduxProductData; + } + // 3์ˆœ์œ„: ๊ธฐ๋ณธ Hardcoded Mock ๋ฐ์ดํ„ฐ + console.log('[CheckOutPanel] Using default hardcoded mock data'); + return [ + { + prdtId: 'MOCK_PRODUCT_1', + prdtNm: 'Mock Product', + patnrId: '1', + patncNm: 'Partner', + prodQty: 1, + finalPrice: 99999, + origPrice: 99999, + discountPrice: 99999, + currSign: '$', + currSignLoc: 'left', + }, + ]; + })() : reduxProductData; console.log('[CheckOutPanel] isMockMode:', BUYNOW_CONFIG.isMockMode()); + console.log('[CheckOutPanel] panelInfo:', panelInfo); console.log('[CheckOutPanel] reduxProductData:', reduxProductData); - console.log('[CheckOutPanel] productData:', productData); + console.log('[CheckOutPanel] productData (raw):', productData); + console.log('[BuyOption][CheckOutPanel] ์ƒํ’ˆ ์ •๋ณด:', productData); - const { currSign, currSignLoc } = productData?.[0] || {}; + // ํ‘œ์‹œ์šฉ์œผ๋กœ ๋ชจ๋“  ์ƒํ’ˆ ๋ฐ์ดํ„ฐ ์ •๊ทœํ™” (์—†๋Š” ํ•„๋“œ๋Š” ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ) + // Mock ๋ชจ๋“œ์—์„œ๋Š” ํ•ญ์ƒ ์ •๊ทœํ™”, API ๋ชจ๋“œ์—์„œ๋Š” ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ + const normalizedProductData = productData?.map((prod) => normalizeProductDataForDisplay(prod)) || []; + const safeProductData = BUYNOW_CONFIG.isMockMode() ? normalizedProductData : productData; + + console.log('[CheckOutPanel] productData (normalized):', normalizedProductData); + console.log('[CheckOutPanel] safeProductData (final):', safeProductData); + + // ์ฒซ ๋ฒˆ์งธ ์ƒํ’ˆ ์ •๋ณด๋กœ ํ†ตํ™” ์ •๋ณด ์ถ”์ถœ + const firstProduct = getSafeFirstProduct(safeProductData); + const { currSign, currSignLoc } = firstProduct + ? getSafeCurrencyInfo(firstProduct) + : { currSign: '$', currSignLoc: 'left' }; + console.log('[CheckOutPanel] firstProduct:', firstProduct); console.log('[CheckOutPanel] currSign:', currSign, 'currSignLoc:', currSignLoc); const [orderSideBarOpen, setOrderSideBarOpen] = useState(false); @@ -218,7 +251,12 @@ export default function CheckOutPanel({ panelInfo }) { console.log('[CheckOutPanel] cleanup useEffect - setting up cleanup'); return () => { console.log('[CheckOutPanel] cleanup useEffect - calling resetCheckoutData'); - dispatch(resetCheckoutData()); + // Mock ๋ชจ๋“œ์ผ ๋•Œ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•ด์•ผ ๋‹ค์‹œ ์ง„์ž…ํ–ˆ์„ ๋•Œ ์˜ฌ๋ฐ”๋ฅธ ์ƒํ’ˆ ์ •๋ณด ๋กœ๋“œ ๊ฐ€๋Šฅ + if (!BUYNOW_CONFIG.isMockMode()) { + dispatch(resetCheckoutData()); + } else { + console.log('[CheckOutPanel] Mock Mode - Skipping resetCheckoutData to preserve product data'); + } }; }, [dispatch]); @@ -414,6 +452,9 @@ export default function CheckOutPanel({ panelInfo }) { currSign={currSign} currSignLoc={currSignLoc} doSendLogPaymentEntry={doSendLogPaymentEntry} + productData={safeProductData} + productInfo={panelInfo?.productInfo} + defaultPrice={panelInfo?.defaultPrice} /> ) : ( - {orderSideBarOpen && } + {orderSideBarOpen && } {offerSideBarOpen && ( )} @@ -498,7 +539,7 @@ export default function CheckOutPanel({ panelInfo }) { )} - {/* + {/* {activePopup === Config.ACTIVE_POPUP.errorPopup && ( state.checkout?.checkoutData?.productList); console.log('[CheckOutPanel] OrderItemsSideBar reduxOrderItemList:', reduxOrderItemList); + console.log('[CheckOutPanel] OrderItemsSideBar productData:', productData); + console.log('[CheckOutPanel] OrderItemsSideBar productInfo:', productInfo); // Check if reduxOrderItemList has actual data const hasValidOrderItemList = Array.isArray(reduxOrderItemList) && reduxOrderItemList.length > 0; @@ -28,22 +34,75 @@ export default function OrderItemsSideBar({ closeSideBar }) { const orderItemList = hasValidOrderItemList ? reduxOrderItemList : BUYNOW_CONFIG.isMockMode() - ? [ - { - prdtId: 'MOCK_PRODUCT_1', - prdtNm: 'Mock Product', - prodQty: 1, - prdtOpt: [{ prodOptCdCval: 'MOCK_OPT_1', optNm: 'Mock Option' }], - patncLogPath: '/mock/image.jpg', - expsPrdtNo: 'MOCK_EXP_1', - currSign: '$', - currSignLoc: 'left', - shippingCharge: 0, - auctProdYn: 'N', - auctFinalPriceChgDt: null, - imgUrls: [{ imgUrl: '/mock/image.jpg' }], - }, - ] + ? (() => { + // Mock Mode: productInfo๊ฐ€ ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ + if (productInfo) { + console.log('[CheckOutPanel] OrderItemsSideBar Using productInfo for image'); + const normalized = normalizeProductDataForDisplay(productInfo); + console.log('[CheckOutPanel] OrderItemsSideBar productInfo normalized imgUrl:', normalized.imgUrl); + + return [{ + prdtId: normalized.prdtId, + prdtNm: normalized.prdtNm, + prodQty: normalized.prodQty, + prdtOpt: normalized.options || [{ prodOptCdCval: 'DEFAULT_OPT', optNm: 'Default Option' }], + patncLogPath: normalized.imgUrl, + expsPrdtNo: normalized.prdtId, + currSign: normalized.currSign, + currSignLoc: normalized.currSignLoc, + shippingCharge: normalized.shippingCharge || 0, + auctProdYn: normalized.auctProdYn || 'N', + auctFinalPriceChgDt: null, + imgUrls: [{ imgUrl: normalized.imgUrl }], // productInfo์—์„œ ์ถ”์ถœํ•œ ์‹ค์ œ ์ด๋ฏธ์ง€ + // ํ‘œ์‹œ์šฉ ์ถ”๊ฐ€ ํ•„๋“œ + price: normalized.price, + discount: normalized.discount, + }]; + } + // productInfo๊ฐ€ ์—†์œผ๋ฉด productData ์‚ฌ์šฉ + else if (productData && productData.length > 0) { + console.log('[CheckOutPanel] OrderItemsSideBar Using productData (fallback)'); + return productData.map((prod) => { + const normalized = normalizeProductDataForDisplay(prod); + console.log('[CheckOutPanel] OrderItemsSideBar productData normalized imgUrl:', normalized.imgUrl); + return { + prdtId: normalized.prdtId, + prdtNm: normalized.prdtNm, + prodQty: normalized.prodQty, + prdtOpt: normalized.options || [{ prodOptCdCval: 'MOCK_OPT_1', optNm: 'Selected Option' }], + patncLogPath: normalized.imgUrl, + expsPrdtNo: normalized.prdtId, + currSign: normalized.currSign, + currSignLoc: normalized.currSignLoc, + shippingCharge: normalized.shippingCharge || 0, + auctProdYn: normalized.auctProdYn || 'N', + auctFinalPriceChgDt: null, + imgUrls: [{ imgUrl: normalized.imgUrl }], // ์ด๋ฏธ์ง€ URL ์ถ”๊ฐ€ + // ํ‘œ์‹œ์šฉ ์ถ”๊ฐ€ ํ•„๋“œ + price: normalized.price, + discount: normalized.discount, + }; + }); + } + // ๋‘˜ ๋‹ค ์—†์œผ๋ฉด ๊ธฐ๋ณธ Mock ๋ฐ์ดํ„ฐ + else { + console.log('[CheckOutPanel] OrderItemsSideBar Using default mock data'); + return [{ + prdtId: 'MOCK_PRODUCT_1', + prdtNm: 'Mock Product', + prodQty: 1, + prdtOpt: [{ prodOptCdCval: 'MOCK_OPT_1', optNm: 'Mock Option' }], + patncLogPath: '/mock/image.jpg', + expsPrdtNo: 'MOCK_EXP_1', + currSign: '$', + currSignLoc: 'left', + shippingCharge: 0, + auctProdYn: 'N', + auctFinalPriceChgDt: null, + imgUrls: [{ imgUrl: '/mock/image.jpg' }], + }]; + } + })() : null; console.log('[CheckOutPanel] OrderItemsSideBar effectiveOrderItemList:', orderItemList); diff --git a/com.twin.app.shoptime/src/views/CheckOutPanel/container/SummaryContainerMock.jsx b/com.twin.app.shoptime/src/views/CheckOutPanel/container/SummaryContainerMock.jsx index 41df0207..a5af1ff1 100644 --- a/com.twin.app.shoptime/src/views/CheckOutPanel/container/SummaryContainerMock.jsx +++ b/com.twin.app.shoptime/src/views/CheckOutPanel/container/SummaryContainerMock.jsx @@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import TButton from '../../../components/TButton/TButton'; import * as Config from '../../../utils/Config'; import { $L, formatCurrencyValue } from '../../../utils/helperMethods'; +import { getSafeFirstProduct, calculateOrderSummaryFromProductInfo } from '../../../utils/mockDataSafetyUtils'; import css from './SummaryContainer.module.less'; export default function SummaryContainerMock({ @@ -12,21 +13,54 @@ export default function SummaryContainerMock({ currSign, currSignLoc, doSendLogPaymentEntry, + productData, + productInfo, + defaultPrice, }) { console.log('[CheckOutPanel] SummaryContainerMock - START render'); console.log('[CheckOutPanel] SummaryContainerMock - empTermsData:', empTermsData); console.log('[CheckOutPanel] SummaryContainerMock - currSign:', currSign); + console.log('[CheckOutPanel] SummaryContainerMock - productData:', productData); + console.log('[CheckOutPanel] SummaryContainerMock - productInfo:', productInfo); - // Mock Mode: ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๊ฐ€๊ฒฉ ๋ฐ์ดํ„ฐ + // Mock Mode: productInfo๋กœ๋ถ€ํ„ฐ ORDER SUMMARY์šฉ ๊ฐ€๊ฒฉ ๋ฐ์ดํ„ฐ ๊ณ„์‚ฐ + const orderSummaryData = useMemo(() => { + if (productInfo) { + // productInfo๊ฐ€ ์žˆ์œผ๋ฉด ์ง์ ‘ ๊ณ„์‚ฐ + return calculateOrderSummaryFromProductInfo(productInfo); + } else { + // productInfo๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ์กด ๋ฐฉ์‹์œผ๋กœ fallback + const selectedProduct = getSafeFirstProduct(productData); + const productPrice = parseFloat(defaultPrice || selectedProduct?.price || 0); + const productDiscount = parseFloat(selectedProduct?.discount || 0); + const tax = Math.round((productPrice * 0.1) * 100) / 100; + + return { + items: productPrice, + couponSavings: productDiscount, + shipping: 0, + subtotal: Math.max(0, productPrice - productDiscount), + tax: tax, + total: Math.max(0, productPrice - productDiscount) + tax, + currency: { currSign, currSignLoc } + }; + } + }, [productInfo, productData, defaultPrice, currSign, currSignLoc]); + + console.log('[CheckOutPanel] SummaryContainerMock - orderSummaryData:', orderSummaryData); + + // ๊ธฐ์กด ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด effectivePriceTotalData ์œ ์ง€ const effectivePriceTotalData = { - totProdPrc: 521.66, - totDcAmt: 0, - totDlvrAmt: 0, - ordPmtNoTaxAmt: 521.66, - ordTotTaxAmt: 50, - ordPmtReqAmt: 571.66, + totProdPrc: orderSummaryData.items, + totDcAmt: orderSummaryData.couponSavings, + totDlvrAmt: orderSummaryData.shipping, + ordPmtNoTaxAmt: orderSummaryData.subtotal, + ordTotTaxAmt: orderSummaryData.tax, + ordPmtReqAmt: orderSummaryData.total, }; + console.log('[CheckOutPanel] SummaryContainerMock - effectivePriceTotalData:', effectivePriceTotalData); + // Mock Mode: ๊ธฐ๋ณธ ์ƒํ’ˆ ์ •๋ณด const productList = { auctProdYn: 'N', @@ -37,38 +71,38 @@ export default function SummaryContainerMock({ () => [ { name: 'Items', - value: formatCurrencyValue(effectivePriceTotalData.totProdPrc, currSign, currSignLoc), + value: formatCurrencyValue(effectivePriceTotalData.totProdPrc, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc), }, { name: 'Your Coupon Savings', value: effectivePriceTotalData.totDcAmt - ? formatCurrencyValue(effectivePriceTotalData.totDcAmt, currSign, currSignLoc, true) + ? formatCurrencyValue(effectivePriceTotalData.totDcAmt, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc, true) : '-', }, { name: 'Shipping & Handling', - value: formatCurrencyValue(effectivePriceTotalData.totDlvrAmt, currSign, currSignLoc), + value: formatCurrencyValue(effectivePriceTotalData.totDlvrAmt, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc), }, { name: 'TOTAL (before Tax)', - value: formatCurrencyValue(effectivePriceTotalData.ordPmtNoTaxAmt, currSign, currSignLoc), + value: formatCurrencyValue(effectivePriceTotalData.ordPmtNoTaxAmt, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc), }, { name: 'Estimated Sales Tax', - value: formatCurrencyValue(effectivePriceTotalData.ordTotTaxAmt, currSign, currSignLoc), + value: formatCurrencyValue(effectivePriceTotalData.ordTotTaxAmt, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc), }, ], - [effectivePriceTotalData, currSign, currSignLoc] + [effectivePriceTotalData, orderSummaryData.currency] ); const handleClickOrder = useCallback(() => { console.log('[SummaryContainerMock] Place order clicked'); - if (doSendLogPaymentEntry) { - doSendLogPaymentEntry(); - } + doSendLogPaymentEntry(); setPlaceOrderPopup(true); }, [doSendLogPaymentEntry, setPlaceOrderPopup]); + console.log('[CheckOutPanel] SummaryContainerMock - items:', items); + const renderItemList = useCallback( () => items.map((item, index) => ( @@ -100,8 +134,8 @@ export default function SummaryContainerMock({ const estimatedTotal = useMemo(() => { console.log('[CheckOutPanel] SummaryContainerMock - estimatedTotal useMemo'); - return formatCurrencyValue(effectivePriceTotalData.ordPmtReqAmt, currSign, currSignLoc); - }, [effectivePriceTotalData, currSign, currSignLoc]); + return formatCurrencyValue(effectivePriceTotalData.ordPmtReqAmt, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc); + }, [effectivePriceTotalData, orderSummaryData.currency]); const showAuctionNotice = productList?.auctProdYn === 'Y' && !productList.auctFinalPriceChgDt; diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less index 4852ae2e..8ae7be0c 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less @@ -533,6 +533,25 @@ // ๋ฐฐ๊ฒฝ์ƒ‰๊ณผ ๋ผ์šด๋“œ๋Š” FavoriteBtn ๋‚ด๋ถ€์—์„œ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ ์ œ๊ฑฐ } +// FavoriteBtn ์ปดํฌ๋„ŒํŠธ์— ์ ์šฉํ•  ์Šคํƒ€์ผ +.favoriteBtn { + width: 60px !important; + height: 60px !important; + background: rgba(68, 68, 68, 0.5) !important; + border-radius: 6px !important; + border: none !important; + padding: 0 !important; + margin: 0 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + + &:focus { + background: @PRIMARY_COLOR_RED !important; + // outline์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ + } +} + // ์•ก์…˜ ๋ฒ„ํŠผ๋“ค (actionButtons ์ฐธ๊ณ ) .actionButtonsWrapper { width: 100%; 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 3931eb74..fcdac5ac 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx @@ -110,23 +110,30 @@ const BuyOption = ({ // Mock Mode์—์„œ ์˜ต์…˜ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ const productOptionInfos = useMemo(() => { + console.log('[BuyOption] productOptionInfos useMemo - isMockMode:', BUYNOW_CONFIG.isMockMode()); + console.log('[BuyOption] productOptionInfos useMemo - reduxProductOptionInfos:', reduxProductOptionInfos); + // API Mode: ๊ธฐ์กด ๋กœ์ง 100% ์œ ์ง€ if (!BUYNOW_CONFIG.isMockMode()) { + console.log('[BuyOption] API Mode - using reduxProductOptionInfos'); return reduxProductOptionInfos; } - // Mock Mode: ์˜ต์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด Mock ๋ฐ์ดํ„ฐ ์ƒ์„ฑ - if (!reduxProductOptionInfos || reduxProductOptionInfos.length === 0) { + // Mock Mode: ์˜ต์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๋ฐฐ์—ด์ด ์•„๋‹ˆ๋ฉด Mock ๋ฐ์ดํ„ฐ ์ƒ์„ฑ + const isValidReduxData = Array.isArray(reduxProductOptionInfos) && reduxProductOptionInfos.length > 0; + + if (!isValidReduxData) { + console.log('[BuyOption] Mock Mode - generating mock option data (reduxData invalid)'); const mockOptionData = createMockProductOptionData(productData); - // Mock ์˜ต์…˜ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด ๋ฐ˜ํ™˜ (๊ธฐ์กด ๊ตฌ์กฐ์™€ ํ˜ธํ™˜) - return mockOptionData?.optionList || []; + console.log('[BuyOption] Mock Mode - createMockProductOptionData result:', mockOptionData); + // Mock ์˜ต์…˜ ๋ฐ์ดํ„ฐ๋Š” ์ด๋ฏธ ๋ฐฐ์—ด ๊ตฌ์กฐ๋กœ ๋ฐ˜ํ™˜๋จ + return mockOptionData || []; } - // Mock Mode์ด๊ณ  ์˜ต์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ + // Mock Mode์ด๊ณ  ์œ ํšจํ•œ ์˜ต์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ + console.log('[BuyOption] Mock Mode - using existing valid reduxProductOptionInfos'); return reduxProductOptionInfos; - }, [reduxProductOptionInfos, productData]); - - // logInfo ์ƒ์„ฑ (SingleOption๊ณผ ๋™์ผํ•œ ๋กœ์ง, productData ์šฐ์„  ์‚ฌ์šฉ) + }, [reduxProductOptionInfos, productData]); // logInfo ์ƒ์„ฑ (SingleOption๊ณผ ๋™์ผํ•œ ๋กœ์ง, productData ์šฐ์„  ์‚ฌ์šฉ) const logInfo = useMemo(() => { if (productData) { // productData๊ฐ€ ์žˆ์œผ๋ฉด SingleOption๊ณผ ๋™์ผํ•˜๊ฒŒ ์ฒ˜๋ฆฌ @@ -222,17 +229,50 @@ const BuyOption = ({ }, [selectedIndex, productOptionInfos, type]); // ์˜ต์…˜ ์ž๋™ ์„ ํƒ ๋กœ์ง (SingleOption๊ณผ ๋™์ผ) + // Mock Mode: ํ•ญ์ƒ ์ฒซ ๋ฒˆ์งธ ์˜ต์…˜์„ ์ž๋™์œผ๋กœ ์„ ํƒ useEffect(() => { - if ( - productOptionInfos && - selectedBtnOptIdx >= 0 && - productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length === 1 && - !isOptionValue - ) { - setSelectedOptions(productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl[0]); + console.log('[BuyOption] autoSelect useEffect - productOptionInfos:', productOptionInfos); + console.log('[BuyOption] autoSelect useEffect - selectedBtnOptIdx:', selectedBtnOptIdx); + console.log('[BuyOption] autoSelect useEffect - isOptionValue:', isOptionValue); + + if (!productOptionInfos || productOptionInfos.length === 0) { + console.log('[BuyOption] autoSelect - productOptionInfos is empty, returning'); + return; + } + + const currentOptionGroup = productOptionInfos[selectedBtnOptIdx]; + if (!currentOptionGroup) { + console.log('[BuyOption] autoSelect - currentOptionGroup is not found at index:', selectedBtnOptIdx); + return; + } + + const optionDetails = currentOptionGroup.prdtOptDtl; + console.log('[BuyOption] autoSelect - optionDetails:', optionDetails); + + if (!optionDetails || optionDetails.length === 0) { + console.log('[BuyOption] autoSelect - optionDetails is empty'); + return; + } + + // ์ด๋ฏธ ์„ ํƒ๋˜์—ˆ์œผ๋ฉด ์Šคํ‚ต + if (isOptionValue) { + console.log('[BuyOption] autoSelect - already selected, skipping'); + return; + } + + // Mock Mode: ์ฒซ ๋ฒˆ์งธ ์˜ต์…˜ ์ž๋™ ์„ ํƒ + if (BUYNOW_CONFIG.isMockMode()) { + console.log('[BuyOption] Mock Mode - Auto selecting first option:', optionDetails[0]); + setSelectedOptions(optionDetails[0]); setIsOptionValue(true); } - }, [productOptionInfos, selectedBtnOptIdx, isOptionValue]); + // API Mode: ์˜ต์…˜์ด 1๊ฐœ์ผ ๋•Œ๋งŒ ์ž๋™ ์„ ํƒ + else if (optionDetails.length === 1) { + console.log('[BuyOption] API Mode - Auto selecting only option:', optionDetails[0]); + setSelectedOptions(optionDetails[0]); + setIsOptionValue(true); + } + }, [productOptionInfos, selectedBtnOptIdx]); // ํ•„์ˆ˜ ๋ฐ์ดํ„ฐ ๋กœ๋“œ (SingleOption๊ณผ ๋™์ผ) // Mock Mode: API ํ˜ธ์ถœ ์Šคํ‚ต (Mock ๋ฐ์ดํ„ฐ๋งŒ ์‚ฌ์šฉ) @@ -320,17 +360,17 @@ const BuyOption = ({ } } else if (response.retCode === 1001) { dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup)); - dispatch(changeAppStatus({ isLoading: false })); - } else { + dispatch(changeAppStatus({ isLoading: false })); + } else { dispatch( showError( response.retCode, - response.retMsg, - false, + response.retMsg, + false, response.retDetailCode, response.returnBindStrings ) - ); + ); dispatch(changeAppStatus({ isLoading: false })); return; } @@ -369,18 +409,18 @@ const BuyOption = ({ productOptionInfos.length > 1 && productInfo?.optProdYn === 'Y' ) { - + if (selectFirstOptionIndex === 0) { dispatch( - showError( + showError( null, "PLEASE SELECT OPTION", false, null, null ) - ); - + ); + return; } } @@ -389,18 +429,18 @@ const BuyOption = ({ productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1 && productInfo?.optProdYn === 'Y' ) { - + if (selectSecondOptionIndex === 0) { dispatch( - showError( + showError( null, "PLEASE SELECT OPTION", false, null, null ) - ); - + ); + return; } } @@ -408,10 +448,27 @@ const BuyOption = ({ if (userNumber && selectedPatnrId && selectedPrdtId && quantity) { const { prodOptCval, priceInfo } = selectedOptions || {}; const { patncNm, brndNm, catNm, prdtNm, prdtId } = productInfo; - const regularPrice = priceInfo?.split('|')[0]; - const discountPrice = priceInfo?.split('|')[1]; + console.log('[BuyOption] handleClickOrder - selectedOptions:', selectedOptions); + console.log('[BuyOption] handleClickOrder - priceInfo:', priceInfo); + console.log('[BuyOption] handleClickOrder - logInfo:', logInfo); + + // priceInfo ํŒŒ์‹ฑ ๋ฐ ์ˆซ์ž ๋ณ€ํ™˜ + let regularPrice = parseInt(priceInfo?.split('|')[0], 10) || 0; + let discountPrice = parseInt(priceInfo?.split('|')[1], 10) || 0; const discountRate = priceInfo?.split('|')[4]; + // selectedOptions๊ฐ€ ์—†์œผ๋ฉด logInfo์—์„œ ๊ฐ€๊ฒฉ ์ถ”์ถœ + if (!selectedOptions && logInfo) { + console.log('[BuyOption] handleClickOrder - selectedOptions is undefined, using logInfo prices'); + // logInfo์˜ dcBefPrc์™€ dcAftrPrc๋Š” "$ 521.66" ํ˜•์‹์ด๋ฏ€๋กœ ์ˆซ์ž๋งŒ ์ถ”์ถœ (์†Œ์ˆ˜์  ํฌํ•จ) + const dcBefPrcMatch = logInfo.dcBefPrc?.match(/[\d.]+/); + const dcAftrPrcMatch = logInfo.dcAftrPrc?.match(/[\d.]+/); + regularPrice = dcBefPrcMatch ? parseFloat(dcBefPrcMatch[0]) : 0; + discountPrice = dcAftrPrcMatch ? parseFloat(dcAftrPrcMatch[0]) : regularPrice; + console.log('[BuyOption] handleClickOrder - extracted from logInfo - dcBefPrc:', logInfo.dcBefPrc, 'dcAftrPrc:', logInfo.dcAftrPrc); + } + console.log('[BuyOption] handleClickOrder - regularPrice:', regularPrice, 'discountPrice:', discountPrice, 'discountRate:', discountRate); + dispatch( sendLogTotalRecommend({ nowMenu: nowMenu, @@ -452,13 +509,44 @@ const BuyOption = ({ ); } else { // Mock Mode: ์ฒดํฌ์•„์›ƒ ํŽ˜์ด์ง€๋กœ ์ด๋™ (์‹œ๋ฎฌ๋ ˆ์ด์…˜) + // panelInfo์˜ logInfo์— ์„ ํƒํ•œ ์ƒํ’ˆ ์ •๋ณด๋ฅผ ํฌํ•จ์‹œ์ผœ CheckOutPanel์—์„œ ์‚ฌ์šฉ console.log('[BuyOption] Mock Mode - Simulating checkout'); console.log('[BuyOption] logInfo:', logInfo); console.log('[BuyOption] Dispatching pushPanel to CHECKOUT_PANEL'); dispatch(finishVideoPreview()); dispatch(finishMediaPreview()); - const checkoutPanelInfo = { logInfo: { ...logInfo, cartTpSno: `MOCK_${Date.now()}` } }; - console.log('[BuyOption] checkoutPanelInfo:', checkoutPanelInfo); + + // Mock ๋ชจ๋“œ: ์„ ํƒ ์ƒํ’ˆ์˜ ์ •๋ณด๋ฅผ panelInfo์— ๋‹ด์•„์„œ ์ „๋‹ฌ + // CheckOutPanel์—์„œ ์ด ์ •๋ณด๋กœ Mock ์ƒํ’ˆ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ + // ์ด๋ฏธ์ง€ URL ์ถ”์ถœ (productInfo์˜ imgList ๋˜๋Š” thumbnailUrl ์‚ฌ์šฉ) + const imgUrl = productInfo?.imgList?.[0]?.imgUrl || + productInfo?.thumbnailUrl || + productInfo?.patncLogPath || + '/mock/image.jpg'; + + const mockProductInfo = { + prdtId: selectedPrdtId, + prdtNm: productInfo?.prdtNm, + patnrId: selectedPatnrId, + patncNm: patncNm, + prodQty: quantity, + origPrice: regularPrice || 99999, // ์›๋ž˜ ๊ฐ€๊ฒฉ (์ˆซ์ž) + discountPrice: discountPrice || regularPrice || 99999, // ํ• ์ธ๋œ ๊ฐ€๊ฒฉ (์‹ค์ œ ํŒ๋งค ๊ฐ€๊ฒฉ, ์ˆซ์ž) + finalPrice: discountPrice || regularPrice || 99999, // ์ตœ์ข… ๊ฐ€๊ฒฉ (์ˆซ์ž) + currSign: '$', + currSignLoc: 'left', + imgUrl: imgUrl, // ์ƒํ’ˆ ์ด๋ฏธ์ง€ URL ์ถ”๊ฐ€ + }; + + const checkoutPanelInfo = { + logInfo: { ...logInfo, cartTpSno: `MOCK_${Date.now()}` }, + productInfo: productInfo, + defaultPrice: discountPrice, + }; + + console.log('[BuyOption] Mock Mode - mockProductInfo:', mockProductInfo); + console.log('[BuyOption] Mock Mode - regularPrice(์ˆซ์ž):', regularPrice, 'discountPrice(์ˆซ์ž):', discountPrice); + console.log('[BuyOption] Mock Mode - checkoutPanelInfo:', checkoutPanelInfo); dispatch( pushPanel({ name: Config.panel_names.CHECKOUT_PANEL, @@ -673,6 +761,11 @@ const BuyOption = ({ setFavoriteFlag(ev); }, []); + // ๊ตฌ๋งค์ฐฝ์ด ๋œฐ ๋•Œ ์ฝ˜์†” ๋กœ๊ทธ ์ถœ๋ ฅ (์ƒํ’ˆ ์ •๋ณด ํฌํ•จ ํƒœ๊ทธ [BuyOption] ๋ถ™์ž„) + useEffect(() => { + console.log('[BuyOption]', '์ƒํ’ˆ ์ •๋ณด:', JSON.stringify(productInfo)); + }, []); // ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ํ•œ ๋ฒˆ๋งŒ ์ถœ๋ ฅ + // hasOnClose ๋กœ์ง (SingleOption๊ณผ ๋™์ผ) const hasOnClose = useMemo(() => { if (productOptionInfos && productOptionInfos.length > 0) { diff --git a/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx b/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx index 6575276b..15517e80 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx @@ -10,6 +10,7 @@ import { } from 'react-redux'; import Spotlight from '@enact/spotlight'; +import Spottable from '@enact/spotlight/Spottable'; import { setHidePopup, @@ -28,6 +29,8 @@ import * as Config from '../../../utils/Config'; import { $L } from '../../../utils/helperMethods'; import css from './FavoriteBtn.module.less'; +const SpottableDiv = Spottable('div'); + export default function FavoriteBtn({ selectedPatnrId, selectedPrdtId, @@ -80,7 +83,7 @@ export default function FavoriteBtn({ }, [dispatch, favoriteFlag, onFavoriteFlagChanged]); return ( -
)} -
+ ); } diff --git a/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.module.less b/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.module.less index f77e7866..c881fb34 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.module.less +++ b/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.module.less @@ -22,8 +22,7 @@ .favorUnableBtn { min-width: 60px; height: 60px; - background-image: url(../../../../assets/images/icons/ic-heart-nor@3x.png); - background-color: #4f172c; // ์ƒ‰์ƒ ์ถ”๊ฐ€. + background-color: rgba(68, 68, 68, 0.5); // ๋‹ค๋ฅธ ๋ฒ„ํŠผ๋“ค๊ณผ ๋™์ผํ•œ ๋ฐฐ๊ฒฝ์ƒ‰ .imgElement(54px, 54px, center, center); } } diff --git a/com.twin.app.shoptime/src/views/ErrorBoundary.js b/com.twin.app.shoptime/src/views/ErrorBoundary.js index d9e7e22e..2f71f411 100644 --- a/com.twin.app.shoptime/src/views/ErrorBoundary.js +++ b/com.twin.app.shoptime/src/views/ErrorBoundary.js @@ -10,7 +10,12 @@ const DEBUG_MODE = true; class ErrorBoundary extends Component { constructor(props) { super(props); - this.state = { hasError: false }; + this.state = { + hasError: false, + error: null, + errorInfo: null, + errorTimestamp: null, + }; } static getDerivedStateFromError(error) { @@ -18,11 +23,43 @@ class ErrorBoundary extends Component { } componentDidCatch(error, errorInfo) { - console.error("Uncaught error:", error, errorInfo); + const errorTimestamp = new Date().toLocaleString(); + + console.error("โŒ Uncaught error:", error); + console.error("๐Ÿ“‹ Error Info:", errorInfo); + if (DEBUG_MODE) { - // Development mode: log error details instead of reloading - console.error("Error Stack:", error.stack); - console.error("Component Stack:", errorInfo.componentStack); + // Development mode: log detailed error information + console.error("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"); + console.error("๐Ÿ”ด ERROR BOUNDARY CAUGHT AN ERROR"); + console.error("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"); + + // Error message and type + console.error("โš ๏ธ Error Type:", error.name); + console.error("๐Ÿ“ Error Message:", error.message); + + // Error stack trace + console.error("\n๐Ÿ“ Stack Trace:"); + console.error(error.stack); + + // Component stack + console.error("\n๐Ÿ—๏ธ Component Stack:"); + console.error(errorInfo.componentStack); + + // Additional error details + console.error("\n๐Ÿ“Š Error Details:"); + console.error("- Time:", errorTimestamp); + console.error("- Type:", typeof error); + console.error("- Error Object:", error); + + console.error("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"); + + // Store error info in state for UI display + this.setState({ + error, + errorInfo, + errorTimestamp + }); } } componentDidUpdate(prevProps, prevState) { @@ -37,32 +74,221 @@ class ErrorBoundary extends Component { render() { if (this.state.hasError) { - // In DEBUG_MODE, show error UI; otherwise show empty div and reload + // In DEBUG_MODE, show detailed error UI; otherwise show empty div and reload if (DEBUG_MODE) { + const { error, errorInfo, errorTimestamp } = this.state; return (
-

โš ๏ธ Error Caught by ErrorBoundary (DEBUG_MODE)

-

Check browser console for detailed error information.

- +

+ ๐Ÿšจ ERROR BOUNDARY - DEBUG MODE +

+
+ + {/* Content - Scrollable */} +
+ {errorTimestamp && ( +
+ โฐ Timestamp: +
{errorTimestamp}
+
+ )} + + {error && ( + <> +
+ โŒ Error Type: +
{error.name}
+
+ +
+ ๐Ÿ“ Error Message: +
+ {error.message} +
+
+ + )} + + {error && error.stack && ( +
+ ๐Ÿ“ Stack Trace: +
+                    {error.stack}
+                  
+
+ )} + + {errorInfo && errorInfo.componentStack && ( +
+ ๐Ÿ—๏ธ Component Stack: +
+                    {errorInfo.componentStack}
+                  
+
+ )} + +
+

+ โ„น๏ธ Check browser DevTools Console (F12) for additional error details and network information +

+
+
+ + {/* Footer - Fixed */} +
+ +
); }