diff --git a/com.twin.app.shoptime/src/App/App.js b/com.twin.app.shoptime/src/App/App.js index b46adb1f..d73fb139 100644 --- a/com.twin.app.shoptime/src/App/App.js +++ b/com.twin.app.shoptime/src/App/App.js @@ -72,6 +72,9 @@ import { types } from '../actions/actionTypes'; // } from "../utils/focus-monitor"; // import { PanelHoc } from "../components/TPanel/TPanel"; +// DEBUG_MODE - true인 경우에만 로그 출력 +const DEBUG_MODE = false; + let foreGroundChangeTimer = null; // 기존 콘솔 메서드를 백업 @@ -185,86 +188,92 @@ const sendVoiceLogToPanel = (args) => { }; console.log = function (...args) { - // Voice 로그를 VoicePanel로 전송 - sendVoiceLogToPanel(args); - // 원래 console.log 실행 - originalConsoleLog.apply(console, processArgs(args)); + if (DEBUG_MODE) { + // Voice 로그를 VoicePanel로 전송 + sendVoiceLogToPanel(args); + // 원래 console.log 실행 + originalConsoleLog.apply(console, processArgs(args)); + } }; console.error = function (...args) { - // Voice 로그를 VoicePanel로 전송 (에러는 강제로 ERROR 타입) - try { - const firstArg = args[0]; - if ( - typeof firstArg === 'string' && - (firstArg.includes('[Voice]') || firstArg.includes('[VoiceConductor]')) - ) { - const logData = {}; - if (args.length > 1) { - args.slice(1).forEach((arg, index) => { - if (typeof arg === 'object') { - Object.assign(logData, arg); - } else { - logData[`arg${index + 1}`] = arg; - } + if (DEBUG_MODE) { + // Voice 로그를 VoicePanel로 전송 (에러는 강제로 ERROR 타입) + try { + const firstArg = args[0]; + if ( + typeof firstArg === 'string' && + (firstArg.includes('[Voice]') || firstArg.includes('[VoiceConductor]')) + ) { + const logData = {}; + if (args.length > 1) { + args.slice(1).forEach((arg, index) => { + if (typeof arg === 'object') { + Object.assign(logData, arg); + } else { + logData[`arg${index + 1}`] = arg; + } + }); + } + + store.dispatch({ + type: types.VOICE_ADD_LOG, + payload: { + timestamp: new Date().toISOString(), + type: 'ERROR', + title: firstArg.replace(/^\[Voice\]\s*/, '').replace(/^\[VoiceConductor\]\s*/, ''), + data: Object.keys(logData).length > 0 ? logData : { message: firstArg }, + success: false, + }, }); } - - store.dispatch({ - type: types.VOICE_ADD_LOG, - payload: { - timestamp: new Date().toISOString(), - type: 'ERROR', - title: firstArg.replace(/^\[Voice\]\s*/, '').replace(/^\[VoiceConductor\]\s*/, ''), - data: Object.keys(logData).length > 0 ? logData : { message: firstArg }, - success: false, - }, - }); + } catch (error) { + originalConsoleError.call(console, '[VoiceLog] Error sending error to panel:', error); } - } catch (error) { - originalConsoleError.call(console, '[VoiceLog] Error sending error to panel:', error); - } - originalConsoleError.apply(console, processArgs(args)); + originalConsoleError.apply(console, processArgs(args)); + } }; console.warn = function (...args) { - // Voice 로그를 VoicePanel로 전송 (경고는 ERROR 타입으로) - try { - const firstArg = args[0]; - if ( - typeof firstArg === 'string' && - (firstArg.includes('[Voice]') || firstArg.includes('[VoiceConductor]')) - ) { - const logData = {}; - if (args.length > 1) { - args.slice(1).forEach((arg, index) => { - if (typeof arg === 'object') { - Object.assign(logData, arg); - } else { - logData[`arg${index + 1}`] = arg; - } + if (DEBUG_MODE) { + // Voice 로그를 VoicePanel로 전송 (경고는 ERROR 타입으로) + try { + const firstArg = args[0]; + if ( + typeof firstArg === 'string' && + (firstArg.includes('[Voice]') || firstArg.includes('[VoiceConductor]')) + ) { + const logData = {}; + if (args.length > 1) { + args.slice(1).forEach((arg, index) => { + if (typeof arg === 'object') { + Object.assign(logData, arg); + } else { + logData[`arg${index + 1}`] = arg; + } + }); + } + + store.dispatch({ + type: types.VOICE_ADD_LOG, + payload: { + timestamp: new Date().toISOString(), + type: 'ERROR', + title: + 'WARNING: ' + + firstArg.replace(/^\[Voice\]\s*/, '').replace(/^\[VoiceConductor\]\s*/, ''), + data: Object.keys(logData).length > 0 ? logData : { message: firstArg }, + success: false, + }, }); } - - store.dispatch({ - type: types.VOICE_ADD_LOG, - payload: { - timestamp: new Date().toISOString(), - type: 'ERROR', - title: - 'WARNING: ' + - firstArg.replace(/^\[Voice\]\s*/, '').replace(/^\[VoiceConductor\]\s*/, ''), - data: Object.keys(logData).length > 0 ? logData : { message: firstArg }, - success: false, - }, - }); + } catch (error) { + originalConsoleWarn.call(console, '[VoiceLog] Error sending warning to panel:', error); } - } catch (error) { - originalConsoleWarn.call(console, '[VoiceLog] Error sending warning to panel:', error); - } - originalConsoleWarn.apply(console, processArgs(args)); + originalConsoleWarn.apply(console, processArgs(args)); + } }; const originFocus = Spotlight.focus; @@ -304,12 +313,12 @@ const logFocusTransition = (previousNode, currentNode) => { const currentId = resolveSpotlightIdFromNode(currentNode); if (previousId && previousId !== currentId) { - console.log(`[SpotlightFocus] blur - ${previousId}`); + if (DEBUG_MODE) console.log(`[SpotlightFocus] blur - ${previousId}`); lastLoggedBlurSpotlightId = previousId; } if (currentId && currentId !== lastLoggedSpotlightId) { - console.log(`[SpotlightFocus] focus - ${currentId}`); + if (DEBUG_MODE) console.log(`[SpotlightFocus] focus - ${currentId}`); lastLoggedSpotlightId = currentId; } }; diff --git a/com.twin.app.shoptime/src/actions/productActions.js b/com.twin.app.shoptime/src/actions/productActions.js index d9fdc580..381656f4 100644 --- a/com.twin.app.shoptime/src/actions/productActions.js +++ b/com.twin.app.shoptime/src/actions/productActions.js @@ -7,6 +7,9 @@ import { reduce, set, get } from '../utils/fp'; // CustomerImages용 리뷰 이미지 import import reviewSampleImage from '../../assets/images/image-review-sample-1.png'; +// DEBUG_MODE - true인 경우에만 로그 출력 +const DEBUG_MODE = false; + // Best Seller 상품 목록 조회 IF-LGSP-303 // FP helpers const pickParams = (keys) => (src) => @@ -36,57 +39,60 @@ const createRequestThunk = const body = data(props); // 📡 REQUEST 로그: API 호출 전 (tag별로 다르게 표시) - console.log( - `%c[${tag}] 📤 REQUEST - ${method.toUpperCase()} ${url}`, - 'background: #4CAF50; color: white; font-weight: bold; padding: 3px;', - { - method: method.toUpperCase(), - url: url, - params: query, - body: body, - timestamp: new Date().toISOString(), - } - ); - - const onSuccess = (response) => { - // ✅ RESPONSE 로그: API 호출 성공 (tag별로 다르게 표시) + if (DEBUG_MODE) console.log( - `%c[${tag}] ✅ RESPONSE SUCCESS - ${method.toUpperCase()} ${url}`, - 'background: #2196F3; color: white; font-weight: bold; padding: 3px;', + `%c[${tag}] 📤 REQUEST - ${method.toUpperCase()} ${url}`, + 'background: #4CAF50; color: white; font-weight: bold; padding: 3px;', { method: method.toUpperCase(), url: url, - httpStatus: response?.status, - httpStatusText: response?.statusText, - retCode: response?.data?.retCode, - retMsg: response?.data?.retMsg, - responseData: response?.data, + params: query, + body: body, timestamp: new Date().toISOString(), } ); + const onSuccess = (response) => { + // ✅ RESPONSE 로그: API 호출 성공 (tag별로 다르게 표시) + if (DEBUG_MODE) + console.log( + `%c[${tag}] ✅ RESPONSE SUCCESS - ${method.toUpperCase()} ${url}`, + 'background: #2196F3; color: white; font-weight: bold; padding: 3px;', + { + method: method.toUpperCase(), + url: url, + httpStatus: response?.status, + httpStatusText: response?.statusText, + retCode: response?.data?.retCode, + retMsg: response?.data?.retMsg, + responseData: response?.data, + timestamp: new Date().toISOString(), + } + ); + dispatch({ type, payload: selectPayload(response) }); onSuccessExtra(props, dispatch, getState, response); }; const onFail = (error) => { // ❌ ERROR 로그: API 호출 실패 (tag별로 다르게 표시) - console.error( - `%c[${tag}] ❌ RESPONSE ERROR - ${method.toUpperCase()} ${url}`, - 'background: #F44336; color: white; font-weight: bold; padding: 3px;', - { - method: method.toUpperCase(), - url: url, - errorMessage: error?.message, - errorType: error?.type, - httpStatus: error?.response?.status, - httpStatusText: error?.response?.statusText, - responseRetCode: error?.response?.data?.retCode, - responseRetMsg: error?.response?.data?.retMsg, - responseData: error?.response?.data, - timestamp: new Date().toISOString(), - } - ); + if (DEBUG_MODE) + console.error( + `%c[${tag}] ❌ RESPONSE ERROR - ${method.toUpperCase()} ${url}`, + 'background: #F44336; color: white; font-weight: bold; padding: 3px;', + { + method: method.toUpperCase(), + url: url, + errorMessage: error?.message, + errorType: error?.type, + httpStatus: error?.response?.status, + httpStatusText: error?.response?.statusText, + responseRetCode: error?.response?.data?.retCode, + responseRetMsg: error?.response?.data?.retMsg, + responseData: error?.response?.data, + timestamp: new Date().toISOString(), + } + ); onFailExtra(props, dispatch, getState, error); }; @@ -100,14 +106,14 @@ const createGetThunk = ({ url, type, params = () => ({}), tag }) => export const getBestSeller = (callback) => (dispatch, getState) => { const onSuccess = (response) => { - console.log('getBestSeller onSuccess', response.data); + if (DEBUG_MODE) console.log('getBestSeller onSuccess', response.data); dispatch({ type: types.GET_BEST_SELLER, payload: get('data.data', response) }); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); callback && callback(); }; const onFail = (error) => { - console.error('getBestSeller onFail', error); + if (DEBUG_MODE) console.error('getBestSeller onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); callback && callback(); }; diff --git a/com.twin.app.shoptime/src/api/TAxios.js b/com.twin.app.shoptime/src/api/TAxios.js index f45d47d5..a16fa9e7 100644 --- a/com.twin.app.shoptime/src/api/TAxios.js +++ b/com.twin.app.shoptime/src/api/TAxios.js @@ -120,7 +120,7 @@ export const TAxios = ( if (axiosInstance) { axiosInstance .then((res) => { - console.log('TAxios response', url, res); + // console.log('TAxios response', url, res); const apiSysStatus = res.headers['api-sys-status']; const apiSysMessage = res.headers['api-sys-message']; diff --git a/com.twin.app.shoptime/src/utils/panelUtils.js b/com.twin.app.shoptime/src/utils/panelUtils.js index d9d06f11..d66c6a1a 100644 --- a/com.twin.app.shoptime/src/utils/panelUtils.js +++ b/com.twin.app.shoptime/src/utils/panelUtils.js @@ -1,238 +1,239 @@ -/** - * src/utils/panelUtils.js - * Panel 관련 유틸리티 함수들 - * - * MainView의 Panel Stack 관리 로직을 기반으로 - * 정확한 isOnTop 상태 계산을 제공합니다. - */ - -import { panel_names } from './Config'; - -// DEBUG_MODE - true인 경우에만 로그 출력 -const DEBUG_MODE = true; - -/** - * 🎯 [isOnTop 계산] MainView의 Panel Stack 기반으로 특정 패널의 isOnTop 상태 계산 - * - * @param {Array} panels - Redux store의 panels 배열 - * @param {string} targetPanelName - isOnTop 상태를 확인할 패널 이름 - * @returns {boolean} 해당 패널이 현재 최상위에 있는지 여부 - */ -export const calculateIsPanelOnTop = (panels, targetPanelName) => { - if (!panels || panels.length === 0 || !targetPanelName) { - if (DEBUG_MODE) { - console.log('[PanelUtils] calculateIsPanelOnTop: Invalid input', { - panelsLength: panels?.length || 0, - targetPanelName, - }); - } - return false; - } - - // MainView의 renderTopPanel 로직과 동일한 방식으로 isOnTop 계산 - let renderingPanels = []; - - // 3-layer 구조 체크: PlayerPanel + DetailPanel + MediaPanel(modal) - const hasThreeLayerStructure = - panels.length >= 3 && - // PlayerPanel이 맨 아래 - (panels[panels.length - 3]?.name === panel_names.PLAYER_PANEL || - panels[panels.length - 3]?.name === panel_names.PLAYER_PANEL_NEW) && - // DetailPanel이 중간 - panels[panels.length - 2]?.name === panel_names.DETAIL_PANEL && - // MediaPanel modal이 맨 위 - panels[panels.length - 1]?.name === panel_names.MEDIA_PANEL && - panels[panels.length - 1]?.panelInfo?.modal === true; - - if (hasThreeLayerStructure) { - renderingPanels = panels.slice(-3); - if (DEBUG_MODE) { - console.log('[PanelUtils] 3-layer structure detected', { - renderingPanels: renderingPanels.map(p => p.name), - }); - } - } else if ( - panels[panels.length - 1]?.name === panel_names.PLAYER_PANEL || - panels[panels.length - 1]?.name === panel_names.PLAYER_PANEL_NEW || - panels[panels.length - 1]?.name === panel_names.MEDIA_PANEL || - panels[panels.length - 2]?.name === panel_names.PLAYER_PANEL || - panels[panels.length - 2]?.name === panel_names.MEDIA_PANEL - ) { - renderingPanels = panels.slice(-2); - } else { - renderingPanels = panels.slice(-1); - } - - // 각 패널의 isOnTop 상태 계산 (MainView 로직과 동일) - for (let i = 0; i < renderingPanels.length; i++) { - const panel = renderingPanels[i]; - let isPanelOnTop = false; - - // 3-layer 케이스: 중간 패널(DetailPanel)이 onTop - if (renderingPanels.length === 3) { - if (i === 1) { - // DetailPanel (중간) - isPanelOnTop = true; - } - // PlayerPanel (index 0): isOnTop = false (백그라운드) - // MediaPanel (index 2): isOnTop = false (modal overlay) - } - // 2-layer 케이스: modal이면 첫 번째가 onTop - else if ( - i === 0 && - renderingPanels.length === 2 && - (renderingPanels[1].name === panel_names.PLAYER_PANEL || - renderingPanels[1].name === panel_names.PLAYER_PANEL_NEW || - renderingPanels[1].name === panel_names.MEDIA_PANEL) && - renderingPanels[1].panelInfo.modal - ) { - isPanelOnTop = true; - } - // 일반 케이스: 마지막 패널이 onTop - else if (i === renderingPanels.length - 1) { - isPanelOnTop = true; - } - - if (panel.name === targetPanelName && isPanelOnTop) { - if (DEBUG_MODE) { - console.log('[PanelUtils] calculateIsPanelOnTop: Panel is on top', { - targetPanelName, - panelsLength: panels.length, - renderingPanels: renderingPanels.map(p => p.name), - foundAt: i, - }); - } - return true; - } - } - - if (DEBUG_MODE) { - console.log('[PanelUtils] calculateIsPanelOnTop: Panel is NOT on top', { - targetPanelName, - panelsLength: panels.length, - renderingPanels: renderingPanels.map(p => p.name), - }); - } - return false; -}; - -/** - * 🎯 [현재 최상위 패널] 현재 렌더링되는 패널 중 최상위 패널 이름 반환 - * - * @param {Array} panels - Redux store의 panels 배열 - * @returns {string|null} 최상위 패널 이름 - */ -export const getTopPanelName = (panels) => { - if (!panels || panels.length === 0) { - return null; - } - - // MainView의 topPanelName 로직과 동일 - let targetName = panels[panels.length - 1].name; - if ( - (panels[panels.length - 1].name === panel_names.PLAYER_PANEL || - panels[panels.length - 1].name === panel_names.MEDIA_PANEL) && - panels[panels.length - 1].panelInfo.modal - ) { - targetName = panels[panels.length - 2]?.name; - } - - return targetName; -}; - -/** - * 🎯 [패널 렌더링 정보] 현재 렌더링되는 패널들의 상세 정보 반환 - * - * @param {Array} panels - Redux store의 panels 배열 - * @returns {Object} 렌더링 패널 정보 객체 - */ -export const getRenderingPanelsInfo = (panels) => { - if (!panels || panels.length === 0) { - return { - renderingPanels: [], - topPanelName: null, - isThreeLayerStructure: false, - isTwoLayerModalStructure: false, - }; - } - - let renderingPanels = []; - let isThreeLayerStructure = false; - let isTwoLayerModalStructure = false; - - // 3-layer 구조 체크 - if ( - panels.length >= 3 && - (panels[panels.length - 3]?.name === panel_names.PLAYER_PANEL || - panels[panels.length - 3]?.name === panel_names.PLAYER_PANEL_NEW) && - panels[panels.length - 2]?.name === panel_names.DETAIL_PANEL && - panels[panels.length - 1]?.name === panel_names.MEDIA_PANEL && - panels[panels.length - 1]?.panelInfo?.modal === true - ) { - isThreeLayerStructure = true; - renderingPanels = panels.slice(-3); - } else if ( - panels[panels.length - 1]?.name === panel_names.PLAYER_PANEL || - panels[panels.length - 1]?.name === panel_names.PLAYER_PANEL_NEW || - panels[panels.length - 1]?.name === panel_names.MEDIA_PANEL || - panels[panels.length - 2]?.name === panel_names.PLAYER_PANEL || - panels[panels.length - 2]?.name === panel_names.MEDIA_PANEL - ) { - renderingPanels = panels.slice(-2); - isTwoLayerModalStructure = true; - } else { - renderingPanels = panels.slice(-1); - } - - const topPanelName = getTopPanelName(panels); - - return { - renderingPanels, - topPanelName, - isThreeLayerStructure, - isTwoLayerModalStructure, - }; -}; - -/** - * 🎯 [디버그 정보] Panel stack 상태에 대한 상세 디버그 정보 반환 - * - * @param {Array} panels - Redux store의 panels 배열 - * @param {string} targetPanelName - 확인할 패널 이름 (선택사항) - * @returns {Object} 디버그 정보 객체 - */ -export const getPanelStackDebugInfo = (panels, targetPanelName = null) => { - const renderingInfo = getRenderingPanelsInfo(panels); - const topPanelName = renderingInfo.topPanelName; - const isTargetOnTop = targetPanelName ? calculateIsPanelOnTop(panels, targetPanelName) : null; - - return { - // 기본 정보 - panelsCount: panels?.length || 0, - topPanelName, - - // 렌더링 정보 - renderingPanels: renderingInfo.renderingPanels.map(p => p.name), - isThreeLayerStructure: renderingInfo.isThreeLayerStructure, - isTwoLayerModalStructure: renderingInfo.isTwoLayerModalStructure, - - // 타겟 패널 정보 - targetPanelName, - isTargetOnTop, - - // 전체 패널 목록 - allPanels: panels?.map(p => ({ - name: p.name, - hasModal: !!p.panelInfo?.modal, - })) || [], - - timestamp: new Date().toISOString(), - }; -}; - -export default { - calculateIsPanelOnTop, - getTopPanelName, - getRenderingPanelsInfo, - getPanelStackDebugInfo, -}; \ No newline at end of file +/** + * src/utils/panelUtils.js + * Panel 관련 유틸리티 함수들 + * + * MainView의 Panel Stack 관리 로직을 기반으로 + * 정확한 isOnTop 상태 계산을 제공합니다. + */ + +import { panel_names } from './Config'; + +// DEBUG_MODE - true인 경우에만 로그 출력 +const DEBUG_MODE = false; + +/** + * 🎯 [isOnTop 계산] MainView의 Panel Stack 기반으로 특정 패널의 isOnTop 상태 계산 + * + * @param {Array} panels - Redux store의 panels 배열 + * @param {string} targetPanelName - isOnTop 상태를 확인할 패널 이름 + * @returns {boolean} 해당 패널이 현재 최상위에 있는지 여부 + */ +export const calculateIsPanelOnTop = (panels, targetPanelName) => { + if (!panels || panels.length === 0 || !targetPanelName) { + if (DEBUG_MODE) { + console.log('[PanelUtils] calculateIsPanelOnTop: Invalid input', { + panelsLength: panels?.length || 0, + targetPanelName, + }); + } + return false; + } + + // MainView의 renderTopPanel 로직과 동일한 방식으로 isOnTop 계산 + let renderingPanels = []; + + // 3-layer 구조 체크: PlayerPanel + DetailPanel + MediaPanel(modal) + const hasThreeLayerStructure = + panels.length >= 3 && + // PlayerPanel이 맨 아래 + (panels[panels.length - 3]?.name === panel_names.PLAYER_PANEL || + panels[panels.length - 3]?.name === panel_names.PLAYER_PANEL_NEW) && + // DetailPanel이 중간 + panels[panels.length - 2]?.name === panel_names.DETAIL_PANEL && + // MediaPanel modal이 맨 위 + panels[panels.length - 1]?.name === panel_names.MEDIA_PANEL && + panels[panels.length - 1]?.panelInfo?.modal === true; + + if (hasThreeLayerStructure) { + renderingPanels = panels.slice(-3); + if (DEBUG_MODE) { + console.log('[PanelUtils] 3-layer structure detected', { + renderingPanels: renderingPanels.map((p) => p.name), + }); + } + } else if ( + panels[panels.length - 1]?.name === panel_names.PLAYER_PANEL || + panels[panels.length - 1]?.name === panel_names.PLAYER_PANEL_NEW || + panels[panels.length - 1]?.name === panel_names.MEDIA_PANEL || + panels[panels.length - 2]?.name === panel_names.PLAYER_PANEL || + panels[panels.length - 2]?.name === panel_names.MEDIA_PANEL + ) { + renderingPanels = panels.slice(-2); + } else { + renderingPanels = panels.slice(-1); + } + + // 각 패널의 isOnTop 상태 계산 (MainView 로직과 동일) + for (let i = 0; i < renderingPanels.length; i++) { + const panel = renderingPanels[i]; + let isPanelOnTop = false; + + // 3-layer 케이스: 중간 패널(DetailPanel)이 onTop + if (renderingPanels.length === 3) { + if (i === 1) { + // DetailPanel (중간) + isPanelOnTop = true; + } + // PlayerPanel (index 0): isOnTop = false (백그라운드) + // MediaPanel (index 2): isOnTop = false (modal overlay) + } + // 2-layer 케이스: modal이면 첫 번째가 onTop + else if ( + i === 0 && + renderingPanels.length === 2 && + (renderingPanels[1].name === panel_names.PLAYER_PANEL || + renderingPanels[1].name === panel_names.PLAYER_PANEL_NEW || + renderingPanels[1].name === panel_names.MEDIA_PANEL) && + renderingPanels[1].panelInfo.modal + ) { + isPanelOnTop = true; + } + // 일반 케이스: 마지막 패널이 onTop + else if (i === renderingPanels.length - 1) { + isPanelOnTop = true; + } + + if (panel.name === targetPanelName && isPanelOnTop) { + if (DEBUG_MODE) { + console.log('[PanelUtils] calculateIsPanelOnTop: Panel is on top', { + targetPanelName, + panelsLength: panels.length, + renderingPanels: renderingPanels.map((p) => p.name), + foundAt: i, + }); + } + return true; + } + } + + if (DEBUG_MODE) { + console.log('[PanelUtils] calculateIsPanelOnTop: Panel is NOT on top', { + targetPanelName, + panelsLength: panels.length, + renderingPanels: renderingPanels.map((p) => p.name), + }); + } + return false; +}; + +/** + * 🎯 [현재 최상위 패널] 현재 렌더링되는 패널 중 최상위 패널 이름 반환 + * + * @param {Array} panels - Redux store의 panels 배열 + * @returns {string|null} 최상위 패널 이름 + */ +export const getTopPanelName = (panels) => { + if (!panels || panels.length === 0) { + return null; + } + + // MainView의 topPanelName 로직과 동일 + let targetName = panels[panels.length - 1].name; + if ( + (panels[panels.length - 1].name === panel_names.PLAYER_PANEL || + panels[panels.length - 1].name === panel_names.MEDIA_PANEL) && + panels[panels.length - 1].panelInfo.modal + ) { + targetName = panels[panels.length - 2]?.name; + } + + return targetName; +}; + +/** + * 🎯 [패널 렌더링 정보] 현재 렌더링되는 패널들의 상세 정보 반환 + * + * @param {Array} panels - Redux store의 panels 배열 + * @returns {Object} 렌더링 패널 정보 객체 + */ +export const getRenderingPanelsInfo = (panels) => { + if (!panels || panels.length === 0) { + return { + renderingPanels: [], + topPanelName: null, + isThreeLayerStructure: false, + isTwoLayerModalStructure: false, + }; + } + + let renderingPanels = []; + let isThreeLayerStructure = false; + let isTwoLayerModalStructure = false; + + // 3-layer 구조 체크 + if ( + panels.length >= 3 && + (panels[panels.length - 3]?.name === panel_names.PLAYER_PANEL || + panels[panels.length - 3]?.name === panel_names.PLAYER_PANEL_NEW) && + panels[panels.length - 2]?.name === panel_names.DETAIL_PANEL && + panels[panels.length - 1]?.name === panel_names.MEDIA_PANEL && + panels[panels.length - 1]?.panelInfo?.modal === true + ) { + isThreeLayerStructure = true; + renderingPanels = panels.slice(-3); + } else if ( + panels[panels.length - 1]?.name === panel_names.PLAYER_PANEL || + panels[panels.length - 1]?.name === panel_names.PLAYER_PANEL_NEW || + panels[panels.length - 1]?.name === panel_names.MEDIA_PANEL || + panels[panels.length - 2]?.name === panel_names.PLAYER_PANEL || + panels[panels.length - 2]?.name === panel_names.MEDIA_PANEL + ) { + renderingPanels = panels.slice(-2); + isTwoLayerModalStructure = true; + } else { + renderingPanels = panels.slice(-1); + } + + const topPanelName = getTopPanelName(panels); + + return { + renderingPanels, + topPanelName, + isThreeLayerStructure, + isTwoLayerModalStructure, + }; +}; + +/** + * 🎯 [디버그 정보] Panel stack 상태에 대한 상세 디버그 정보 반환 + * + * @param {Array} panels - Redux store의 panels 배열 + * @param {string} targetPanelName - 확인할 패널 이름 (선택사항) + * @returns {Object} 디버그 정보 객체 + */ +export const getPanelStackDebugInfo = (panels, targetPanelName = null) => { + const renderingInfo = getRenderingPanelsInfo(panels); + const topPanelName = renderingInfo.topPanelName; + const isTargetOnTop = targetPanelName ? calculateIsPanelOnTop(panels, targetPanelName) : null; + + return { + // 기본 정보 + panelsCount: panels?.length || 0, + topPanelName, + + // 렌더링 정보 + renderingPanels: renderingInfo.renderingPanels.map((p) => p.name), + isThreeLayerStructure: renderingInfo.isThreeLayerStructure, + isTwoLayerModalStructure: renderingInfo.isTwoLayerModalStructure, + + // 타겟 패널 정보 + targetPanelName, + isTargetOnTop, + + // 전체 패널 목록 + allPanels: + panels?.map((p) => ({ + name: p.name, + hasModal: !!p.panelInfo?.modal, + })) || [], + + timestamp: new Date().toISOString(), + }; +}; + +export default { + calculateIsPanelOnTop, + getTopPanelName, + getRenderingPanelsInfo, + getPanelStackDebugInfo, +}; diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx index a493e83c..5a6396a2 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx @@ -1,5 +1,8 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +// 디버그 모드 설정 - true일 때만 console.log 출력 +const DEBUG_MODE = false; + import classNames from 'classnames'; import { useDispatch, useSelector } from 'react-redux'; @@ -266,24 +269,28 @@ export default function RandomUnit({ // 포커스 인 const handleFocus = useCallback(() => { // 🔽 [251116] 현재 비디오 상태 로그 출력 - console.log('[RandomUnit] onFocus - 현재 비디오 상태', { - playback: videoPlayState.playback, - display: videoPlayState.display, - videoId: videoPlayState.videoId, - loadingProgress: videoPlayState.loadingProgress, - loadingError: videoPlayState.loadingError, - isPlaying: videoPlayState.isPlaying, - isPaused: videoPlayState.isPaused, - lastUpdate: videoPlayState.lastUpdate, - }); + if (DEBUG_MODE) { + console.log('[RandomUnit] onFocus - 현재 비디오 상태', { + playback: videoPlayState.playback, + display: videoPlayState.display, + videoId: videoPlayState.videoId, + loadingProgress: videoPlayState.loadingProgress, + loadingError: videoPlayState.loadingError, + isPlaying: videoPlayState.isPlaying, + isPaused: videoPlayState.isPaused, + lastUpdate: videoPlayState.lastUpdate, + }); + } - console.log('[RandomUnit] onFocus - 비디오 재생 의도 저장', { - bannerId: spotlightId, - videoPlayerable, - currentIntent: videoPlayIntentRef.current?.bannerId, - currentVideoBannerId, - randomData: randomData?.showId || 'no-show-id', - }); + if (DEBUG_MODE) { + console.log('[RandomUnit] onFocus - 비디오 재생 의도 저장', { + bannerId: spotlightId, + videoPlayerable, + currentIntent: videoPlayIntentRef.current?.bannerId, + currentVideoBannerId, + randomData: randomData?.showId || 'no-show-id', + }); + } setIsFocused(true); @@ -305,10 +312,12 @@ export default function RandomUnit({ timestamp: Date.now(), }; - console.log('[RandomUnit] 비디오 재생 의도 저장됨', { - intentBannerId: spotlightId, - videoUrl: randomData.showUrl, - }); + if (DEBUG_MODE) { + console.log('[RandomUnit] 비디오 재생 의도 저장됨', { + intentBannerId: spotlightId, + videoUrl: randomData.showUrl, + }); + } setVideoIntentVersion((version) => version + 1); @@ -320,10 +329,12 @@ export default function RandomUnit({ // video가 플레이 불가능한 경우: 재생 중인 비디오를 1px로 축소 if (!videoPlayerable && currentVideoBannerId && isCurrentBannerVideoPlaying) { - console.log('[RandomUnit] videoPlayerable=false, shrinking video to 1px', { - videoPlayerable, - currentVideoBannerId, - }); + if (DEBUG_MODE) { + console.log('[RandomUnit] videoPlayerable=false, shrinking video to 1px', { + videoPlayerable, + currentVideoBannerId, + }); + } dispatch(hideModalVideo()); } @@ -582,10 +593,12 @@ export default function RandomUnit({ // ✅ modal=true → modal=false로 전환 (또는 초기 로드 시 modal=false) // playActions의 shouldSkipVideoPlayback이 modal 상태를 확인하므로 // 모드 전환은 중복 방지 로직을 스킵하여 정상 작동 - console.log( - '[RandomUnit] videoClick: current playerPanel modal state:', - playerPanelInfo?.modal - ); + if (DEBUG_MODE) { + console.log( + '[RandomUnit] videoClick: current playerPanel modal state:', + playerPanelInfo?.modal + ); + } handleStartVideo({ bannerId: spotlightId, @@ -739,17 +752,19 @@ export default function RandomUnit({ const currentVideoId = videoPlayState.videoId; const activeBannerId = currentVideoBannerId; - console.log('[RandomUnit] videoPlayIntent useEffect', { - intentBannerId: intent.bannerId, - currentVideoId, - activeBannerId, - currentPlayback: videoPlayState.playback, - currentDisplay: videoPlayState.display, - loadingProgress: videoPlayState.loadingProgress, - shouldStart: intent.bannerId !== activeBannerId, - isVideoTransitionLocked, - timestamp: Date.now(), - }); + if (DEBUG_MODE) { + console.log('[RandomUnit] videoPlayIntent useEffect', { + intentBannerId: intent.bannerId, + currentVideoId, + activeBannerId, + currentPlayback: videoPlayState.playback, + currentDisplay: videoPlayState.display, + loadingProgress: videoPlayState.loadingProgress, + shouldStart: intent.bannerId !== activeBannerId, + isVideoTransitionLocked, + timestamp: Date.now(), + }); + } if (intent.bannerId === activeBannerId) { return undefined; @@ -764,10 +779,12 @@ export default function RandomUnit({ return; } - console.log('[RandomUnit] 비디오 변경 실행', { - from: currentVideoId, - to: intent.bannerId, - }); + if (DEBUG_MODE) { + console.log('[RandomUnit] 비디오 변경 실행', { + from: currentVideoId, + to: intent.bannerId, + }); + } if (activeBannerId) { dispatch(finishVideoPreview()); diff --git a/com.twin.app.shoptime/src/views/TrendingNowPanel/PopularShow/PopularVideoPlayer/PopularVideoPlayer.jsx b/com.twin.app.shoptime/src/views/TrendingNowPanel/PopularShow/PopularVideoPlayer/PopularVideoPlayer.jsx index ad3a9c1a..764814af 100644 --- a/com.twin.app.shoptime/src/views/TrendingNowPanel/PopularShow/PopularVideoPlayer/PopularVideoPlayer.jsx +++ b/com.twin.app.shoptime/src/views/TrendingNowPanel/PopularShow/PopularVideoPlayer/PopularVideoPlayer.jsx @@ -1,9 +1,9 @@ -import React, { memo, useCallback } from "react"; +import React, { memo, useCallback } from 'react'; -import ReactPlayer from "react-player"; +import ReactPlayer from 'react-player'; -import VideoPlayer from "../../../../components/VideoPlayer/VideoPlayer"; -import { scaleH, scaleW } from "../../../../utils/helperMethods"; +import VideoPlayer from '../../../../components/VideoPlayer/VideoPlayer'; +import { scaleH, scaleW } from '../../../../utils/helperMethods'; export default memo(function PopularVideoPlayer({ className, @@ -20,16 +20,16 @@ export default memo(function PopularVideoPlayer({ const mediainfoHandler = useCallback((e) => { const type = e.type; - if (type !== "timeupdate") { - console.log("@@ [mediaInfoHandler].....", type); + if (type !== 'timeupdate') { + console.log('[mediaInfoHandler].....', type); } switch (type) { - case "loadeddata": - console.log("@@ [mediaInfoHandler] loaded data"); + case 'loadeddata': + console.log('[mediaInfoHandler] loaded data'); break; - case "durationchange": - console.log("@@ [mediaInfoHandler] duration", e.currentTarget.duration); + case 'durationchange': + console.log('[mediaInfoHandler] duration', e.currentTarget.duration); break; } }, []); @@ -52,7 +52,7 @@ export default memo(function PopularVideoPlayer({ className={className} onReady={handleReady} playing={shouldPlay} - url={shouldPlay ? src : ""} + url={shouldPlay ? src : ''} width={scaleW(width)} height={scaleH(height)} />