diff --git a/com.twin.app.shoptime/src/actions/mainActions.js b/com.twin.app.shoptime/src/actions/mainActions.js index 5167f09c..f528e0e3 100644 --- a/com.twin.app.shoptime/src/actions/mainActions.js +++ b/com.twin.app.shoptime/src/actions/mainActions.js @@ -5,13 +5,18 @@ import { CATEGORY_DATA_MAX_RESULTS_LIMIT, LOG_CONTEXT_NAME, LOG_MESSAGE_ID } fro import * as HelperMethods from '../utils/helperMethods'; import { types } from './actionTypes'; import { addReservation, changeAppStatus, deleteReservation } from './commonActions'; +import { createDebugHelpers } from '../utils/debug'; + +// 디버그 헬퍼 설정 +const DEBUG_MODE = false; +const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE); //IF-LGSP-007 export const getMainLiveShow = (props) => (dispatch, getState) => { const vodIncFlag = props?.vodIncFlag; const onSuccess = (response) => { - console.log('@@ getMainLiveShow onSuccess', response.data); + dlog('@@ getMainLiveShow onSuccess', response.data); dispatch({ type: types.GET_MAIN_LIVE_SHOW, @@ -20,7 +25,7 @@ export const getMainLiveShow = (props) => (dispatch, getState) => { }; const onFail = (error) => { - console.error('@@ getMainLiveShow onFail', error); + derror('@@ getMainLiveShow onFail', error); }; TAxios(dispatch, getState, 'get', URLS.GET_MAIN_LIVE_SHOW, { vodIncFlag }, {}, onSuccess, onFail); @@ -31,7 +36,7 @@ export const setMainLiveUpcomingAlarm = (props) => (dispatch, getState) => { const { alamDispFlag, chanId, endDt, patnrId, patncNm, showId, showNm, strtDt } = props; const onSuccess = (response) => { - console.log('setMainLiveUpcomingAlarm onSuccess', response.data); + dlog('setMainLiveUpcomingAlarm onSuccess', response.data); if (alamDispFlag === 'Y') { const convertedStrtDt = convertUtcToLocal(strtDt); @@ -72,7 +77,7 @@ export const setMainLiveUpcomingAlarm = (props) => (dispatch, getState) => { }; const onFail = (error) => { - console.error('setMainLiveUpcomingAlarm onFail', error); + derror('setMainLiveUpcomingAlarm onFail', error); }; TAxios( @@ -94,7 +99,7 @@ export const getMainCategoryDetail = (props) => (dispatch, getState) => { dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } })); const onSuccess = (response) => { - console.log('getMainCategoryDetail onSuccess ', response.data); + dlog('getMainCategoryDetail onSuccess ', response.data); dispatch({ type: types.GET_PRODUCT_DETAIL, @@ -105,7 +110,7 @@ export const getMainCategoryDetail = (props) => (dispatch, getState) => { }; const onFail = (error) => { - console.error('getMainCategoryDetail onFail', error); + derror('getMainCategoryDetail onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; @@ -125,7 +130,7 @@ export const getMainCategoryDetail = (props) => (dispatch, getState) => { export const getMainCategoryShowDetail = (props) => (dispatch, getState) => { const { patnrId, showId, curationId } = props; const onSuccess = (response) => { - console.log('getMainCategoryShowDetail onSuccess ', response.data); + dlog('getMainCategoryShowDetail onSuccess ', response.data); dispatch({ type: types.GET_MAIN_CATEGORY_SHOW_DETAIL, @@ -134,7 +139,7 @@ export const getMainCategoryShowDetail = (props) => (dispatch, getState) => { }; const onFail = (error) => { - console.error('getMainCategoryShowDetail onFail', error); + derror('getMainCategoryShowDetail onFail', error); }; TAxios( @@ -165,7 +170,7 @@ export const getSubCategory = lastSubCategoryParams && JSON.stringify(lastSubCategoryParams) === JSON.stringify(params) ) { - console.log('getSubCategory ignore patch'); + dlog('getSubCategory ignore patch'); return; } lastSubCategoryParams = { ...params }; @@ -176,7 +181,7 @@ export const getSubCategory = let currentKey = key; const onSuccess = (response) => { - console.log('getSubCategory onSuccess ', response.data); + dlog('getSubCategory onSuccess ', response.data); if (pageNo === 1) { getSubCategoryKey = new Date(); @@ -220,7 +225,7 @@ export const getSubCategory = const canRetry = nextRetryCount < SUB_CATEGORY_RETRY_LIMIT; if (canRetry) { - console.warn('getSubCategory retry', { + dwarn('getSubCategory retry', { lgCatCd, pageNo, retryCount: nextRetryCount, @@ -232,7 +237,7 @@ export const getSubCategory = return; } - console.error('getSubCategory onFail', error); + derror('getSubCategory onFail', error); if (pageNo === 1) { lastSubCategoryParams = {}; } @@ -253,7 +258,7 @@ export const getSubCategory = export const continueGetSubCategory = (key, pageNo) => (dispatch, getState) => { if (!lastSubCategoryParams) { // <<<<<<< HEAD - console.warn('No previous category parameters found'); + dwarn('No previous category parameters found'); // ======= // console.warn("No previous category parameters found"); // >>>>>>> gitlab/develop @@ -295,7 +300,7 @@ const clearSubCategory = () => ({ // TOP20 영상 목록 조회 IF-LGSP-069 export const getTop20Show = () => (dispatch, getState) => { const onSuccess = (response) => { - console.log('getTop20Show onSuccess ', response.data); + dlog('getTop20Show onSuccess ', response.data); dispatch({ type: types.GET_TOP_20_SHOW, @@ -305,7 +310,7 @@ export const getTop20Show = () => (dispatch, getState) => { }; const onFail = (error) => { - console.error('getTop20Show onFail', error); + derror('getTop20Show onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; @@ -375,14 +380,14 @@ export const getMyFavoriteFlag = (params) => (dispatch, getState) => { const { patnrId, prdtId } = params; const onSuccess = (response) => { - console.log('getMyFavoriteFlag onSuccess ', response.data); + dlog('getMyFavoriteFlag onSuccess ', response.data); dispatch({ type: types.GET_MY_FAVORITE_FLAG, payload: response.data.data, }); }; const onFail = (error) => { - console.error('getMyFavoriteFlag onFail', error); + derror('getMyFavoriteFlag onFail', error); }; TAxios( @@ -401,7 +406,7 @@ export const setMainLikeCategory = (params) => (dispatch, getState) => { const { patnrId, prdtId } = params; const onSuccess = (response) => { - console.log('setMainLikeCategory onSuccess ', response.data); + dlog('setMainLikeCategory onSuccess ', response.data); dispatch({ type: types.SET_MAIN_LIKE_CATEGORY, payload: response.data.data, @@ -428,10 +433,10 @@ export const getHomeFullVideoInfo = ({ lgCatCd }) => (dispatch, getState) => { const onSuccess = (response) => { - console.log('getHomeFullVideoInfo onSuccess', response.data.data.showInfos); + dlog('getHomeFullVideoInfo onSuccess', response.data.data.showInfos); // ✨ DEBUG: youmaylikeInfos 데이터 확인 - console.log('[DEBUG] GET_HOME_FULL_VIDEO_INFO - API Response:', { + dlog('[DEBUG] GET_HOME_FULL_VIDEO_INFO - API Response:', { youmaylikeInfos: response.data.data.youmaylikeInfos, youmaylikeInfos_length: response.data.data.youmaylikeInfos?.length, liveChannelInfos_length: response.data.data.liveChannelInfos?.length, @@ -445,7 +450,7 @@ export const getHomeFullVideoInfo = }; const onFail = (error) => { - console.error('getHomeFullVideoInfo onSuccess', error); + derror('getHomeFullVideoInfo onSuccess', error); }; TAxios( diff --git a/com.twin.app.shoptime/src/reducers/mainReducer.js b/com.twin.app.shoptime/src/reducers/mainReducer.js index 8143b9f5..d2dae9a7 100644 --- a/com.twin.app.shoptime/src/reducers/mainReducer.js +++ b/com.twin.app.shoptime/src/reducers/mainReducer.js @@ -1,5 +1,10 @@ import { types } from '../actions/actionTypes'; import { CATEGORY_DATA_MAX_RESULTS_LIMIT } from '../utils/Config'; +import { createDebugHelpers } from '../utils/debug'; + +// 디버그 헬퍼 설정 +const DEBUG_MODE = false; +const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE); const initialState = { subCategoryData: {}, @@ -159,7 +164,7 @@ export const mainReducer = (state = initialState, action) => { const { data, lgCatCd } = action.payload; // ✨ DEBUG: Reducer에서 youmaylikeInfos 저장 확인 - console.log('[DEBUG] Reducer - GET_HOME_FULL_VIDEO_INFO:', { + dlog('[DEBUG] Reducer - GET_HOME_FULL_VIDEO_INFO:', { youmaylikeInfos_length: data.youmaylikeInfos?.length, youmaylikeInfos: data.youmaylikeInfos, }); @@ -197,7 +202,7 @@ export const mainReducer = (state = initialState, action) => { }; } case types.CLEAR_SHOPNOW_INFO: - console.log('[DEBUG] Reducer - CLEAR_SHOPNOW_INFO called - youmaylikeInfos will be null'); + dlog('[DEBUG] Reducer - CLEAR_SHOPNOW_INFO called - youmaylikeInfos will be null'); return { ...state, fullVideolgCatCd: '', diff --git a/com.twin.app.shoptime/src/reducers/searchReducer.js b/com.twin.app.shoptime/src/reducers/searchReducer.js index a45ceed8..979dcc1c 100644 --- a/com.twin.app.shoptime/src/reducers/searchReducer.js +++ b/com.twin.app.shoptime/src/reducers/searchReducer.js @@ -1,4 +1,9 @@ import { types } from '../actions/actionTypes'; +import { createDebugHelpers } from '../utils/debug'; + +// 디버그 헬퍼 설정 +const DEBUG_MODE = false; +const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE); const initialState = { searchDatas: {}, @@ -81,8 +86,8 @@ export const searchReducer = (state = initialState, action) => { }; case types.RESET_VOICE_SEARCH: - console.log('[VoiceInput]-searchReducer-RESET_VOICE_SEARCH'); - console.log( + dlog('[VoiceInput]-searchReducer-RESET_VOICE_SEARCH'); + dlog( JSON.stringify( { action: 'RESET_VOICE_SEARCH', @@ -120,7 +125,7 @@ export const searchReducer = (state = initialState, action) => { const newPreKey = action.payload?.results?.[0]?.searchId || 'null'; const oldPreKey = state.preShopperHouseData?.results?.[0]?.searchId || 'null'; - console.log('[ShopperHouse]-DIFF (after backup) preShopperHouseKey:', oldPreKey, '→', newPreKey); + dlog('[ShopperHouse]-DIFF (after backup) preShopperHouseKey:', oldPreKey, '→', newPreKey); return { ...state, @@ -150,7 +155,16 @@ export const searchReducer = (state = initialState, action) => { const preSortingType = state.preShopperHouseData?.results?.[0]?.sortingType || 'null'; // [VoiceInput] Redux에 저장 로그 - console.log('[ShopperHouse]-DIFF (after API) shopperHouseKey:', newKey, '| preShopperHouseKey:', preKey, '| sortingType:', sortingType || 'null', '| preSortingType:', preSortingType); + dlog( + '[ShopperHouse]-DIFF (after API) shopperHouseKey:', + newKey, + '| preShopperHouseKey:', + preKey, + '| sortingType:', + sortingType || 'null', + '| preSortingType:', + preSortingType + ); return { ...state, @@ -167,7 +181,7 @@ export const searchReducer = (state = initialState, action) => { } case types.SET_SHOPPERHOUSE_ERROR: - console.log('[VoiceInput] ❌ Redux shopperHouseError 저장:', action.payload); + dlog('[VoiceInput] ❌ Redux shopperHouseError 저장:', action.payload); return { ...state, shopperHouseError: action.payload, @@ -177,7 +191,7 @@ export const searchReducer = (state = initialState, action) => { }; case types.SHOW_SHOPPERHOUSE_ERROR: - console.log('[ShopperHouse] 🔴 Redux shopperHouseErrorPopup 표시:', action.payload); + dlog('[ShopperHouse] 🔴 Redux shopperHouseErrorPopup 표시:', action.payload); return { ...state, shopperHouseErrorPopup: { @@ -190,7 +204,7 @@ export const searchReducer = (state = initialState, action) => { }; case types.HIDE_SHOPPERHOUSE_ERROR: - console.log('[ShopperHouse] ✅ Redux shopperHouseErrorPopup 숨김'); + dlog('[ShopperHouse] ✅ Redux shopperHouseErrorPopup 숨김'); return { ...state, shopperHouseErrorPopup: { @@ -203,16 +217,13 @@ export const searchReducer = (state = initialState, action) => { }; case types.CLEAR_SHOPPERHOUSE_DATA: - console.log('[DEBUG] 🧹 Redux shopperHouseData 초기화 호출 - 호출 스택 추적:'); - console.log( - '[DEBUG] 호출 위치:', - new Error().stack?.split('\n')[1]?.trim() || '(스택 추적 불가)' - ); - console.log( + dlog('[DEBUG] 🧹 Redux shopperHouseData 초기화 호출 - 호출 스택 추적:'); + dlog('[DEBUG] 호출 위치:', new Error().stack?.split('\n')[1]?.trim() || '(스택 추적 불가)'); + dlog( '[VoiceInput] 🧹 Redux shopperHouseData 초기화 (searchId & relativeQueries & preShopperHouseData는 유지)' ); - console.log('[VoiceInput]-searchReducer-CLEAR_SHOPPERHOUSE_DATA'); - console.log( + dlog('[VoiceInput]-searchReducer-CLEAR_SHOPPERHOUSE_DATA'); + dlog( JSON.stringify( { shopperHouseData_cleared: true, @@ -239,7 +250,7 @@ export const searchReducer = (state = initialState, action) => { // 🔽 검색 메인 데이터 처리 case types.GET_SEARCH_MAIN: { - console.log('🔍 [searchReducer] GET_SEARCH_MAIN 받은 payload:', action.payload); + dlog('🔍 [searchReducer] GET_SEARCH_MAIN 받은 payload:', action.payload); // 여러 가능한 구조 확인 let resultData = null; @@ -247,15 +258,15 @@ export const searchReducer = (state = initialState, action) => { if (action.payload?.result) { // payload.result 구조 resultData = action.payload.result; - console.log('✅ [searchReducer] payload.result 구조 확인'); + dlog('✅ [searchReducer] payload.result 구조 확인'); } else if (action.payload?.data?.result) { // payload.data.result 구조 resultData = action.payload.data.result; - console.log('✅ [searchReducer] payload.data.result 구조 확인'); + dlog('✅ [searchReducer] payload.data.result 구조 확인'); } else if (action.payload?.data) { // payload.data에 직접 데이터가 있는 경우 resultData = action.payload.data; - console.log('✅ [searchReducer] payload.data 직접 구조 확인'); + dlog('✅ [searchReducer] payload.data 직접 구조 확인'); } if (!resultData) { @@ -264,7 +275,7 @@ export const searchReducer = (state = initialState, action) => { return state; } - console.log('[searchReducer] ✅ GET_SEARCH_MAIN success'); + dlog('[searchReducer] ✅ GET_SEARCH_MAIN success'); return { ...state, @@ -278,7 +289,7 @@ export const searchReducer = (state = initialState, action) => { } case types.CLEAR_SEARCH_MAIN_DATA: - console.log('[searchReducer] 🧹 searchMainData 초기화'); + dlog('[searchReducer] 🧹 searchMainData 초기화'); return { ...state, searchMainData: { @@ -291,7 +302,7 @@ export const searchReducer = (state = initialState, action) => { // 🎯 [Phase 1] SearchPanel 모드 제어 명령 case types.SWITCH_TO_SEARCH_INPUT_OVERLAY: - console.log('[searchReducer] 🔄 SWITCH_TO_SEARCH_INPUT_OVERLAY 명령 저장', { + dlog('[searchReducer] 🔄 SWITCH_TO_SEARCH_INPUT_OVERLAY 명령 저장', { source: action.payload?.source, timestamp: new Date().toISOString(), }); diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx index a9dabf68..1beae590 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx @@ -7,6 +7,12 @@ import Spotlight from '@enact/spotlight'; import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import Spottable from '@enact/spotlight/Spottable'; +import { createDebugHelpers } from '../../../../utils/debug'; + +// 디버그 헬퍼 설정 +const DEBUG_MODE = false; +const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE); + // import icon_arrow_right from '../../../../../assets/images/icons'; import icon_arrow_dwon from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png'; import icon_shop_now from '../../../../../assets/images/player/icon_tabcontainer_shopnow.png'; @@ -74,7 +80,7 @@ export default function TabContainerV2({ // ✨ DEBUG: youmaylikeInfos 데이터 로그 useEffect(() => { - console.log('[DEBUG] TabContainerV2 - youmaylikeInfos:', { + dlog('[DEBUG] TabContainerV2 - youmaylikeInfos:', { exists: !!youmaylikeInfos, length: youmaylikeInfos?.length, data: youmaylikeInfos, diff --git a/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx b/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx index 505d4fcd..046fb8ef 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx @@ -1,37 +1,19 @@ // src/views/SearchPanel/SearchPanel.new.jsx -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; -import { - useDispatch, - useSelector, -} from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import Spotlight from '@enact/spotlight'; -import SpotlightContainerDecorator - from '@enact/spotlight/SpotlightContainerDecorator'; +import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import Spottable from '@enact/spotlight/Spottable'; import micIcon from '../../../assets/images/searchpanel/image-mic.png'; import hotPicksImage from '../../../assets/images/searchpanel/img-hotpicks.png'; -import hotPicksBrandImage - from '../../../assets/images/searchpanel/img-search-hotpicks.png'; -import { - sendLogGNB, - sendLogTotalRecommend, -} from '../../actions/logActions'; +import hotPicksBrandImage from '../../../assets/images/searchpanel/img-search-hotpicks.png'; +import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions'; import { getMyRecommandedKeyword } from '../../actions/myPageActions'; -import { - popPanel, - pushPanel, - updatePanel, -} from '../../actions/panelActions'; +import { popPanel, pushPanel, updatePanel } from '../../actions/panelActions'; import { clearPanelCommand, clearShopperHouseData, @@ -51,36 +33,27 @@ import { // showWarningToast, // } from '../../actions/toastActions'; import TBody from '../../components/TBody/TBody'; -import TItemCardNew, { - removeDotAndColon, -} from '../../components/TItemCard/TItemCard.new'; +import TItemCardNew, { removeDotAndColon } from '../../components/TItemCard/TItemCard.new'; import TPanel from '../../components/TPanel/TPanel'; -import TVerticalPagenator - from '../../components/TVerticalPagenator/TVerticalPagenator'; -import TVirtualGridList - from '../../components/TVirtualGridList/TVirtualGridList'; +import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator'; +import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList'; import usePanelHistory from '../../hooks/usePanelHistory/usePanelHistory'; // import VirtualKeyboardContainer from "../../components/TToast/VirtualKeyboardContainer"; import usePrevious from '../../hooks/usePrevious'; import { useSearchHistory } from '../../hooks/useSearchHistory'; -import { - LOG_CONTEXT_NAME, - LOG_MENU, - LOG_MESSAGE_ID, - panel_names, -} from '../../utils/Config'; +import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../utils/Config'; import NoSearchResults from './NoSearchResults/NoSearchResults'; // import NoSearchResults from './NoSearchResults/NoSearchResults'; import SearchInputOverlay from './SearchInputOverlay'; import css from './SearchPanel.new.module.less'; import SearchResultsNew from './SearchResults.new.v2'; -import TInputSimple, { - ICONS, - KINDS, -} from './TInput/TInputSimple'; -import VoiceInputOverlay, { - VOICE_MODES, -} from './VoiceInputOverlay/VoiceInputOverlay'; +import TInputSimple, { ICONS, KINDS } from './TInput/TInputSimple'; +import VoiceInputOverlay, { VOICE_MODES } from './VoiceInputOverlay/VoiceInputOverlay'; +import { createDebugHelpers } from '../../utils/debug'; + +// 디버그 헬퍼 설정 +const DEBUG_MODE = false; +const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE); /** * ✨ Mode-Based Architecture 도입 @@ -240,7 +213,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // 🐛 [DEBUG] shopperHouseData 상태 변경 추적 (DEBUG_MODE가 true일 경우에만) useEffect(() => { if (DEBUG_MODE) { - console.log('[DEBUG] 📊 SearchPanel shopperHouseData 상태 변경:', { + dlog('[DEBUG] 📊 SearchPanel shopperHouseData 상태 변경:', { hasData: !!shopperHouseData, dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0, searchId: shopperHouseData?.results?.[0]?.searchId || '(없음)', @@ -256,7 +229,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // 🐛 [DEBUG] SearchPanel 마운트/언마운트 추적 (DEBUG_MODE가 true일 경우에만) useEffect(() => { if (DEBUG_MODE) { - console.log('[DEBUG] 🚀 SearchPanel 마운트됨 - 초기 shopperHouseData 상태:', { + dlog('[DEBUG] 🚀 SearchPanel 마운트됨 - 초기 shopperHouseData 상태:', { hasData: !!shopperHouseData, dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0, currentMode, @@ -266,7 +239,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { return () => { if (DEBUG_MODE) { - console.log('[DEBUG] 🔚 SearchPanel 언마운트됨 - shopperHouseData 상태:', { + dlog('[DEBUG] 🔚 SearchPanel 언마운트됨 - shopperHouseData 상태:', { hasData: !!shopperHouseData, dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0, timestamp: new Date().toISOString(), @@ -278,7 +251,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // 🐛 [DEBUG] isOnTop 상태 변경 추적 (DetailPanel <-> SearchPanel 전환, DEBUG_MODE가 true일 경우에만) useEffect(() => { if (isOnTopRef.current !== isOnTop && DEBUG_MODE) { - console.log('[DEBUG] 🔄 SearchPanel isOnTop 상태 변경:', { + dlog('[DEBUG] 🔄 SearchPanel isOnTop 상태 변경:', { from: isOnTopRef.current, to: isOnTop, shopperHouseData_preserved: !!shopperHouseData, @@ -317,7 +290,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // API 실패 시 fallback reference 초기화 useEffect(() => { if (shopperHouseErrorPopup?.visible && shopperHouseErrorPopup?.type === 'API_FAILURE') { - console.log('[SearchPanel] 🧹 API 실패 감지 - fallbackShopperHouseData 초기화'); + dlog('[SearchPanel] 🧹 API 실패 감지 - fallbackShopperHouseData 초기화'); shopperHouseDataRef.current = null; } }, [shopperHouseErrorPopup?.visible, shopperHouseErrorPopup?.type]); @@ -356,7 +329,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { currentPanel?.panelInfo?.currentSpot; if (DEBUG_MODE && isReturning) { - console.log('[FOCUS] 🎯 DetailPanel 복귀 감지:', { + dlog('[FOCUS] 🎯 DetailPanel 복귀 감지:', { current: currentPanel?.panelName, previous: previousPanel?.panelName, action: currentPanel?.action, @@ -449,9 +422,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // ✨ [Phase 4] Enter/OK 키 처리 - SearchInputOverlay 표시 if (e.key === 'Enter' || e.keyCode === 13) { - console.log( - '[DEBUG] [SearchPanel] TInputSimple에서 Enter/OK 키 감지 → SearchInputOverlay 오픈' - ); + dlog('[DEBUG] [SearchPanel] TInputSimple에서 Enter/OK 키 감지 → SearchInputOverlay 오픈'); e.preventDefault(); // ✨ [Phase 6] SearchInputOverlay 오픈 후 자동으로 입력 준비 완료 @@ -525,7 +496,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // * 0hun: input에 포커스 발생하여 가상 키보드 활성 시, `isInputModeActive` 상태 Boolean 값 설정 // */ // const handleInputModeChange = useCallback((isActive) => { - // console.log( + // dlog( // "[SearchPanel] TInput 입력 모드:", // isActive ? "활성화 (키보드 표시)" : "비활성화 (키보드 숨김)" // ); @@ -613,7 +584,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { } if (DEBUG_MODE) { - console.log( + dlog( '🎤 [DEBUG][SearchPanel] openVoiceOverlay called, current isVoiceOverlayVisible:', isVoiceOverlayVisible ); @@ -636,7 +607,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { */ const onCancel = useCallback(() => { if (DEBUG_MODE) { - console.log('[DEBUG]-onCancel called', { + dlog('[DEBUG]-onCancel called', { isOnTop: isOnTopRef.current, isVoiceOverlayVisible, isSearchOverlayVisible, @@ -649,7 +620,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { if (!isOnTopRef.current) { if (DEBUG_MODE) { - console.log('[DEBUG]-onCancel: isOnTopRef is false, returning'); + dlog('[DEBUG]-onCancel: isOnTopRef is false, returning'); } return; } @@ -657,7 +628,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // VoiceInputOverlay가 열려있으면 먼저 닫기 if (isVoiceOverlayVisible) { if (DEBUG_MODE) { - console.log('[DEBUG]-onCancel: closing VoiceInputOverlay'); + dlog('[DEBUG]-onCancel: closing VoiceInputOverlay'); } setIsVoiceOverlayVisible(false); return; @@ -666,7 +637,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // SearchInputOverlay가 열려있으면 먼저 닫기 if (isSearchOverlayVisible) { if (DEBUG_MODE) { - console.log('[DEBUG]-onCancel: closing SearchInputOverlay'); + dlog('[DEBUG]-onCancel: closing SearchInputOverlay'); } handleSearchOverlayClose(); return; @@ -674,7 +645,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // ✨ [Phase 5] VOICE_RESULT 모드에서 ESC/뒤로가기 누르면 INITIAL 모드로 돌아가기 if (DEBUG_MODE) { - console.log('[DEBUG]-VOICE_RESULT check:', { + dlog('[DEBUG]-VOICE_RESULT check:', { currentMode, isVoiceResultMode: currentMode === SEARCH_PANEL_MODES.VOICE_RESULT, VOICE_RESULT_value: SEARCH_PANEL_MODES.VOICE_RESULT, @@ -683,11 +654,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { if (currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) { if (DEBUG_MODE) { - console.log( + dlog( '[DEBUG]-VOICE_RESULT: Clearing ShopperHouse data (searchId will be preserved for 2nd search)' ); - console.log('[VoiceInput]-SearchPanel-onCancel-VOICE_RESULT'); - console.log('[VoiceInput] 🧹 VOICE_RESULT 모드에서 ESC 누름 - clearShopperHouseData 호출'); + dlog('[VoiceInput]-SearchPanel-onCancel-VOICE_RESULT'); + dlog('[VoiceInput] 🧹 VOICE_RESULT 모드에서 ESC 누름 - clearShopperHouseData 호출'); } // 🎯 [포커스 로직 통합] 포커스는 상태 변경에 의해 자동으로 처리됨 setIsShopperHousePending(false); @@ -696,17 +667,17 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { } if (DEBUG_MODE) { - console.log('[DEBUG]-onCancel: normal cancel logic', { searchQuery }); + dlog('[DEBUG]-onCancel: normal cancel logic', { searchQuery }); } if (searchQuery === null || searchQuery === '') { if (DEBUG_MODE) { - console.log('[DEBUG]-onCancel: popping panel'); + dlog('[DEBUG]-onCancel: popping panel'); } dispatch(popPanel(panel_names.SEARCH_PANEL)); } else { if (DEBUG_MODE) { - console.log('[DEBUG]-onCancel: resetting search query'); + dlog('[DEBUG]-onCancel: resetting search query'); } setSearchQuery(''); // 🎯 [포커스 로직 통합] 포커스는 상태 변경(searchQuery)에 의해 자동으로 처리됨 @@ -820,7 +791,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { const analyzeCurrentScenario = useCallback(() => { // DEBUG: 모든 기본 상태값 출력 if (DEBUG_MODE) { - console.log('[DEBUG] analyzeCurrentScenario 호출됨:', { + dlog('[DEBUG] analyzeCurrentScenario 호출됨:', { // 🎯 기존 isOnTop과 usePanelHistory의 isOnTop 비교 propIsOnTop: isOnTop, historyIsOnTop: currentIsOnTop, @@ -845,8 +816,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { if (isReturningFromDetailPanel) { const currentSpot = currentPanel?.panelInfo?.currentSpot; if (DEBUG_MODE) { - console.log('[Focus] usePanelHistory로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동'); - console.log('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (usePanelHistory)', { + dlog('[Focus] usePanelHistory로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동'); + dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (usePanelHistory)', { currentSpot, mode: currentMode, fromSearchResult: currentMode === SEARCH_PANEL_MODES.SEARCH_RESULT, @@ -862,7 +833,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // 🎯 [개선된 fallback] usePanelHistory의 isOnTop 정보 활용 // DetailPanel에서 방금 복귀한 상황 (usePanelHistory가 없을 경우를 대비) if (DEBUG_MODE) { - console.log('[DEBUG] 개선된 DETAIL_PANEL_RETURN 조건 확인 (fallback):', { + dlog('[DEBUG] 개선된 DETAIL_PANEL_RETURN 조건 확인 (fallback):', { // 🎯 여러 isOnTop 소스 비교 propIsOnTop: isOnTop, historyIsOnTop: currentIsOnTop, @@ -889,8 +860,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { ) { const usedHistoryOnTop = currentIsOnTop && isOnTopChange?.becameOnTop; if (DEBUG_MODE) { - console.log('[Focus] 개선된 방식으로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동'); - console.log('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (improved fallback)', { + dlog('[Focus] 개선된 방식으로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동'); + dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (improved fallback)', { currentSpot: panelInfo.currentSpot, mode: currentMode, fromSearchResult: currentMode === SEARCH_PANEL_MODES.SEARCH_RESULT, @@ -908,7 +879,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // - 위의 DETAIL_PANEL_RETURN이 아닌 경우 (= currentSpot이 없거나 모드가 검색 결과 아님) if (isOnTop && !isOnTopRef.current) { if (DEBUG_MODE) { - console.log('[FOCUS] 🎯 Scenario: INITIAL_OPEN', { + dlog('[FOCUS] 🎯 Scenario: INITIAL_OPEN', { currentSpot: panelInfo?.currentSpot, mode: currentMode, }); @@ -927,7 +898,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { currentModeRef.current !== SEARCH_PANEL_MODES.SEARCH_RESULT ) { if (DEBUG_MODE) { - console.log('[FOCUS] 🎯 Scenario: SEARCH_RESULT_LOADED (Mode Changed)', { + dlog('[FOCUS] 🎯 Scenario: SEARCH_RESULT_LOADED (Mode Changed)', { themeCount: searchDatas?.theme?.length || 0, itemCount: searchDatas?.item?.length || 0, showCount: searchDatas?.show?.length || 0, @@ -948,10 +919,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { currentMode === SEARCH_PANEL_MODES.VOICE_RESULT && shopperHouseData && // 🎯 [개선] 모드 변경 OR 새로운 데이터 도착 감지 - (currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT || shopperHouseDataRef.current !== shopperHouseData) + (currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT || + shopperHouseDataRef.current !== shopperHouseData) ) { if (DEBUG_MODE) { - console.log('[FOCUS] 🎯 Scenario: NEW_SEARCH_LOADED (Voice Result Mode)', { + dlog('[FOCUS] 🎯 Scenario: NEW_SEARCH_LOADED (Voice Result Mode)', { itemCount: shopperHouseData?.results?.[0]?.docs?.length || 0, prevMode: currentModeRef.current, nextMode: currentMode, @@ -966,7 +938,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // Voice Overlay가 닫힌 상황 if (!isVoiceOverlayVisible && isVoiceOverlayVisibleRef.current) { if (DEBUG_MODE) { - console.log('[FOCUS] 🎯 Scenario: VOICE_OVERLAY_CLOSED', { + dlog('[FOCUS] 🎯 Scenario: VOICE_OVERLAY_CLOSED', { hasShopperHouseData: !!shopperHouseData, }); } @@ -974,7 +946,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // 이렇게 하면 VOICE_OVERLAY_CLOSED 시나리오에서 TInput으로 가는 것을 방지 if (shopperHouseData && currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) { if (DEBUG_MODE) { - console.log('[FOCUS] 🔄 VOICE_OVERLAY_CLOSED + new data → NEW_SEARCH_LOADED 우선 처리'); + dlog('[FOCUS] 🔄 VOICE_OVERLAY_CLOSED + new data → NEW_SEARCH_LOADED 우선 처리'); } return 'NEW_SEARCH_LOADED'; } @@ -986,7 +958,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // - 검색이 수행되지 않았거나 SearchPanel이 SEARCH_RESULT 모드가 아닌 경우 if (!isSearchOverlayVisible && isSearchOverlayVisibleRef.current) { if (DEBUG_MODE) { - console.log('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED', { + dlog('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED', { isSearchOverlayVisible, prevIsSearchOverlayVisible: isSearchOverlayVisibleRef.current, currentMode, @@ -1040,25 +1012,25 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { if (isReturningFromDetailPanel && currentPanel?.panelInfo?.currentSpot) { currentSpot = currentPanel.panelInfo.currentSpot; if (DEBUG_MODE) { - console.log('[FOCUS] 🎯 usePanelHistory currentSpot 사용:', currentSpot); + dlog('[FOCUS] 🎯 usePanelHistory currentSpot 사용:', currentSpot); } } // 2. fallback: 기존 panelInfo.currentSpot 사용 else if (panelInfo?.currentSpot) { currentSpot = panelInfo.currentSpot; if (DEBUG_MODE) { - console.log('[FOCUS] 🔄 fallback으로 panelInfo.currentSpot 사용:', currentSpot); + dlog('[FOCUS] 🔄 fallback으로 panelInfo.currentSpot 사용:', currentSpot); } } if (currentSpot && currentSpot.startsWith('searchItemContents')) { if (DEBUG_MODE) { - console.log('[FOCUS] 🎯 DETAIL_PANEL_RETURN: 이전 상품으로 포커스 복원:', currentSpot); + dlog('[FOCUS] 🎯 DETAIL_PANEL_RETURN: 이전 상품으로 포커스 복원:', currentSpot); } return currentSpot; } else { if (DEBUG_MODE) { - console.log( + dlog( '[FOCUS] ⚠️ DETAIL_PANEL_RETURN: currentSpot이 유효하지 않음, fallback으로 이동:', { currentSpot, @@ -1094,7 +1066,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // SearchInputOverlay에서 검색을 실행하면 isSearchOverlayVisible이 false로 설정되고 // 동시에 검색 결과에 따라 모드가 변경되므로, 이 케이스는 검색어 선택 후 닫을 때만 발생 if (DEBUG_MODE) { - console.log('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED - TInputSimple으로 포커스'); + dlog('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED - TInputSimple으로 포커스'); } return SPOTLIGHT_IDS.SEARCH_INPUT_BOX; @@ -1118,7 +1090,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { */ const handleTransitionToSearchInput = useCallback(() => { if (DEBUG_MODE) { - console.log('[SearchPanel] 🔄 handleTransitionToSearchInput 호출'); + dlog('[SearchPanel] 🔄 handleTransitionToSearchInput 호출'); } // Redux Thunk 액션으로 모든 전환 로직 처리 @@ -1135,12 +1107,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { * Search overlay close handler */ const handleSearchOverlayClose = useCallback(() => { - console.log('[DEBUG] 🚪 handleSearchOverlayClose 호출됨 - 직접 확인!', { + dlog('[DEBUG] 🚪 handleSearchOverlayClose 호출됨 - 직접 확인!', { timestamp: new Date().toISOString(), }); if (DEBUG_MODE) { - console.log('[DEBUG] 🚪 SearchInputOverlay closing'); + dlog('[DEBUG] 🚪 SearchInputOverlay closing'); } const hasSearchResults = @@ -1149,11 +1121,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { (searchDatas?.show?.length || 0) > 0; // 🎯 SearchInputOverlay 닫힘 후 TInputSimple으로 포커스 이동을 위한 플래그 설정 - console.log('[DEBUG] setShouldFocusSearchInput(true) 설정 직전', { + dlog('[DEBUG] setShouldFocusSearchInput(true) 설정 직전', { timestamp: new Date().toISOString(), }); setShouldFocusSearchInput(true); - console.log('[DEBUG] setShouldFocusSearchInput(true) 설정됨!', { + dlog('[DEBUG] setShouldFocusSearchInput(true) 설정됨!', { timestamp: new Date().toISOString(), }); @@ -1171,7 +1143,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { */ const handleVoiceOverlayClose = useCallback(() => { if (DEBUG_MODE) { - console.log( + dlog( '🚪 [DEBUG][SearchPanel] handleVoiceOverlayClose called, setting isVoiceOverlayVisible to FALSE' ); } @@ -1213,7 +1185,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // Redux state 업데이트를 위해 약간의 지연 후 API 호출 setTimeout(() => { - console.log('[HowAboutThese] 🔄 Redux 업데이트 후 API 호출'); + dlog('[HowAboutThese] 🔄 Redux 업데이트 후 API 호출'); dispatch(getShopperHouseSearch(trimmedQuery, shopperHouseSearchId)); }, 50); // 50ms 지연 @@ -1337,40 +1309,40 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { [hotPicksForYou, dispatch, SafeImage] ); - const handleClick = useCallback((patnrId, prdtId) => { - dispatch( - pushPanel({ - name: panel_names.DETAIL_PANEL, - panelInfo: { patnrId, prdtId }, - }) - ); - },[dispatch]) - + const handleClick = useCallback( + (patnrId, prdtId) => { + dispatch( + pushPanel({ + name: panel_names.DETAIL_PANEL, + panelInfo: { patnrId, prdtId }, + }) + ); + }, + [dispatch] + ); const renderTsvItem = useCallback( - ({ index, ...rest }) => { - const { offerInfo, prdtId, imgUrl, patnrId, prdtNm, priceInfo } = - tsvInfo[index]; - - return ( - {handleClick(patnrId,prdtId)}} - offerInfo={offerInfo} - priceInfo={priceInfo} - productId={prdtId} - productName={prdtNm} - spotlightId={ - "searchMain-tsvInfo-spotlightId-" + removeDotAndColon(prdtId) - } - {...rest} - /> - ); - }, - [tsvInfo,handleClick] - ); + ({ index, ...rest }) => { + const { offerInfo, prdtId, imgUrl, patnrId, prdtNm, priceInfo } = tsvInfo[index]; + return ( + { + handleClick(patnrId, prdtId); + }} + offerInfo={offerInfo} + priceInfo={priceInfo} + productId={prdtId} + productName={prdtNm} + spotlightId={'searchMain-tsvInfo-spotlightId-' + removeDotAndColon(prdtId)} + {...rest} + /> + ); + }, + [tsvInfo, handleClick] + ); /** * ✨ [Phase 2] 모드별 콘텐츠 렌더링 (VoiceInputOverlay의 renderModeContent와 동일한 패턴) @@ -1553,7 +1525,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
- )} - ); } @@ -1606,7 +1576,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { let nextMode = SEARCH_PANEL_MODES.INITIAL; if (DEBUG_MODE) { - console.log('[DEBUG]-MODE DECISION useEffect running', { + dlog('[DEBUG]-MODE DECISION useEffect running', { isVoiceOverlayVisible, hasShopperHouseData: !!shopperHouseData, shopperHouseData_detail: shopperHouseData ? 'EXISTS' : 'NULL', @@ -1626,14 +1596,14 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // 우선순위 1: 음성 입력 오버레이가 열려있으면 VOICE_INPUT 모드 if (isVoiceOverlayVisible) { if (DEBUG_MODE) { - console.log('[DEBUG]-MODE: isVoiceOverlayVisible is TRUE → VOICE_INPUT'); + dlog('[DEBUG]-MODE: isVoiceOverlayVisible is TRUE → VOICE_INPUT'); } nextMode = SEARCH_PANEL_MODES.VOICE_INPUT; } // 우선순위 2: 음성 검색 결과가 있으면 VOICE_RESULT 모드 else if (shopperHouseData || isShopperHousePending) { if (DEBUG_MODE) { - console.log('[DEBUG]-MODE: shopperHouseData EXISTS or pending → VOICE_RESULT', { + dlog('[DEBUG]-MODE: shopperHouseData EXISTS or pending → VOICE_RESULT', { hasData: !!shopperHouseData, isPending: isShopperHousePending, }); @@ -1648,21 +1618,21 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { searchDatas?.show?.length > 0 ) { if (DEBUG_MODE) { - console.log('[DEBUG]-MODE: searchResults EXISTS → SEARCH_RESULT'); + dlog('[DEBUG]-MODE: searchResults EXISTS → SEARCH_RESULT'); } nextMode = SEARCH_PANEL_MODES.SEARCH_RESULT; } // 우선순위 4: 검색 입력 오버레이가 열려있으면 SEARCH_INPUT 모드 else if (isSearchOverlayVisible) { if (DEBUG_MODE) { - console.log('[DEBUG]-MODE: isSearchOverlayVisible is TRUE → SEARCH_INPUT'); + dlog('[DEBUG]-MODE: isSearchOverlayVisible is TRUE → SEARCH_INPUT'); } nextMode = SEARCH_PANEL_MODES.SEARCH_INPUT; } // 우선순위 5: 초기 상태 (기본값) else { if (DEBUG_MODE) { - console.log('[DEBUG]-MODE: No condition met → INITIAL'); + dlog('[DEBUG]-MODE: No condition met → INITIAL'); } nextMode = SEARCH_PANEL_MODES.INITIAL; } @@ -1670,7 +1640,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // 모드가 변경되었을 때만 업데이트 if (nextMode !== currentMode) { if (DEBUG_MODE) { - console.log(`[DEBUG]-VOICE_RESULT 🔀 Mode changed: ${currentMode} → ${nextMode}`, { + dlog(`[DEBUG]-VOICE_RESULT 🔀 Mode changed: ${currentMode} → ${nextMode}`, { isVoiceOverlayVisible, shopperHouseData: !!shopperHouseData, isShopperHousePending, @@ -1688,7 +1658,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { setCurrentMode(nextMode); } else { if (DEBUG_MODE) { - console.log('[DEBUG]-MODE: Mode unchanged -', currentMode); + dlog('[DEBUG]-MODE: Mode unchanged -', currentMode); } } }, [ @@ -1711,11 +1681,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { */ useEffect(() => { if (DEBUG_MODE) { - console.log('[DEBUG]-searchQuery useEffect:', { searchQuery }); + dlog('[DEBUG]-searchQuery useEffect:', { searchQuery }); } if (!searchQuery) { if (DEBUG_MODE) { - console.log('[DEBUG]-VOICE_RESULT: searchQuery is empty, calling resetSearch'); + dlog('[DEBUG]-VOICE_RESULT: searchQuery is empty, calling resetSearch'); } dispatch(resetSearch()); } @@ -1757,7 +1727,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { */ useEffect(() => { if (DEBUG_MODE) { - console.log('[DEBUG][Focus] Focus useEffect 호출됨 - 상태값 확인:', { + dlog('[DEBUG][Focus] Focus useEffect 호출됨 - 상태값 확인:', { isOnTop, panelInfo: panelInfo, currentMode, @@ -1809,9 +1779,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // NEW_SEARCH_LOADED: 음성 검색 결과 로드 시 VoiceInputOverlay와 충돌 방지 // 다른 시나리오에서는 기존과 같은 지연 시간 (100ms) const focusDelay = - scenario === 'DETAIL_PANEL_RETURN' || scenario === 'NEW_SEARCH_LOADED' - ? 50 - : 100; + scenario === 'DETAIL_PANEL_RETURN' || scenario === 'NEW_SEARCH_LOADED' ? 50 : 100; unifiedFocusTimerRef.current = setTimeout(() => { const targetElement = document.querySelector(`[data-spotlight-id="${targetId}"]`); @@ -1819,7 +1787,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { if (targetElement || targetId === SPOTLIGHT_IDS.SEARCH_INPUT_BOX) { Spotlight.focus(targetId); if (DEBUG_MODE) { - console.log('[FOCUS] ✅ 포커스 이동 완료:', { + dlog('[FOCUS] ✅ 포커스 이동 완료:', { targetId, scenario, hasElement: !!targetElement, @@ -1829,7 +1797,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { } } else { if (DEBUG_MODE) { - console.log('[FOCUS] ⚠️ 포커스 대상 요소를 찾지 못했습니다:', { + dlog('[FOCUS] ⚠️ 포커스 대상 요소를 찾지 못했습니다:', { targetId, scenario, timestamp: new Date().toISOString(), @@ -1842,7 +1810,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { const fallbackElement = document.querySelector(`[data-spotlight-id="${fallbackTarget}"]`); if (fallbackElement) { if (DEBUG_MODE) { - console.log( + dlog( '[FOCUS] 🔄 DETAIL_PANEL_RETURN fallback: 첫 번째 상품으로 포커스:', fallbackTarget ); @@ -1860,7 +1828,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { if (scenario === 'NEW_SEARCH_LOADED' && targetId === 'searchItemContents0') { setTimeout(() => { if (DEBUG_MODE) { - console.log('[FOCUS] 🔄 NEW_SEARCH_LOADED: 1초 후 첫 번째 상품으로 다시 포커스 이동'); + dlog('[FOCUS] 🔄 NEW_SEARCH_LOADED: 1초 후 첫 번째 상품으로 다시 포커스 이동'); } Spotlight.focus('searchItemContents0'); }, 500); // 0.5초 후 @@ -1908,7 +1876,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { * - 자동으로 플래그 초기화 */ useEffect(() => { - console.log('[DEBUG] shouldFocusSearchInput useEffect 실행됨!', { + dlog('[DEBUG] shouldFocusSearchInput useEffect 실행됨!', { shouldFocusSearchInput, timestamp: new Date().toISOString(), }); @@ -1916,12 +1884,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { if (shouldFocusSearchInput) { let focusTimer = null; - console.log('[DEBUG] shouldFocusSearchInput === true, 타이머 설정 중...', { + dlog('[DEBUG] shouldFocusSearchInput === true, 타이머 설정 중...', { timestamp: new Date().toISOString(), }); if (DEBUG_MODE) { - console.log('[FOCUS] 🎯 SearchInputOverlay 닫힘 후 포커스 관리 useEffect 실행', { + dlog('[FOCUS] 🎯 SearchInputOverlay 닫힘 후 포커스 관리 useEffect 실행', { shouldFocusSearchInput, timestamp: new Date().toISOString(), }); @@ -1929,65 +1897,62 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { // 500ms 후 TInputSimple에 포커스 이동 focusTimer = setTimeout(() => { - console.log('[DEBUG] ⏰ 500ms 타이머 콜백 실행!', { + dlog('[DEBUG] ⏰ 500ms 타이머 콜백 실행!', { targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX, timestamp: new Date().toISOString(), }); if (DEBUG_MODE) { - console.log('[FOCUS] ⏰ 500ms 타이머 콜백 실행 - TInputSimple으로 포커스 이동', { + dlog('[FOCUS] ⏰ 500ms 타이머 콜백 실행 - TInputSimple으로 포커스 이동', { targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX, timestamp: new Date().toISOString(), }); } - console.log('[DEBUG] Spotlight.focus() 호출 직전', { + dlog('[DEBUG] Spotlight.focus() 호출 직전', { targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX, }); Spotlight.focus(SPOTLIGHT_IDS.SEARCH_INPUT_BOX); - console.log('[DEBUG] Spotlight.focus() 호출 완료!', { + dlog('[DEBUG] Spotlight.focus() 호출 완료!', { targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX, timestamp: new Date().toISOString(), }); if (DEBUG_MODE) { - console.log('[FOCUS] ✅ Spotlight.focus() 호출 완료', { + dlog('[FOCUS] ✅ Spotlight.focus() 호출 완료', { targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX, timestamp: new Date().toISOString(), }); } - console.log('[DEBUG] setShouldFocusSearchInput(false) 호출 직전', { + dlog('[DEBUG] setShouldFocusSearchInput(false) 호출 직전', { timestamp: new Date().toISOString(), }); // 포커스 이동 완료 후 플래그 초기화 setShouldFocusSearchInput(false); - console.log('[DEBUG] setShouldFocusSearchInput(false) 호출 완료!', { + dlog('[DEBUG] setShouldFocusSearchInput(false) 호출 완료!', { timestamp: new Date().toISOString(), }); }, 500); - console.log('[DEBUG] 타이머 설정 완료 - 500ms 후 포커스 이동 예약됨', { + dlog('[DEBUG] 타이머 설정 완료 - 500ms 후 포커스 이동 예약됨', { timestamp: new Date().toISOString(), }); // Cleanup: 타이머 정리 return () => { - console.log('[DEBUG] shouldFocusSearchInput useEffect cleanup 실행', { + dlog('[DEBUG] shouldFocusSearchInput useEffect cleanup 실행', { timestamp: new Date().toISOString(), }); if (focusTimer) { if (DEBUG_MODE) { - console.log( - '[FOCUS] 🧹 SearchInputOverlay 포커스 관리 useEffect cleanup - 타이머 정리', - { - timestamp: new Date().toISOString(), - } - ); + dlog('[FOCUS] 🧹 SearchInputOverlay 포커스 관리 useEffect cleanup - 타이머 정리', { + timestamp: new Date().toISOString(), + }); } clearTimeout(focusTimer); } @@ -2149,7 +2114,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { } // ✅ INITIAL, VOICE_RESULT & SEARCH_RESULT 모드에서 TInputSimple 내부 포커스 활성화 onKeyDown={handleKeydown} onMouseDown={() => { - console.log( + dlog( '[DEBUG] [SearchPanel] TInputSimple에서 마우스 클릭 감지 → SearchInputOverlay 오픈' ); setIsSearchOverlayVisible(true); diff --git a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx index a80868ac..1a33691f 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx @@ -34,6 +34,7 @@ import { readLocalStorage, writeLocalStorage } from '../../../utils/helperMethod import TInputSimple, { ICONS, KINDS } from '../TInput/TInputSimple'; import ApiStatusDisplay from './ApiStatusDisplay'; import VoiceApiError from './modes/VoiceApiError'; +import { createDebugHelpers } from '../../../utils/debug'; import VoiceListening from './modes/VoiceListening'; import VoiceNotRecognized from './modes/VoiceNotRecognized'; import VoiceNotRecognizedCircle from './modes/VoiceNotRecognizedCircle'; @@ -56,6 +57,7 @@ const SpottableDebugButton = Spottable('div'); // Debug mode constant - 항상 디버그 화면 표시 const DEBUG_MODE = false; +const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE); // Voice overlay 모드 상수 export const VOICE_MODES = { @@ -150,7 +152,7 @@ const VoiceInputOverlay = ({ externalShopperHouseData = null, // 🎯 [포커스 충돌 해결] 외부 음성 검색 결과 데이터 }) => { if (DEBUG_MODE) { - console.log('🔄 [DEBUG] VoiceInputOverlay render - isVisible:', isVisible, 'mode:', mode); + dlog('🔄 [DEBUG] VoiceInputOverlay render - isVisible:', isVisible, 'mode:', mode); } const dispatch = useDispatch(); @@ -198,7 +200,7 @@ const VoiceInputOverlay = ({ const [webSpeechEventLogs, setWebSpeechEventLogs] = useState(() => { const persisted = readLocalStorage(VOICE_EVENT_LOGS_KEY, []); if (DEBUG_MODE) { - console.log('📚 [DEBUG] Loaded webSpeechEventLogs from localStorage:', persisted); + dlog('📚 [DEBUG] Loaded webSpeechEventLogs from localStorage:', persisted); } return persisted; }); @@ -222,7 +224,7 @@ const VoiceInputOverlay = ({ const [legacySearchHistory, setLegacySearchHistory] = useState(() => { const history = readLocalStorage(SEARCH_HISTORY_KEY, DEFAULT_SUGGESTIONS); if (DEBUG_MODE) { - console.log('📚 [DEBUG] Loaded legacy searchHistory from localStorage:', history); + dlog('📚 [DEBUG] Loaded legacy searchHistory from localStorage:', history); } return history; }); @@ -231,11 +233,11 @@ const VoiceInputOverlay = ({ // ⭐ isVisible prop 변경 추적 useEffect(() => { - console.log('👁️ [DEBUG][VoiceInputOverlay] isVisible prop changed to:', isVisible); + dlog('👁️ [DEBUG][VoiceInputOverlay] isVisible prop changed to:', isVisible); if (isVisible) { - console.log('📺 [DEBUG][VoiceInputOverlay] Overlay is now VISIBLE, mode:', currentMode); + dlog('📺 [DEBUG][VoiceInputOverlay] Overlay is now VISIBLE, mode:', currentMode); } else { - console.log('🙈 [DEBUG][VoiceInputOverlay] Overlay is now HIDDEN'); + dlog('🙈 [DEBUG][VoiceInputOverlay] Overlay is now HIDDEN'); } }, [isVisible, currentMode]); @@ -268,7 +270,7 @@ const VoiceInputOverlay = ({ }; if (DEBUG_MODE) { - console.log(`[WebSpeech Event] ${event}: ${details}`); + dlog(`[WebSpeech Event] ${event}: ${details}`); } setWebSpeechEventLogs((prev) => { @@ -281,11 +283,9 @@ const VoiceInputOverlay = ({ // ⭐ currentMode 변경 추적 useEffect(() => { - console.log('🔀 [DEBUG][VoiceInputOverlay] currentMode changed to:', currentMode); + dlog('🔀 [DEBUG][VoiceInputOverlay] currentMode changed to:', currentMode); if (isVisible) { - console.log( - `📍 [DEBUG][VoiceInputOverlay] Current state: isVisible=true, mode=${currentMode}` - ); + dlog(`📍 [DEBUG][VoiceInputOverlay] Current state: isVisible=true, mode=${currentMode}`); // 모드 변경 이벤트 로깅 addWebSpeechEventLog('MODE_CHANGE', `Mode switched to: ${currentMode}`); } @@ -296,7 +296,7 @@ const VoiceInputOverlay = ({ if (webSpeechEventLogs.length > 0) { writeLocalStorage(VOICE_EVENT_LOGS_KEY, webSpeechEventLogs); if (DEBUG_MODE) { - console.log( + dlog( '💾 [DEBUG] Saved webSpeechEventLogs to localStorage:', webSpeechEventLogs.length, 'logs' @@ -329,14 +329,14 @@ const VoiceInputOverlay = ({ writeLocalStorage(SEARCH_HISTORY_KEY, newHistory); if (DEBUG_MODE) { - console.log('💾 [VoiceInputOverlay.v2] Legacy search history updated:', newHistory); + dlog('💾 [VoiceInputOverlay.v2] Legacy search history updated:', newHistory); } return newHistory; }); if (DEBUG_MODE) { - console.log( + dlog( '🎤 [VoiceInputOverlay] Voice search added to history:', trimmedText, 'searchId:', @@ -383,12 +383,12 @@ const VoiceInputOverlay = ({ // 🎯 API 실패 감지 - APIERROR 모드로 전환 useEffect(() => { if (shopperHouseErrorPopup?.visible && shopperHouseErrorPopup?.type === 'API_FAILURE') { - console.log('[VoiceInputOverlay] 🚨 API 실패 감지 - APIERROR 모드로 전환'); + dlog('[VoiceInputOverlay] 🚨 API 실패 감지 - APIERROR 모드로 전환'); setCurrentMode(VOICE_MODES.APIERROR); // 3초 후 자동으로 PROMPT 모드로 복귀 const timer = setTimeout(() => { - console.log('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀'); + dlog('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀'); setCurrentMode(VOICE_MODES.PROMPT); }, 3000); @@ -398,15 +398,15 @@ const VoiceInputOverlay = ({ // 🎯 VoiceResponse 에러 핸들러 - 빈 query 처리 const handleVoiceResponseError = useCallback((error) => { - console.log('[VoiceInputOverlay] ⚠️ VoiceResponse 에러 수신:', error); + dlog('[VoiceInputOverlay] ⚠️ VoiceResponse 에러 수신:', error); if (error.type === 'EMPTY_QUERY') { - console.log('[VoiceInputOverlay] 🚨 빈 query 에러 - NOTRECOGNIZED 모드로 전환'); + dlog('[VoiceInputOverlay] 🚨 빈 query 에러 - NOTRECOGNIZED 모드로 전환'); setCurrentMode(VOICE_MODES.NOTRECOGNIZED); // 3초 후 자동으로 PROMPT 모드로 복귀 const timer = setTimeout(() => { - console.log('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (빈 query 에러)'); + dlog('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (빈 query 에러)'); setCurrentMode(VOICE_MODES.PROMPT); }, 3000); @@ -421,14 +421,14 @@ const VoiceInputOverlay = ({ useEffect(() => { shopperHouseSearchIdRef.current = shopperHouseSearchId; if (shopperHouseSearchId) { - console.log('[VoiceInput] 🔄 searchId ref 업데이트:', shopperHouseSearchId); + dlog('[VoiceInput] 🔄 searchId ref 업데이트:', shopperHouseSearchId); } }, [shopperHouseSearchId]); // 🔍 DEBUG: shopperHouseData 변경 추적 useEffect(() => { if (DEBUG_MODE) { - console.log('🔍 [DEBUG] shopperHouseData changed:', { + dlog('🔍 [DEBUG] shopperHouseData changed:', { shopperHouseData, refValue: shopperHouseDataRef.current, isVisible, @@ -440,7 +440,7 @@ const VoiceInputOverlay = ({ // 🔄 WebSpeech 에러 재시작 함수 const restartWebSpeech = useCallback(() => { if (DEBUG_MODE) { - console.log('[VoiceInput] 🔄 Restarting WebSpeech after error'); + dlog('[VoiceInput] 🔄 Restarting WebSpeech after error'); } // 이벤트 로그 기록 @@ -479,7 +479,7 @@ const VoiceInputOverlay = ({ // 약간의 지연 후 새로 시작 (안정성을 위해) setTimeout(() => { if (DEBUG_MODE) { - console.log('[VoiceInput] ✅ Restart complete - ready for new input'); + dlog('[VoiceInput] ✅ Restart complete - ready for new input'); } }, 300); }, [dispatch, onSearchChange, addWebSpeechEventLog]); @@ -489,7 +489,7 @@ const VoiceInputOverlay = ({ // Bubble 클릭으로 검색이 시작된 경우 WebSpeech 오류를 우회 if (isBubbleClickSearch) { if (DEBUG_MODE) { - console.log('[VoiceInput] 🔇 WebSpeech 오류 우회 (bubble click search)'); + dlog('[VoiceInput] 🔇 WebSpeech 오류 우회 (bubble click search)'); } return; } @@ -540,20 +540,20 @@ const VoiceInputOverlay = ({ // 음성 인식 중지 if (isListening) { - console.log('[VoiceInput] 🛑 Stopping listening due to error'); + dlog('[VoiceInput] 🛑 Stopping listening due to error'); stopListening(); } // 에러 모드로 전환 (NOTRECOGNIZED) - 사용자에게 에러 상태 표시 if (isVisible) { - console.log('[VoiceInput] 🔀 Switching to NOTRECOGNIZED mode due to error'); + dlog('[VoiceInput] 🔀 Switching to NOTRECOGNIZED mode due to error'); setCurrentMode(VOICE_MODES.NOTRECOGNIZED); setVoiceInputMode(null); // 일부 에러 타입은 자동 재시작 시도 const autoRetryErrors = ['network', 'aborted', 'service-not-allowed']; if (autoRetryErrors.includes(errorType)) { - console.log('[VoiceInput] 🔄 Auto-restarting for error type:', errorType); + dlog('[VoiceInput] 🔄 Auto-restarting for error type:', errorType); setTimeout(() => { restartWebSpeech(); }, 2000); // 2초 후 자동 재시작 @@ -640,7 +640,7 @@ const VoiceInputOverlay = ({ // ⚠️ 최적화: 실시간 상태 표시 제거, 결과만 표시 useEffect(() => { if (DEBUG_MODE) { - console.log('🔍 [DEBUG] shopperHouseError useEffect running:', { + dlog('🔍 [DEBUG] shopperHouseError useEffect running:', { isVisible, hasError: !!shopperHouseError, error: shopperHouseError, @@ -650,7 +650,7 @@ const VoiceInputOverlay = ({ // ✨ 초기화 중에는 오류 처리 안 함 if (isInitializingRef.current) { if (DEBUG_MODE) { - console.log('🔍 [DEBUG] Skipping error check - overlay is initializing'); + dlog('🔍 [DEBUG] Skipping error check - overlay is initializing'); } return; } @@ -658,7 +658,7 @@ const VoiceInputOverlay = ({ // 오류가 발생한 경우 - API 상태 표시 업데이트만 (팝업 대신) if (shopperHouseError && isVisible) { if (DEBUG_MODE) { - console.log('[VoiceInputOverlay] ❌ API error - showing result only:', shopperHouseError); + dlog('[VoiceInputOverlay] ❌ API error - showing result only:', shopperHouseError); } // ⚠️ 심각한 오류만 표시 (5xx 서버 오류, 커스텀 메시지) @@ -676,7 +676,7 @@ const VoiceInputOverlay = ({ // ShopperHouse API 응답 수신 시 overlay 닫기 및 API 상태 업데이트 useEffect(() => { if (DEBUG_MODE) { - console.log('🔍 [DEBUG] shopperHouseData useEffect running:', { + dlog('🔍 [DEBUG] shopperHouseData useEffect running:', { isVisible, hasData: !!shopperHouseData, refValue: shopperHouseDataRef.current, @@ -688,7 +688,7 @@ const VoiceInputOverlay = ({ // ✨ 초기화 중에는 close 로직 실행 안 함 (중복 닫기 방지) if (isInitializingRef.current) { if (DEBUG_MODE) { - console.log('🔍 [DEBUG] Skipping close check - overlay is initializing'); + dlog('🔍 [DEBUG] Skipping close check - overlay is initializing'); } return; } @@ -696,7 +696,7 @@ const VoiceInputOverlay = ({ // 이전 값과 비교하여 새로운 데이터가 들어왔을 때만 닫기 if (isVisible && shopperHouseData && shopperHouseData !== shopperHouseDataRef.current) { if (DEBUG_MODE) { - console.log('[VoiceInputOverlay.v2] ShopperHouse data received, closing overlay', { + dlog('[VoiceInputOverlay.v2] ShopperHouse data received, closing overlay', { oldRef: shopperHouseDataRef.current, newData: shopperHouseData, }); @@ -708,7 +708,7 @@ const VoiceInputOverlay = ({ setApiStatusMessage('Success'); if (DEBUG_MODE) { - console.log('[VoiceInputOverlay] 📍 API Status updated:', { + dlog('[VoiceInputOverlay] 📍 API Status updated:', { status: 'success', message: 'Success', }); @@ -723,7 +723,7 @@ const VoiceInputOverlay = ({ // STT 텍스트 수신 시 처리 // useEffect(() => { // if (lastSTTText && sttTimestamp && isVisible) { - // console.log('[VoiceInputOverlay.v2] STT text received in overlay:', lastSTTText); + // dlog('[VoiceInputOverlay.v2] STT text received in overlay:', lastSTTText); // // // 입력창에 텍스트 표시 (부모 컴포넌트로 전달) // if (onSearchChange) { @@ -745,7 +745,7 @@ const VoiceInputOverlay = ({ if (!ENABLE_WAKE_WORD) return; if (DEBUG_MODE) { - console.log('🎉 [VoiceInputOverlay.v2] Wake word detected! Switching to LISTENING mode'); + dlog('🎉 [VoiceInputOverlay.v2] Wake word detected! Switching to LISTENING mode'); } // 기존 타이머 정리 @@ -766,7 +766,7 @@ const VoiceInputOverlay = ({ // 15초 타이머 설정 listeningTimerRef.current = setTimeout(() => { if (DEBUG_MODE) { - console.log('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - PROMPT 모드로 복귀'); + dlog('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - PROMPT 모드로 복귀'); } setCurrentMode(VOICE_MODES.PROMPT); setVoiceInputMode(null); @@ -789,7 +789,7 @@ const VoiceInputOverlay = ({ if (detected) { if (DEBUG_MODE) { - console.log('🎉 [VoiceInputOverlay.v2] Wake word detected in PROMPT mode:', text); + dlog('🎉 [VoiceInputOverlay.v2] Wake word detected in PROMPT mode:', text); } handleWakeWordDetected(); } @@ -799,7 +799,7 @@ const VoiceInputOverlay = ({ useEffect(() => { if (isVisible && voiceVersion === VOICE_VERSION.WEB_SPEECH && isSupported === false) { if (DEBUG_MODE) { - console.log('⚠️ [VoiceInputOverlay.v2] WebSpeech not supported, switching to NOINIT mode'); + dlog('⚠️ [VoiceInputOverlay.v2] WebSpeech not supported, switching to NOINIT mode'); } setCurrentMode(VOICE_MODES.NOINIT); } @@ -808,7 +808,7 @@ const VoiceInputOverlay = ({ // 🎤 음성 입력 최종 처리 함수 (15초 타이머 & 3초 silence detection 공통 사용) const processFinalVoiceInput = useCallback( (source) => { - console.log(`[VoiceInput] 🏁 음성 입력 종료 (${source})`); + dlog(`[VoiceInput] 🏁 음성 입력 종료 (${source})`); // 모든 타이머 정리 및 ref 초기화 clearTimerRef(listeningTimerRef); @@ -833,9 +833,9 @@ const VoiceInputOverlay = ({ ? reduxFinalText.trim() : interimRefText; - console.log('[VoiceInput] ├─ Redux finalText:', reduxFinalText || '(없음)'); - console.log('[VoiceInput] ├─ interimRef:', interimRefText || '(없음)'); - console.log('[VoiceInput] └─ 사용할 텍스트:', finalText, `(길이: ${finalText.length})`); + dlog('[VoiceInput] ├─ Redux finalText:', reduxFinalText || '(없음)'); + dlog('[VoiceInput] ├─ interimRef:', interimRefText || '(없음)'); + dlog('[VoiceInput] └─ 사용할 텍스트:', finalText, `(길이: ${finalText.length})`); if (finalText && finalText.length >= 3) { // STT 텍스트 저장 @@ -851,11 +851,11 @@ const VoiceInputOverlay = ({ // ✨ ShopperHouse API 자동 호출 (2차 발화 시 searchId 포함) const query = finalText.trim(); - console.log('[VoiceInput] 📤 API 요청 전송'); - console.log('[VoiceInput] ├─ query:', query); - console.log('[VoiceInput] ├─ ref 값:', shopperHouseSearchIdRef.current); - console.log('[VoiceInput] ├─ currentSearchId:', currentSearchId); - console.log('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)'); + dlog('[VoiceInput] 📤 API 요청 전송'); + dlog('[VoiceInput] ├─ query:', query); + dlog('[VoiceInput] ├─ ref 값:', shopperHouseSearchIdRef.current); + dlog('[VoiceInput] ├─ currentSearchId:', currentSearchId); + dlog('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)'); // API 호출 이벤트 로깅 const searchIdInfo = currentSearchId @@ -878,14 +878,14 @@ const VoiceInputOverlay = ({ ); } else { // 입력이 없거나 너무 짧으면 NOTRECOGNIZED 모드로 전환 - console.log('[VoiceInput] ⚠️ 입력 없음 또는 너무 짧음 - NOTRECOGNIZED 모드로 전환'); - console.log('[VoiceInput] └─ finalText 길이:', finalText.length); + dlog('[VoiceInput] ⚠️ 입력 없음 또는 너무 짧음 - NOTRECOGNIZED 모드로 전환'); + dlog('[VoiceInput] └─ finalText 길이:', finalText.length); setCurrentMode(VOICE_MODES.NOTRECOGNIZED); setVoiceInputMode(null); // 3초 후 자동으로 PROMPT 모드로 복귀 const timer = setTimeout(() => { - console.log('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (NOTRECOGNIZED)'); + dlog('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (NOTRECOGNIZED)'); setCurrentMode(VOICE_MODES.PROMPT); }, 3000); @@ -913,13 +913,13 @@ const VoiceInputOverlay = ({ // currentMode === VOICE_MODES.LISTENING && // !isListening // ) { - // console.log('🎙️ [VoiceInputOverlay.v2] Auto-starting Web Speech API after mode change...'); + // dlog('🎙️ [VoiceInputOverlay.v2] Auto-starting Web Speech API after mode change...'); // startListening(); // // // 15초 타이머 설정 // clearTimerRef(listeningTimerRef); // listeningTimerRef.current = setTimeout(() => { - // console.log('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - WebSpeech 자동 종료'); + // dlog('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - WebSpeech 자동 종료'); // stopListening(); // setCurrentMode(VOICE_MODES.PROMPT); // setVoiceInputMode(null); @@ -961,7 +961,7 @@ const VoiceInputOverlay = ({ const trimmedText = interimText?.trim() || ''; if (!hasReached5Chars && trimmedText.length >= 5) { // 처음 5글자에 도달했을 때만 활성화 - console.log('[VoiceInput] 🎯 5글자 도달! SilenceCheck 활성화'); + dlog('[VoiceInput] 🎯 5글자 도달! SilenceCheck 활성화'); setHasReached5Chars(true); setIsSilenceCheckActive(true); } else if (hasReached5Chars && trimmedText.length < 5) { @@ -983,7 +983,7 @@ const VoiceInputOverlay = ({ // 3초 silence detection: 마지막 입력 후 3초 동안 추가 입력이 없으면 자동 종료 clearTimerRef(silenceDetectionTimerRef); silenceDetectionTimerRef.current = setTimeout(() => { - console.log('[VoiceInput] 🔇 3초 동안 입력 없음 - 자동 종료'); + dlog('[VoiceInput] 🔇 3초 동안 입력 없음 - 자동 종료'); addWebSpeechEventLog('SILENCE_3S', 'No input detected for 3 seconds - auto finish'); processFinalVoiceInput('3초 silence detection'); }, 3000); // 3초 @@ -1010,9 +1010,7 @@ const VoiceInputOverlay = ({ if (currentMode !== VOICE_MODES.PROMPT) return; if (DEBUG_MODE) { - console.log( - '🎙️ [VoiceInputOverlay.v2] Starting background listening for wake word detection' - ); + dlog('🎙️ [VoiceInputOverlay.v2] Starting background listening for wake word detection'); } // PROMPT 모드에서 백그라운드 리스닝 시작 @@ -1037,7 +1035,7 @@ const VoiceInputOverlay = ({ setSilenceSeconds(0); if (DEBUG_MODE) { - console.log('⏱️ [VoiceInputOverlay.v2] Starting countdown from 15'); + dlog('⏱️ [VoiceInputOverlay.v2] Starting countdown from 15'); } // 1초마다 카운트다운 @@ -1045,7 +1043,7 @@ const VoiceInputOverlay = ({ setCountdown((prev) => { const next = prev - 1; if (DEBUG_MODE) { - console.log('⏱️ [VoiceInputOverlay.v2] Countdown:', next); + dlog('⏱️ [VoiceInputOverlay.v2] Countdown:', next); } if (next <= 0) { clearIntervalRef(countdownIntervalRef); @@ -1080,7 +1078,7 @@ const VoiceInputOverlay = ({ setSilenceSeconds((prev) => { const next = prev + 1; if (DEBUG_MODE) { - console.log('🚦 [VoiceInput] Silence Detection Progress:', next); + dlog('🚦 [VoiceInput] Silence Detection Progress:', next); } if (next >= 3) { // 3초 도달하면 interval 정리 (타이머가 곧 종료됨) @@ -1098,11 +1096,11 @@ const VoiceInputOverlay = ({ // VoiceInputOverlay 언마운트 시에만 초기화 useEffect(() => { - console.log('[VoiceInput] 🚀 VoiceInputOverlay 마운트됨 (searchId 유지)'); + dlog('[VoiceInput] 🚀 VoiceInputOverlay 마운트됨 (searchId 유지)'); // Cleanup: 언마운트 시에만 초기화 return () => { - console.log('[VoiceInput] 🔚 VoiceInputOverlay 언마운트 - searchId 초기화'); + dlog('[VoiceInput] 🔚 VoiceInputOverlay 언마운트 - searchId 초기화'); // dispatch(clearShopperHouseData()); dispatch(clearSTTText()); }; @@ -1111,34 +1109,34 @@ const VoiceInputOverlay = ({ // Overlay가 열릴 때 포커스를 overlay 내부로 이동 useEffect(() => { if (DEBUG_MODE) { - console.log('👁️ [DEBUG] isVisible useEffect triggered - isVisible:', isVisible); + dlog('👁️ [DEBUG] isVisible useEffect triggered - isVisible:', isVisible); } if (isVisible) { if (DEBUG_MODE) { - console.log('✅ [DEBUG] ===== OVERLAY OPENING ====='); + dlog('✅ [DEBUG] ===== OVERLAY OPENING ====='); } // ✨ 초기화 시작 플래그 설정 (close 로직 일시 차단) isInitializingRef.current = true; if (DEBUG_MODE) { - console.log('🚧 [DEBUG] isInitializingRef set to TRUE - close logic DISABLED'); + dlog('🚧 [DEBUG] isInitializingRef set to TRUE - close logic DISABLED'); } // 현재 포커스된 요소 저장 lastFocusedElement.current = Spotlight.getCurrent(); - console.log('[lastFocusedElement] 🚀 Current focused element:', Spotlight.getCurrent()); + dlog('[lastFocusedElement] 🚀 Current focused element:', Spotlight.getCurrent()); // ✨ shopperHouseDataRef를 null로 초기화 shopperHouseDataRef.current = null; if (DEBUG_MODE) { - console.log('🔍 [DEBUG] shopperHouseDataRef initialized to NULL'); + dlog('🔍 [DEBUG] shopperHouseDataRef initialized to NULL'); } // 모드 초기화 (항상 prompt 모드로 시작) if (DEBUG_MODE) { - console.log('🔀 [DEBUG] Setting currentMode to:', mode); + dlog('🔀 [DEBUG] Setting currentMode to:', mode); } setCurrentMode(mode); setVoiceInputMode(null); @@ -1149,12 +1147,12 @@ const VoiceInputOverlay = ({ // ✨ 초기화 완료 - close 로직 활성화 isInitializingRef.current = false; if (DEBUG_MODE) { - console.log('✅ [DEBUG] Initialization complete, close logic ENABLED'); + dlog('✅ [DEBUG] Initialization complete, close logic ENABLED'); } }, 100); } else { if (DEBUG_MODE) { - console.log('❌ [DEBUG] ===== OVERLAY CLOSING ====='); + dlog('❌ [DEBUG] ===== OVERLAY CLOSING ====='); } // Overlay가 닫힐 때 원래 포커스 복원 및 상태 초기화 @@ -1168,7 +1166,8 @@ const VoiceInputOverlay = ({ // 🎯 [포커스 충돌 해결] VoiceInputOverlay를 통한 음성 검색 후에는 TInput으로 포커스 복원하지 않음 // SearchResults의 첫 번째 상품으로 포커스가 가도록 SearchPanel에 위임 // shopperHouseData가 있으면 (음성 검색 결과가 있으면) 포커스 복원하지 않음 - const hasVoiceSearchResult = shopperHouseData && shopperHouseData.results && shopperHouseData.results.length > 0; + const hasVoiceSearchResult = + shopperHouseData && shopperHouseData.results && shopperHouseData.results.length > 0; if (lastFocusedElement.current && !isVoiceResultMode && !hasVoiceSearchResult) { focusRestoreTimerRef.current = setTimeout(() => { Spotlight.focus(lastFocusedElement.current); @@ -1195,7 +1194,7 @@ const VoiceInputOverlay = ({ // 🔄 API 에러 재시작 함수 const handleApiErrorRestart = useCallback(() => { if (DEBUG_MODE) { - console.log('[VoiceInput] 🔄 Restarting after API error'); + dlog('[VoiceInput] 🔄 Restarting after API error'); } // ShopperHouse 에러 상태 정리 (Redux) @@ -1216,14 +1215,14 @@ const VoiceInputOverlay = ({ setIsBubbleClickSearch(false); // bubble 클릭 상태 초기화 if (DEBUG_MODE) { - console.log('[VoiceInput] ✅ API error restart complete - ready for new input'); + dlog('[VoiceInput] ✅ API error restart complete - ready for new input'); } }, [dispatch, onSearchChange]); // API 재시도 함수 const handleApiErrorRetry = useCallback(() => { if (DEBUG_MODE) { - console.log('[VoiceInput] 🔄 Retrying API call after error'); + dlog('[VoiceInput] 🔄 Retrying API call after error'); } // 이전 에러 상태 정리 @@ -1235,9 +1234,9 @@ const VoiceInputOverlay = ({ const currentSearchId = shopperHouseSearchIdRef.current; if (DEBUG_MODE) { - console.log('[VoiceInput] 🔄 Retrying API call'); - console.log('[VoiceInput] ├─ query:', query); - console.log('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)'); + dlog('[VoiceInput] 🔄 Retrying API call'); + dlog('[VoiceInput] ├─ query:', query); + dlog('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)'); } // 다시 API 호출 @@ -1267,7 +1266,7 @@ const VoiceInputOverlay = ({ // Overlay 닫기 핸들러 (모든 닫기 동작을 통합) const handleClose = useCallback(() => { if (DEBUG_MODE) { - console.log('🚪 [DEBUG] handleClose called - closing overlay'); + dlog('🚪 [DEBUG] handleClose called - closing overlay'); } // 1. 타이머 정리 @@ -1309,7 +1308,7 @@ const VoiceInputOverlay = ({ const handleSuggestionClick = useCallback( (suggestion) => { if (DEBUG_MODE) { - console.log('💡 [DEBUG] handleSuggestionClick called with:', suggestion); + dlog('💡 [DEBUG] handleSuggestionClick called with:', suggestion); } // Bubble 클릭으로 검색 시작 상태 설정 @@ -1339,9 +1338,9 @@ const VoiceInputOverlay = ({ const currentSearchId = shopperHouseSearchIdRef.current; if (DEBUG_MODE) { - console.log('🔍 [DEBUG] Calling ShopperHouse API from bubble click'); - console.log('[VoiceInput] ├─ query:', query); - console.log('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)'); + dlog('🔍 [DEBUG] Calling ShopperHouse API from bubble click'); + dlog('[VoiceInput] ├─ query:', query); + dlog('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)'); } // API 호출 이벤트 로깅 (Bubble 클릭) @@ -1379,7 +1378,7 @@ const VoiceInputOverlay = ({ // Input 창에서 API 호출 핸들러 (돋보기 아이콘 클릭 시에만) const handleSearchSubmit = useCallback(() => { if (DEBUG_MODE) { - console.log('[VoiceInputOverlay.v2] Search submit:', searchQuery); + dlog('[VoiceInputOverlay.v2] Search submit:', searchQuery); } if (searchQuery && searchQuery.trim()) { // ShopperHouse API 호출 @@ -1423,7 +1422,7 @@ const VoiceInputOverlay = ({ (e) => { if (e.key === 'Enter' || e.keyCode === 13) { e.preventDefault(); - console.log( + dlog( '[VoiceInputOverlay] 🔄 TInputSimple에서 Enter 키 감지 → SearchInputOverlay 전환 시작' ); // 🎯 SearchPanel의 콜백 호출 @@ -1439,7 +1438,7 @@ const VoiceInputOverlay = ({ // ⚠️ [251031] 마우스 클릭 시 프리징 발생 - 추후 원인 분석 후 활성화 필요 // const handleInputMouseDown = useCallback((e) => { // e.preventDefault(); - // console.log('[VoiceInputOverlay] 🖱️ TInputSimple에서 마우스 클릭 감지 → SearchInputOverlay 전환 시작'); + // dlog('[VoiceInputOverlay] 🖱️ TInputSimple에서 마우스 클릭 감지 → SearchInputOverlay 전환 시작'); // // 🎯 SearchPanel의 콜백 호출 // if (onTransitionToSearchInput) { // onTransitionToSearchInput(); @@ -1450,14 +1449,14 @@ const VoiceInputOverlay = ({ const handleInputModeChange = useCallback( (inputMode) => { if (DEBUG_MODE) { - console.log('[VoiceInputOverlay] TInput 모드 변경:', inputMode); + dlog('[VoiceInputOverlay] TInput 모드 변경:', inputMode); } if (inputMode === 'input') { // Input 모드로 전환되면 Overlay 닫기 // SearchPanel의 TInput으로 자연스럽게 전환됨 if (DEBUG_MODE) { - console.log('⌨️ [DEBUG] Input 모드로 전환됨 - VoiceInputOverlay 닫고 SearchPanel로 이동'); + dlog('⌨️ [DEBUG] Input 모드로 전환됨 - VoiceInputOverlay 닫고 SearchPanel로 이동'); } handleClose(); } @@ -1469,7 +1468,7 @@ const VoiceInputOverlay = ({ const handleToggleDashboard = useCallback(() => { setShowDashboard((prev) => !prev); if (DEBUG_MODE) { - console.log('🔧 [DEBUG] Dashboard toggled:', !showDashboard); + dlog('🔧 [DEBUG] Dashboard toggled:', !showDashboard); } }, [showDashboard]); @@ -1484,7 +1483,7 @@ const VoiceInputOverlay = ({ // 모드에 따른 컨텐츠 렌더링 - Memoized const renderModeContent = useMemo(() => { if (DEBUG_MODE) { - console.log( + dlog( '🎬 [DEBUG][VoiceInputOverlay] renderModeContent called', '| currentMode:', currentMode, @@ -1527,8 +1526,8 @@ const VoiceInputOverlay = ({ } // ✨ [DEBUG] Redux 상태 확인 로그 - console.log('[VoiceInput]-shopperHouseRelativeQueries'); - console.log( + dlog('[VoiceInput]-shopperHouseRelativeQueries'); + dlog( JSON.stringify( { hasRelativeQueries: hasRelativeQueries, @@ -1549,25 +1548,19 @@ const VoiceInputOverlay = ({ ); if (DEBUG_MODE) { - console.log( + dlog( '✅ [DEBUG][VoiceInputOverlay] MODE = PROMPT | Rendering VoicePromptScreen', hasRelativeQueries ? '(with relativeQueries)' : '(with legacySearchHistory)', promptSuggestions.length, 'suggestions' ); if (hasRelativeQueries) { - console.log( - '[DEBUG][VoiceInputOverlay] ├─ searchId:', - shopperHouseSearchId || '(없음)' - ); - console.log('[DEBUG][VoiceInputOverlay] ├─ relativeQueries:', promptSuggestions); - console.log('[DEBUG][VoiceInputOverlay] └─ promptTitle:', promptTitle); + dlog('[DEBUG][VoiceInputOverlay] ├─ searchId:', shopperHouseSearchId || '(없음)'); + dlog('[DEBUG][VoiceInputOverlay] ├─ relativeQueries:', promptSuggestions); + dlog('[DEBUG][VoiceInputOverlay] └─ promptTitle:', promptTitle); } else { - console.log('[DEBUG][VoiceInputOverlay] ├─ relativeQueries가 없음'); - console.log( - '[DEBUG][VoiceInputOverlay] └─ legacySearchHistory 사용:', - promptSuggestions - ); + dlog('[DEBUG][VoiceInputOverlay] ├─ relativeQueries가 없음'); + dlog('[DEBUG][VoiceInputOverlay] └─ legacySearchHistory 사용:', promptSuggestions); } } return ( @@ -1581,7 +1574,7 @@ const VoiceInputOverlay = ({ } case VOICE_MODES.LISTENING: if (DEBUG_MODE) { - console.log( + dlog( '🎤 [DEBUG][VoiceInputOverlay] MODE = LISTENING | Rendering VoiceListening (15초 타이머)' ); } @@ -1595,7 +1588,7 @@ const VoiceInputOverlay = ({ ); case VOICE_MODES.RESPONSE: if (DEBUG_MODE) { - console.log( + dlog( '💬 [DEBUG][VoiceInputOverlay] MODE = RESPONSE | Rendering VoiceResponse with text:', sttResponseText, 'isLoading: true (항상 로딩 애니메이션 표시)' @@ -1614,12 +1607,12 @@ const VoiceInputOverlay = ({ ); case VOICE_MODES.NOINIT: if (DEBUG_MODE) { - console.log('⚠️ [DEBUG][VoiceInputOverlay] MODE = NOINIT | Rendering VoiceNotRecognized'); + dlog('⚠️ [DEBUG][VoiceInputOverlay] MODE = NOINIT | Rendering VoiceNotRecognized'); } return ; case VOICE_MODES.NOTRECOGNIZED: if (DEBUG_MODE) { - console.log( + dlog( '❌ [DEBUG][VoiceInputOverlay] MODE = NOTRECOGNIZED | Rendering VoiceNotRecognized with error message' ); } @@ -1631,7 +1624,7 @@ const VoiceInputOverlay = ({ ); case VOICE_MODES.APIERROR: if (DEBUG_MODE) { - console.log( + dlog( '💥 [DEBUG][VoiceInputOverlay] MODE = APIERROR | Rendering VoiceApiError with error details', shopperHouseError ); @@ -1651,7 +1644,7 @@ const VoiceInputOverlay = ({ return ; default: { if (DEBUG_MODE) { - console.log('🔄 [DEBUG][VoiceInputOverlay] MODE = DEFAULT | Rendering VoicePromptScreen'); + dlog('🔄 [DEBUG][VoiceInputOverlay] MODE = DEFAULT | Rendering VoicePromptScreen'); } // default 케이스에서도 promptSuggestions 계산 const hasRelativeQueries = !!shopperHouseRelativeQueries; @@ -1712,8 +1705,8 @@ const VoiceInputOverlay = ({ const handleWebSpeechMicClick = useCallback( (e) => { if (DEBUG_MODE) { - console.log('🎤 [DEBUG] handleWebSpeechMicClick called, currentMode:', currentMode); - console.log('🎤 [DEBUG] CURRENT_WEBSPEECH_VERSION:', CURRENT_WEBSPEECH_VERSION); + dlog('🎤 [DEBUG] handleWebSpeechMicClick called, currentMode:', currentMode); + dlog('🎤 [DEBUG] CURRENT_WEBSPEECH_VERSION:', CURRENT_WEBSPEECH_VERSION); } // 이벤트 전파 방지 - dim 레이어의 onClick 실행 방지 @@ -1730,9 +1723,9 @@ const VoiceInputOverlay = ({ if (CURRENT_WEBSPEECH_VERSION === 'v1') { if (currentMode === VOICE_MODES.PROMPT) { // ✨ PROMPT 모드에서만 LISTENING으로 전환 가능 - console.log('\n🎤 ════════════════════════════════════════════════════════════════'); - console.log('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V1)'); - console.log('🎤 ════════════════════════════════════════════════════════════════\n'); + dlog('\n🎤 ════════════════════════════════════════════════════════════════'); + dlog('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V1)'); + dlog('🎤 ════════════════════════════════════════════════════════════════\n'); // 📋 단계별 처리: // 1. WebSpeech 완전 cleanup (이전 상태 제거) @@ -1743,56 +1736,56 @@ const VoiceInputOverlay = ({ // ============================================================ // 1️⃣ WebSpeech 완전 cleanup (이전 상태 제거) // ============================================================ - console.log('[VoiceInput] 📍 [STEP 1] WebSpeech Cleanup'); - console.log('[VoiceInput] ├─ Dispatching: cleanupWebSpeech()'); + dlog('[VoiceInput] 📍 [STEP 1] WebSpeech Cleanup'); + dlog('[VoiceInput] ├─ Dispatching: cleanupWebSpeech()'); dispatch(cleanupWebSpeech()); - console.log('[VoiceInput] └─ ✅ Cleanup dispatched\n'); + dlog('[VoiceInput] └─ ✅ Cleanup dispatched\n'); // ============================================================ // 2️⃣ Redux STT 데이터 초기화 (searchId는 Redux에서 유지됨) // ============================================================ - console.log('[VoiceInput] 📍 [STEP 2] STT Data Clear'); - console.log('[VoiceInput] ├─ Dispatching: clearSTTText()'); + dlog('[VoiceInput] 📍 [STEP 2] STT Data Clear'); + dlog('[VoiceInput] ├─ Dispatching: clearSTTText()'); dispatch(clearSTTText()); - console.log('[VoiceInput] ├─ Clearing interim text buffer'); + dlog('[VoiceInput] ├─ Clearing interim text buffer'); // ✅ TInput 초기화 if (onSearchChange) { onSearchChange({ value: '' }); - console.log('[VoiceInput] ├─ TInput cleared'); + dlog('[VoiceInput] ├─ TInput cleared'); } // ✅ Interim text ref 초기화 interimTextRef.current = ''; - console.log('[VoiceInput] ├─ Interim text ref cleared'); + dlog('[VoiceInput] ├─ Interim text ref cleared'); // 기존 타이머 정리 clearTimerRef(listeningTimerRef); clearTimerRef(silenceDetectionTimerRef); - console.log('[VoiceInput] ├─ Previous timers cleared'); + dlog('[VoiceInput] ├─ Previous timers cleared'); // UI 모드 업데이트 setVoiceInputMode(VOICE_INPUT_MODE.WEBSPEECH); setCurrentMode(VOICE_MODES.LISTENING); - console.log('[VoiceInput] ├─ UI mode set to WEBSPEECH / LISTENING'); + dlog('[VoiceInput] ├─ UI mode set to WEBSPEECH / LISTENING'); // ✅ LISTENING 모드 진입 시 로그 초기화 (새로운 음성 입력 시작) setWebSpeechEventLogs([]); writeLocalStorage(VOICE_EVENT_LOGS_KEY, []); - console.log('[VoiceInput] └─ ✅ Event logs cleared\n'); + dlog('[VoiceInput] └─ ✅ Event logs cleared\n'); if (DEBUG_MODE) { - console.log('🧹 [DEBUG] Cleared webSpeechEventLogs on mic click'); + dlog('🧹 [DEBUG] Cleared webSpeechEventLogs on mic click'); } // ============================================================ // 3️⃣ WebSpeech 재초기화 (약간의 지연 후) // ============================================================ - console.log('[VoiceInput] ⏳ [STEP 3] Waiting 100ms for Redux state sync...'); + dlog('[VoiceInput] ⏳ [STEP 3] Waiting 100ms for Redux state sync...'); const reinitializeWebSpeech = setTimeout(() => { - console.log('[VoiceInput] 📍 [STEP 3] WebSpeech Initialization'); - console.log('[VoiceInput] ├─ Dispatching: initializeWebSpeech()'); - console.log('[VoiceInput] │ └─ lang: en-US, continuous: true, interimResults: true'); + dlog('[VoiceInput] 📍 [STEP 3] WebSpeech Initialization'); + dlog('[VoiceInput] ├─ Dispatching: initializeWebSpeech()'); + dlog('[VoiceInput] │ └─ lang: en-US, continuous: true, interimResults: true'); // Redux 상태 업데이트를 기다리기 위해 약간의 지연 dispatch( @@ -1803,37 +1796,37 @@ const VoiceInputOverlay = ({ }) ); - console.log('[VoiceInput] └─ ✅ Initialize dispatched\n'); + dlog('[VoiceInput] └─ ✅ Initialize dispatched\n'); // ============================================================ // 4️⃣ WebSpeech 즉시 시작 // ============================================================ - console.log('[VoiceInput] 📍 [STEP 4] WebSpeech Start'); - console.log('[VoiceInput] ├─ Dispatching: startWebSpeech()'); + dlog('[VoiceInput] 📍 [STEP 4] WebSpeech Start'); + dlog('[VoiceInput] ├─ Dispatching: startWebSpeech()'); dispatch(startWebSpeech()); - console.log('[VoiceInput] └─ ✅ Start dispatched\n'); + dlog('[VoiceInput] └─ ✅ Start dispatched\n'); // ============================================================ // 5️⃣ 15초 타이머 설정 및 준비 완료 // ============================================================ - console.log('[VoiceInput] 📍 [STEP 5] Setup MaxListeningTime Timer'); - console.log('[VoiceInput] ├─ Setting 15-second listening timeout'); + dlog('[VoiceInput] 📍 [STEP 5] Setup MaxListeningTime Timer'); + dlog('[VoiceInput] ├─ Setting 15-second listening timeout'); listeningTimerRef.current = setTimeout(() => { - console.log('[VoiceInput] ⏲️ [TIMEOUT] 15-second max listening time reached'); + dlog('[VoiceInput] ⏲️ [TIMEOUT] 15-second max listening time reached'); addWebSpeechEventLog('TIMEOUT_15S', '15 second timeout reached - finishing input'); processFinalVoiceInput('15초 타임아웃'); }, 15000); // 15초 - console.log('[VoiceInput] ├─ ⏲️ Timer started (15000ms)'); - console.log('[VoiceInput] └─ ✅ Timer configured\n'); + dlog('[VoiceInput] ├─ ⏲️ Timer started (15000ms)'); + dlog('[VoiceInput] └─ ✅ Timer configured\n'); // ============================================================ // ✨ INITIALIZATION COMPLETE // ============================================================ - console.log('🎤 ════════════════════════════════════════════════════════════════'); - console.log('✨ [VoiceInput] 음성 인식 준비 완료! (Voice Recognition Ready!)'); - console.log('✨ [VoiceInput] Waiting for voice input... (max 15 seconds)'); - console.log('🎤 ════════════════════════════════════════════════════════════════\n'); + dlog('🎤 ════════════════════════════════════════════════════════════════'); + dlog('✨ [VoiceInput] 음성 인식 준비 완료! (Voice Recognition Ready!)'); + dlog('✨ [VoiceInput] Waiting for voice input... (max 15 seconds)'); + dlog('🎤 ════════════════════════════════════════════════════════════════\n'); }, 100); // Cleanup: 언마운트 시 타이머 정리 @@ -1843,7 +1836,7 @@ const VoiceInputOverlay = ({ } else { // listening 모드 또는 기타 모드에서 클릭 시 -> overlay 닫기 if (DEBUG_MODE) { - console.log('🎤 [DEBUG] Closing overlay (not in PROMPT mode)'); + dlog('🎤 [DEBUG] Closing overlay (not in PROMPT mode)'); } handleClose(); } @@ -1852,12 +1845,12 @@ const VoiceInputOverlay = ({ // V2: Promise 체인 비동기 방식 (미래 - 아직 구현 대기 중) // ============================================================ else if (CURRENT_WEBSPEECH_VERSION === 'v2') { - console.log('\n🎤 ════════════════════════════════════════════════════════════════'); - console.log('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V2)'); - console.log('🎤 ════════════════════════════════════════════════════════════════\n'); + dlog('\n🎤 ════════════════════════════════════════════════════════════════'); + dlog('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V2)'); + dlog('🎤 ════════════════════════════════════════════════════════════════\n'); if (DEBUG_MODE) { - console.log('🎤 [DEBUG] V2 flow triggered'); + dlog('🎤 [DEBUG] V2 flow triggered'); } // ⚠️ V2 로직은 여기에 구현됨 @@ -2135,7 +2128,7 @@ const VoiceInputOverlay = ({ inputFocus={inputFocused} onKeyDown={handleInputKeyDown} onMouseDown={() => { - console.log('[InputSimple] 마우스 클릭 감지'); + dlog('[InputSimple] 마우스 클릭 감지'); if (onTransitionToSearchInput) { onTransitionToSearchInput(); }