[251124] fix: Log정리-4

🕐 커밋 시간: 2025. 11. 24. 12:19:40

📊 변경 통계:
  • 총 파일: 6개
  • 추가: +283줄
  • 삭제: -255줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/mainActions.js
  ~ com.twin.app.shoptime/src/reducers/mainReducer.js
  ~ com.twin.app.shoptime/src/reducers/searchReducer.js
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx
  ~ com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx
  ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/actions/mainActions.js (javascript):
    🔄 Modified: clearSubCategory()
  📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx (javascript):
    🔄 Modified: Spottable()
  📄 com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx (javascript):
     Added: Spottable()
    🔄 Modified: clearAllTimers()

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
This commit is contained in:
2025-11-24 12:19:40 +09:00
parent 5c3324c120
commit b95628de24
6 changed files with 343 additions and 358 deletions

View File

@@ -5,13 +5,18 @@ import { CATEGORY_DATA_MAX_RESULTS_LIMIT, LOG_CONTEXT_NAME, LOG_MESSAGE_ID } fro
import * as HelperMethods from '../utils/helperMethods'; import * as HelperMethods from '../utils/helperMethods';
import { types } from './actionTypes'; import { types } from './actionTypes';
import { addReservation, changeAppStatus, deleteReservation } from './commonActions'; 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 //IF-LGSP-007
export const getMainLiveShow = (props) => (dispatch, getState) => { export const getMainLiveShow = (props) => (dispatch, getState) => {
const vodIncFlag = props?.vodIncFlag; const vodIncFlag = props?.vodIncFlag;
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('@@ getMainLiveShow onSuccess', response.data); dlog('@@ getMainLiveShow onSuccess', response.data);
dispatch({ dispatch({
type: types.GET_MAIN_LIVE_SHOW, type: types.GET_MAIN_LIVE_SHOW,
@@ -20,7 +25,7 @@ export const getMainLiveShow = (props) => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error('@@ getMainLiveShow onFail', error); derror('@@ getMainLiveShow onFail', error);
}; };
TAxios(dispatch, getState, 'get', URLS.GET_MAIN_LIVE_SHOW, { vodIncFlag }, {}, onSuccess, onFail); 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 { alamDispFlag, chanId, endDt, patnrId, patncNm, showId, showNm, strtDt } = props;
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('setMainLiveUpcomingAlarm onSuccess', response.data); dlog('setMainLiveUpcomingAlarm onSuccess', response.data);
if (alamDispFlag === 'Y') { if (alamDispFlag === 'Y') {
const convertedStrtDt = convertUtcToLocal(strtDt); const convertedStrtDt = convertUtcToLocal(strtDt);
@@ -72,7 +77,7 @@ export const setMainLiveUpcomingAlarm = (props) => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error('setMainLiveUpcomingAlarm onFail', error); derror('setMainLiveUpcomingAlarm onFail', error);
}; };
TAxios( TAxios(
@@ -94,7 +99,7 @@ export const getMainCategoryDetail = (props) => (dispatch, getState) => {
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } })); dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('getMainCategoryDetail onSuccess ', response.data); dlog('getMainCategoryDetail onSuccess ', response.data);
dispatch({ dispatch({
type: types.GET_PRODUCT_DETAIL, type: types.GET_PRODUCT_DETAIL,
@@ -105,7 +110,7 @@ export const getMainCategoryDetail = (props) => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error('getMainCategoryDetail onFail', error); derror('getMainCategoryDetail onFail', error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
}; };
@@ -125,7 +130,7 @@ export const getMainCategoryDetail = (props) => (dispatch, getState) => {
export const getMainCategoryShowDetail = (props) => (dispatch, getState) => { export const getMainCategoryShowDetail = (props) => (dispatch, getState) => {
const { patnrId, showId, curationId } = props; const { patnrId, showId, curationId } = props;
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('getMainCategoryShowDetail onSuccess ', response.data); dlog('getMainCategoryShowDetail onSuccess ', response.data);
dispatch({ dispatch({
type: types.GET_MAIN_CATEGORY_SHOW_DETAIL, type: types.GET_MAIN_CATEGORY_SHOW_DETAIL,
@@ -134,7 +139,7 @@ export const getMainCategoryShowDetail = (props) => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error('getMainCategoryShowDetail onFail', error); derror('getMainCategoryShowDetail onFail', error);
}; };
TAxios( TAxios(
@@ -165,7 +170,7 @@ export const getSubCategory =
lastSubCategoryParams && lastSubCategoryParams &&
JSON.stringify(lastSubCategoryParams) === JSON.stringify(params) JSON.stringify(lastSubCategoryParams) === JSON.stringify(params)
) { ) {
console.log('getSubCategory ignore patch'); dlog('getSubCategory ignore patch');
return; return;
} }
lastSubCategoryParams = { ...params }; lastSubCategoryParams = { ...params };
@@ -176,7 +181,7 @@ export const getSubCategory =
let currentKey = key; let currentKey = key;
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('getSubCategory onSuccess ', response.data); dlog('getSubCategory onSuccess ', response.data);
if (pageNo === 1) { if (pageNo === 1) {
getSubCategoryKey = new Date(); getSubCategoryKey = new Date();
@@ -220,7 +225,7 @@ export const getSubCategory =
const canRetry = nextRetryCount < SUB_CATEGORY_RETRY_LIMIT; const canRetry = nextRetryCount < SUB_CATEGORY_RETRY_LIMIT;
if (canRetry) { if (canRetry) {
console.warn('getSubCategory retry', { dwarn('getSubCategory retry', {
lgCatCd, lgCatCd,
pageNo, pageNo,
retryCount: nextRetryCount, retryCount: nextRetryCount,
@@ -232,7 +237,7 @@ export const getSubCategory =
return; return;
} }
console.error('getSubCategory onFail', error); derror('getSubCategory onFail', error);
if (pageNo === 1) { if (pageNo === 1) {
lastSubCategoryParams = {}; lastSubCategoryParams = {};
} }
@@ -253,7 +258,7 @@ export const getSubCategory =
export const continueGetSubCategory = (key, pageNo) => (dispatch, getState) => { export const continueGetSubCategory = (key, pageNo) => (dispatch, getState) => {
if (!lastSubCategoryParams) { if (!lastSubCategoryParams) {
// <<<<<<< HEAD // <<<<<<< HEAD
console.warn('No previous category parameters found'); dwarn('No previous category parameters found');
// ======= // =======
// console.warn("No previous category parameters found"); // console.warn("No previous category parameters found");
// >>>>>>> gitlab/develop // >>>>>>> gitlab/develop
@@ -295,7 +300,7 @@ const clearSubCategory = () => ({
// TOP20 영상 목록 조회 IF-LGSP-069 // TOP20 영상 목록 조회 IF-LGSP-069
export const getTop20Show = () => (dispatch, getState) => { export const getTop20Show = () => (dispatch, getState) => {
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('getTop20Show onSuccess ', response.data); dlog('getTop20Show onSuccess ', response.data);
dispatch({ dispatch({
type: types.GET_TOP_20_SHOW, type: types.GET_TOP_20_SHOW,
@@ -305,7 +310,7 @@ export const getTop20Show = () => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error('getTop20Show onFail', error); derror('getTop20Show onFail', error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
}; };
@@ -375,14 +380,14 @@ export const getMyFavoriteFlag = (params) => (dispatch, getState) => {
const { patnrId, prdtId } = params; const { patnrId, prdtId } = params;
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('getMyFavoriteFlag onSuccess ', response.data); dlog('getMyFavoriteFlag onSuccess ', response.data);
dispatch({ dispatch({
type: types.GET_MY_FAVORITE_FLAG, type: types.GET_MY_FAVORITE_FLAG,
payload: response.data.data, payload: response.data.data,
}); });
}; };
const onFail = (error) => { const onFail = (error) => {
console.error('getMyFavoriteFlag onFail', error); derror('getMyFavoriteFlag onFail', error);
}; };
TAxios( TAxios(
@@ -401,7 +406,7 @@ export const setMainLikeCategory = (params) => (dispatch, getState) => {
const { patnrId, prdtId } = params; const { patnrId, prdtId } = params;
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('setMainLikeCategory onSuccess ', response.data); dlog('setMainLikeCategory onSuccess ', response.data);
dispatch({ dispatch({
type: types.SET_MAIN_LIKE_CATEGORY, type: types.SET_MAIN_LIKE_CATEGORY,
payload: response.data.data, payload: response.data.data,
@@ -428,10 +433,10 @@ export const getHomeFullVideoInfo =
({ lgCatCd }) => ({ lgCatCd }) =>
(dispatch, getState) => { (dispatch, getState) => {
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('getHomeFullVideoInfo onSuccess', response.data.data.showInfos); dlog('getHomeFullVideoInfo onSuccess', response.data.data.showInfos);
// ✨ DEBUG: youmaylikeInfos 데이터 확인 // ✨ 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: response.data.data.youmaylikeInfos,
youmaylikeInfos_length: response.data.data.youmaylikeInfos?.length, youmaylikeInfos_length: response.data.data.youmaylikeInfos?.length,
liveChannelInfos_length: response.data.data.liveChannelInfos?.length, liveChannelInfos_length: response.data.data.liveChannelInfos?.length,
@@ -445,7 +450,7 @@ export const getHomeFullVideoInfo =
}; };
const onFail = (error) => { const onFail = (error) => {
console.error('getHomeFullVideoInfo onSuccess', error); derror('getHomeFullVideoInfo onSuccess', error);
}; };
TAxios( TAxios(

View File

@@ -1,5 +1,10 @@
import { types } from '../actions/actionTypes'; import { types } from '../actions/actionTypes';
import { CATEGORY_DATA_MAX_RESULTS_LIMIT } from '../utils/Config'; 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 = { const initialState = {
subCategoryData: {}, subCategoryData: {},
@@ -159,7 +164,7 @@ export const mainReducer = (state = initialState, action) => {
const { data, lgCatCd } = action.payload; const { data, lgCatCd } = action.payload;
// ✨ DEBUG: Reducer에서 youmaylikeInfos 저장 확인 // ✨ 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_length: data.youmaylikeInfos?.length,
youmaylikeInfos: data.youmaylikeInfos, youmaylikeInfos: data.youmaylikeInfos,
}); });
@@ -197,7 +202,7 @@ export const mainReducer = (state = initialState, action) => {
}; };
} }
case types.CLEAR_SHOPNOW_INFO: 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 { return {
...state, ...state,
fullVideolgCatCd: '', fullVideolgCatCd: '',

View File

@@ -1,4 +1,9 @@
import { types } from '../actions/actionTypes'; import { types } from '../actions/actionTypes';
import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const initialState = { const initialState = {
searchDatas: {}, searchDatas: {},
@@ -81,8 +86,8 @@ export const searchReducer = (state = initialState, action) => {
}; };
case types.RESET_VOICE_SEARCH: case types.RESET_VOICE_SEARCH:
console.log('[VoiceInput]-searchReducer-RESET_VOICE_SEARCH'); dlog('[VoiceInput]-searchReducer-RESET_VOICE_SEARCH');
console.log( dlog(
JSON.stringify( JSON.stringify(
{ {
action: 'RESET_VOICE_SEARCH', action: 'RESET_VOICE_SEARCH',
@@ -120,7 +125,7 @@ export const searchReducer = (state = initialState, action) => {
const newPreKey = action.payload?.results?.[0]?.searchId || 'null'; const newPreKey = action.payload?.results?.[0]?.searchId || 'null';
const oldPreKey = state.preShopperHouseData?.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 { return {
...state, ...state,
@@ -150,7 +155,16 @@ export const searchReducer = (state = initialState, action) => {
const preSortingType = state.preShopperHouseData?.results?.[0]?.sortingType || 'null'; const preSortingType = state.preShopperHouseData?.results?.[0]?.sortingType || 'null';
// [VoiceInput] Redux에 저장 로그 // [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 { return {
...state, ...state,
@@ -167,7 +181,7 @@ export const searchReducer = (state = initialState, action) => {
} }
case types.SET_SHOPPERHOUSE_ERROR: case types.SET_SHOPPERHOUSE_ERROR:
console.log('[VoiceInput] ❌ Redux shopperHouseError 저장:', action.payload); dlog('[VoiceInput] ❌ Redux shopperHouseError 저장:', action.payload);
return { return {
...state, ...state,
shopperHouseError: action.payload, shopperHouseError: action.payload,
@@ -177,7 +191,7 @@ export const searchReducer = (state = initialState, action) => {
}; };
case types.SHOW_SHOPPERHOUSE_ERROR: case types.SHOW_SHOPPERHOUSE_ERROR:
console.log('[ShopperHouse] 🔴 Redux shopperHouseErrorPopup 표시:', action.payload); dlog('[ShopperHouse] 🔴 Redux shopperHouseErrorPopup 표시:', action.payload);
return { return {
...state, ...state,
shopperHouseErrorPopup: { shopperHouseErrorPopup: {
@@ -190,7 +204,7 @@ export const searchReducer = (state = initialState, action) => {
}; };
case types.HIDE_SHOPPERHOUSE_ERROR: case types.HIDE_SHOPPERHOUSE_ERROR:
console.log('[ShopperHouse] ✅ Redux shopperHouseErrorPopup 숨김'); dlog('[ShopperHouse] ✅ Redux shopperHouseErrorPopup 숨김');
return { return {
...state, ...state,
shopperHouseErrorPopup: { shopperHouseErrorPopup: {
@@ -203,16 +217,13 @@ export const searchReducer = (state = initialState, action) => {
}; };
case types.CLEAR_SHOPPERHOUSE_DATA: case types.CLEAR_SHOPPERHOUSE_DATA:
console.log('[DEBUG] 🧹 Redux shopperHouseData 초기화 호출 - 호출 스택 추적:'); dlog('[DEBUG] 🧹 Redux shopperHouseData 초기화 호출 - 호출 스택 추적:');
console.log( dlog('[DEBUG] 호출 위치:', new Error().stack?.split('\n')[1]?.trim() || '(스택 추적 불가)');
'[DEBUG] 호출 위치:', dlog(
new Error().stack?.split('\n')[1]?.trim() || '(스택 추적 불가)'
);
console.log(
'[VoiceInput] 🧹 Redux shopperHouseData 초기화 (searchId & relativeQueries & preShopperHouseData는 유지)' '[VoiceInput] 🧹 Redux shopperHouseData 초기화 (searchId & relativeQueries & preShopperHouseData는 유지)'
); );
console.log('[VoiceInput]-searchReducer-CLEAR_SHOPPERHOUSE_DATA'); dlog('[VoiceInput]-searchReducer-CLEAR_SHOPPERHOUSE_DATA');
console.log( dlog(
JSON.stringify( JSON.stringify(
{ {
shopperHouseData_cleared: true, shopperHouseData_cleared: true,
@@ -239,7 +250,7 @@ export const searchReducer = (state = initialState, action) => {
// 🔽 검색 메인 데이터 처리 // 🔽 검색 메인 데이터 처리
case types.GET_SEARCH_MAIN: { 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; let resultData = null;
@@ -247,15 +258,15 @@ export const searchReducer = (state = initialState, action) => {
if (action.payload?.result) { if (action.payload?.result) {
// payload.result 구조 // payload.result 구조
resultData = action.payload.result; resultData = action.payload.result;
console.log('✅ [searchReducer] payload.result 구조 확인'); dlog('✅ [searchReducer] payload.result 구조 확인');
} else if (action.payload?.data?.result) { } else if (action.payload?.data?.result) {
// payload.data.result 구조 // payload.data.result 구조
resultData = action.payload.data.result; resultData = action.payload.data.result;
console.log('✅ [searchReducer] payload.data.result 구조 확인'); dlog('✅ [searchReducer] payload.data.result 구조 확인');
} else if (action.payload?.data) { } else if (action.payload?.data) {
// payload.data에 직접 데이터가 있는 경우 // payload.data에 직접 데이터가 있는 경우
resultData = action.payload.data; resultData = action.payload.data;
console.log('✅ [searchReducer] payload.data 직접 구조 확인'); dlog('✅ [searchReducer] payload.data 직접 구조 확인');
} }
if (!resultData) { if (!resultData) {
@@ -264,7 +275,7 @@ export const searchReducer = (state = initialState, action) => {
return state; return state;
} }
console.log('[searchReducer] ✅ GET_SEARCH_MAIN success'); dlog('[searchReducer] ✅ GET_SEARCH_MAIN success');
return { return {
...state, ...state,
@@ -278,7 +289,7 @@ export const searchReducer = (state = initialState, action) => {
} }
case types.CLEAR_SEARCH_MAIN_DATA: case types.CLEAR_SEARCH_MAIN_DATA:
console.log('[searchReducer] 🧹 searchMainData 초기화'); dlog('[searchReducer] 🧹 searchMainData 초기화');
return { return {
...state, ...state,
searchMainData: { searchMainData: {
@@ -291,7 +302,7 @@ export const searchReducer = (state = initialState, action) => {
// 🎯 [Phase 1] SearchPanel 모드 제어 명령 // 🎯 [Phase 1] SearchPanel 모드 제어 명령
case types.SWITCH_TO_SEARCH_INPUT_OVERLAY: 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, source: action.payload?.source,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });

View File

@@ -7,6 +7,12 @@ import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable'; 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_right from '../../../../../assets/images/icons';
import icon_arrow_dwon from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png'; 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'; import icon_shop_now from '../../../../../assets/images/player/icon_tabcontainer_shopnow.png';
@@ -74,7 +80,7 @@ export default function TabContainerV2({
// ✨ DEBUG: youmaylikeInfos 데이터 로그 // ✨ DEBUG: youmaylikeInfos 데이터 로그
useEffect(() => { useEffect(() => {
console.log('[DEBUG] TabContainerV2 - youmaylikeInfos:', { dlog('[DEBUG] TabContainerV2 - youmaylikeInfos:', {
exists: !!youmaylikeInfos, exists: !!youmaylikeInfos,
length: youmaylikeInfos?.length, length: youmaylikeInfos?.length,
data: youmaylikeInfos, data: youmaylikeInfos,

View File

@@ -1,37 +1,19 @@
// src/views/SearchPanel/SearchPanel.new.jsx // src/views/SearchPanel/SearchPanel.new.jsx
import React, { import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { import { useDispatch, useSelector } from 'react-redux';
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight'; import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable'; import Spottable from '@enact/spotlight/Spottable';
import micIcon from '../../../assets/images/searchpanel/image-mic.png'; import micIcon from '../../../assets/images/searchpanel/image-mic.png';
import hotPicksImage from '../../../assets/images/searchpanel/img-hotpicks.png'; import hotPicksImage from '../../../assets/images/searchpanel/img-hotpicks.png';
import hotPicksBrandImage import hotPicksBrandImage from '../../../assets/images/searchpanel/img-search-hotpicks.png';
from '../../../assets/images/searchpanel/img-search-hotpicks.png'; import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
import {
sendLogGNB,
sendLogTotalRecommend,
} from '../../actions/logActions';
import { getMyRecommandedKeyword } from '../../actions/myPageActions'; import { getMyRecommandedKeyword } from '../../actions/myPageActions';
import { import { popPanel, pushPanel, updatePanel } from '../../actions/panelActions';
popPanel,
pushPanel,
updatePanel,
} from '../../actions/panelActions';
import { import {
clearPanelCommand, clearPanelCommand,
clearShopperHouseData, clearShopperHouseData,
@@ -51,36 +33,27 @@ import {
// showWarningToast, // showWarningToast,
// } from '../../actions/toastActions'; // } from '../../actions/toastActions';
import TBody from '../../components/TBody/TBody'; import TBody from '../../components/TBody/TBody';
import TItemCardNew, { import TItemCardNew, { removeDotAndColon } from '../../components/TItemCard/TItemCard.new';
removeDotAndColon,
} from '../../components/TItemCard/TItemCard.new';
import TPanel from '../../components/TPanel/TPanel'; import TPanel from '../../components/TPanel/TPanel';
import TVerticalPagenator import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
from '../../components/TVerticalPagenator/TVerticalPagenator'; import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList';
import TVirtualGridList
from '../../components/TVirtualGridList/TVirtualGridList';
import usePanelHistory from '../../hooks/usePanelHistory/usePanelHistory'; import usePanelHistory from '../../hooks/usePanelHistory/usePanelHistory';
// import VirtualKeyboardContainer from "../../components/TToast/VirtualKeyboardContainer"; // import VirtualKeyboardContainer from "../../components/TToast/VirtualKeyboardContainer";
import usePrevious from '../../hooks/usePrevious'; import usePrevious from '../../hooks/usePrevious';
import { useSearchHistory } from '../../hooks/useSearchHistory'; import { useSearchHistory } from '../../hooks/useSearchHistory';
import { import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../utils/Config';
LOG_CONTEXT_NAME,
LOG_MENU,
LOG_MESSAGE_ID,
panel_names,
} from '../../utils/Config';
import NoSearchResults from './NoSearchResults/NoSearchResults'; import NoSearchResults from './NoSearchResults/NoSearchResults';
// import NoSearchResults from './NoSearchResults/NoSearchResults'; // import NoSearchResults from './NoSearchResults/NoSearchResults';
import SearchInputOverlay from './SearchInputOverlay'; import SearchInputOverlay from './SearchInputOverlay';
import css from './SearchPanel.new.module.less'; import css from './SearchPanel.new.module.less';
import SearchResultsNew from './SearchResults.new.v2'; import SearchResultsNew from './SearchResults.new.v2';
import TInputSimple, { import TInputSimple, { ICONS, KINDS } from './TInput/TInputSimple';
ICONS, import VoiceInputOverlay, { VOICE_MODES } from './VoiceInputOverlay/VoiceInputOverlay';
KINDS, import { createDebugHelpers } from '../../utils/debug';
} from './TInput/TInputSimple';
import VoiceInputOverlay, { // 디버그 헬퍼 설정
VOICE_MODES, const DEBUG_MODE = false;
} from './VoiceInputOverlay/VoiceInputOverlay'; const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
/** /**
* ✨ Mode-Based Architecture 도입 * ✨ Mode-Based Architecture 도입
@@ -240,7 +213,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// 🐛 [DEBUG] shopperHouseData 상태 변경 추적 (DEBUG_MODE가 true일 경우에만) // 🐛 [DEBUG] shopperHouseData 상태 변경 추적 (DEBUG_MODE가 true일 경우에만)
useEffect(() => { useEffect(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG] 📊 SearchPanel shopperHouseData 상태 변경:', { dlog('[DEBUG] 📊 SearchPanel shopperHouseData 상태 변경:', {
hasData: !!shopperHouseData, hasData: !!shopperHouseData,
dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0, dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0,
searchId: shopperHouseData?.results?.[0]?.searchId || '(없음)', searchId: shopperHouseData?.results?.[0]?.searchId || '(없음)',
@@ -256,7 +229,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// 🐛 [DEBUG] SearchPanel 마운트/언마운트 추적 (DEBUG_MODE가 true일 경우에만) // 🐛 [DEBUG] SearchPanel 마운트/언마운트 추적 (DEBUG_MODE가 true일 경우에만)
useEffect(() => { useEffect(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG] 🚀 SearchPanel 마운트됨 - 초기 shopperHouseData 상태:', { dlog('[DEBUG] 🚀 SearchPanel 마운트됨 - 초기 shopperHouseData 상태:', {
hasData: !!shopperHouseData, hasData: !!shopperHouseData,
dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0, dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0,
currentMode, currentMode,
@@ -266,7 +239,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
return () => { return () => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG] 🔚 SearchPanel 언마운트됨 - shopperHouseData 상태:', { dlog('[DEBUG] 🔚 SearchPanel 언마운트됨 - shopperHouseData 상태:', {
hasData: !!shopperHouseData, hasData: !!shopperHouseData,
dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0, dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
@@ -278,7 +251,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// 🐛 [DEBUG] isOnTop 상태 변경 추적 (DetailPanel <-> SearchPanel 전환, DEBUG_MODE가 true일 경우에만) // 🐛 [DEBUG] isOnTop 상태 변경 추적 (DetailPanel <-> SearchPanel 전환, DEBUG_MODE가 true일 경우에만)
useEffect(() => { useEffect(() => {
if (isOnTopRef.current !== isOnTop && DEBUG_MODE) { if (isOnTopRef.current !== isOnTop && DEBUG_MODE) {
console.log('[DEBUG] 🔄 SearchPanel isOnTop 상태 변경:', { dlog('[DEBUG] 🔄 SearchPanel isOnTop 상태 변경:', {
from: isOnTopRef.current, from: isOnTopRef.current,
to: isOnTop, to: isOnTop,
shopperHouseData_preserved: !!shopperHouseData, shopperHouseData_preserved: !!shopperHouseData,
@@ -317,7 +290,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// API 실패 시 fallback reference 초기화 // API 실패 시 fallback reference 초기화
useEffect(() => { useEffect(() => {
if (shopperHouseErrorPopup?.visible && shopperHouseErrorPopup?.type === 'API_FAILURE') { if (shopperHouseErrorPopup?.visible && shopperHouseErrorPopup?.type === 'API_FAILURE') {
console.log('[SearchPanel] 🧹 API 실패 감지 - fallbackShopperHouseData 초기화'); dlog('[SearchPanel] 🧹 API 실패 감지 - fallbackShopperHouseData 초기화');
shopperHouseDataRef.current = null; shopperHouseDataRef.current = null;
} }
}, [shopperHouseErrorPopup?.visible, shopperHouseErrorPopup?.type]); }, [shopperHouseErrorPopup?.visible, shopperHouseErrorPopup?.type]);
@@ -356,7 +329,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
currentPanel?.panelInfo?.currentSpot; currentPanel?.panelInfo?.currentSpot;
if (DEBUG_MODE && isReturning) { if (DEBUG_MODE && isReturning) {
console.log('[FOCUS] 🎯 DetailPanel 복귀 감지:', { dlog('[FOCUS] 🎯 DetailPanel 복귀 감지:', {
current: currentPanel?.panelName, current: currentPanel?.panelName,
previous: previousPanel?.panelName, previous: previousPanel?.panelName,
action: currentPanel?.action, action: currentPanel?.action,
@@ -449,9 +422,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// ✨ [Phase 4] Enter/OK 키 처리 - SearchInputOverlay 표시 // ✨ [Phase 4] Enter/OK 키 처리 - SearchInputOverlay 표시
if (e.key === 'Enter' || e.keyCode === 13) { if (e.key === 'Enter' || e.keyCode === 13) {
console.log( dlog('[DEBUG] [SearchPanel] TInputSimple에서 Enter/OK 키 감지 → SearchInputOverlay 오픈');
'[DEBUG] [SearchPanel] TInputSimple에서 Enter/OK 키 감지 → SearchInputOverlay 오픈'
);
e.preventDefault(); e.preventDefault();
// ✨ [Phase 6] SearchInputOverlay 오픈 후 자동으로 입력 준비 완료 // ✨ [Phase 6] SearchInputOverlay 오픈 후 자동으로 입력 준비 완료
@@ -525,7 +496,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// * 0hun: input에 포커스 발생하여 가상 키보드 활성 시, `isInputModeActive` 상태 Boolean 값 설정 // * 0hun: input에 포커스 발생하여 가상 키보드 활성 시, `isInputModeActive` 상태 Boolean 값 설정
// */ // */
// const handleInputModeChange = useCallback((isActive) => { // const handleInputModeChange = useCallback((isActive) => {
// console.log( // dlog(
// "[SearchPanel] TInput 입력 모드:", // "[SearchPanel] TInput 입력 모드:",
// isActive ? "활성화 (키보드 표시)" : "비활성화 (키보드 숨김)" // isActive ? "활성화 (키보드 표시)" : "비활성화 (키보드 숨김)"
// ); // );
@@ -613,7 +584,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
} }
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'🎤 [DEBUG][SearchPanel] openVoiceOverlay called, current isVoiceOverlayVisible:', '🎤 [DEBUG][SearchPanel] openVoiceOverlay called, current isVoiceOverlayVisible:',
isVoiceOverlayVisible isVoiceOverlayVisible
); );
@@ -636,7 +607,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
*/ */
const onCancel = useCallback(() => { const onCancel = useCallback(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-onCancel called', { dlog('[DEBUG]-onCancel called', {
isOnTop: isOnTopRef.current, isOnTop: isOnTopRef.current,
isVoiceOverlayVisible, isVoiceOverlayVisible,
isSearchOverlayVisible, isSearchOverlayVisible,
@@ -649,7 +620,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
if (!isOnTopRef.current) { if (!isOnTopRef.current) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-onCancel: isOnTopRef is false, returning'); dlog('[DEBUG]-onCancel: isOnTopRef is false, returning');
} }
return; return;
} }
@@ -657,7 +628,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// VoiceInputOverlay가 열려있으면 먼저 닫기 // VoiceInputOverlay가 열려있으면 먼저 닫기
if (isVoiceOverlayVisible) { if (isVoiceOverlayVisible) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-onCancel: closing VoiceInputOverlay'); dlog('[DEBUG]-onCancel: closing VoiceInputOverlay');
} }
setIsVoiceOverlayVisible(false); setIsVoiceOverlayVisible(false);
return; return;
@@ -666,7 +637,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// SearchInputOverlay가 열려있으면 먼저 닫기 // SearchInputOverlay가 열려있으면 먼저 닫기
if (isSearchOverlayVisible) { if (isSearchOverlayVisible) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-onCancel: closing SearchInputOverlay'); dlog('[DEBUG]-onCancel: closing SearchInputOverlay');
} }
handleSearchOverlayClose(); handleSearchOverlayClose();
return; return;
@@ -674,7 +645,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// ✨ [Phase 5] VOICE_RESULT 모드에서 ESC/뒤로가기 누르면 INITIAL 모드로 돌아가기 // ✨ [Phase 5] VOICE_RESULT 모드에서 ESC/뒤로가기 누르면 INITIAL 모드로 돌아가기
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-VOICE_RESULT check:', { dlog('[DEBUG]-VOICE_RESULT check:', {
currentMode, currentMode,
isVoiceResultMode: currentMode === SEARCH_PANEL_MODES.VOICE_RESULT, isVoiceResultMode: currentMode === SEARCH_PANEL_MODES.VOICE_RESULT,
VOICE_RESULT_value: 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 (currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'[DEBUG]-VOICE_RESULT: Clearing ShopperHouse data (searchId will be preserved for 2nd search)' '[DEBUG]-VOICE_RESULT: Clearing ShopperHouse data (searchId will be preserved for 2nd search)'
); );
console.log('[VoiceInput]-SearchPanel-onCancel-VOICE_RESULT'); dlog('[VoiceInput]-SearchPanel-onCancel-VOICE_RESULT');
console.log('[VoiceInput] 🧹 VOICE_RESULT 모드에서 ESC 누름 - clearShopperHouseData 호출'); dlog('[VoiceInput] 🧹 VOICE_RESULT 모드에서 ESC 누름 - clearShopperHouseData 호출');
} }
// 🎯 [포커스 로직 통합] 포커스는 상태 변경에 의해 자동으로 처리됨 // 🎯 [포커스 로직 통합] 포커스는 상태 변경에 의해 자동으로 처리됨
setIsShopperHousePending(false); setIsShopperHousePending(false);
@@ -696,17 +667,17 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
} }
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-onCancel: normal cancel logic', { searchQuery }); dlog('[DEBUG]-onCancel: normal cancel logic', { searchQuery });
} }
if (searchQuery === null || searchQuery === '') { if (searchQuery === null || searchQuery === '') {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-onCancel: popping panel'); dlog('[DEBUG]-onCancel: popping panel');
} }
dispatch(popPanel(panel_names.SEARCH_PANEL)); dispatch(popPanel(panel_names.SEARCH_PANEL));
} else { } else {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-onCancel: resetting search query'); dlog('[DEBUG]-onCancel: resetting search query');
} }
setSearchQuery(''); setSearchQuery('');
// 🎯 [포커스 로직 통합] 포커스는 상태 변경(searchQuery)에 의해 자동으로 처리됨 // 🎯 [포커스 로직 통합] 포커스는 상태 변경(searchQuery)에 의해 자동으로 처리됨
@@ -820,7 +791,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
const analyzeCurrentScenario = useCallback(() => { const analyzeCurrentScenario = useCallback(() => {
// DEBUG: 모든 기본 상태값 출력 // DEBUG: 모든 기본 상태값 출력
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG] analyzeCurrentScenario 호출됨:', { dlog('[DEBUG] analyzeCurrentScenario 호출됨:', {
// 🎯 기존 isOnTop과 usePanelHistory의 isOnTop 비교 // 🎯 기존 isOnTop과 usePanelHistory의 isOnTop 비교
propIsOnTop: isOnTop, propIsOnTop: isOnTop,
historyIsOnTop: currentIsOnTop, historyIsOnTop: currentIsOnTop,
@@ -845,8 +816,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
if (isReturningFromDetailPanel) { if (isReturningFromDetailPanel) {
const currentSpot = currentPanel?.panelInfo?.currentSpot; const currentSpot = currentPanel?.panelInfo?.currentSpot;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[Focus] usePanelHistory로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동'); dlog('[Focus] usePanelHistory로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동');
console.log('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (usePanelHistory)', { dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (usePanelHistory)', {
currentSpot, currentSpot,
mode: currentMode, mode: currentMode,
fromSearchResult: currentMode === SEARCH_PANEL_MODES.SEARCH_RESULT, fromSearchResult: currentMode === SEARCH_PANEL_MODES.SEARCH_RESULT,
@@ -862,7 +833,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// 🎯 [개선된 fallback] usePanelHistory의 isOnTop 정보 활용 // 🎯 [개선된 fallback] usePanelHistory의 isOnTop 정보 활용
// DetailPanel에서 방금 복귀한 상황 (usePanelHistory가 없을 경우를 대비) // DetailPanel에서 방금 복귀한 상황 (usePanelHistory가 없을 경우를 대비)
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG] 개선된 DETAIL_PANEL_RETURN 조건 확인 (fallback):', { dlog('[DEBUG] 개선된 DETAIL_PANEL_RETURN 조건 확인 (fallback):', {
// 🎯 여러 isOnTop 소스 비교 // 🎯 여러 isOnTop 소스 비교
propIsOnTop: isOnTop, propIsOnTop: isOnTop,
historyIsOnTop: currentIsOnTop, historyIsOnTop: currentIsOnTop,
@@ -889,8 +860,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
) { ) {
const usedHistoryOnTop = currentIsOnTop && isOnTopChange?.becameOnTop; const usedHistoryOnTop = currentIsOnTop && isOnTopChange?.becameOnTop;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[Focus] 개선된 방식으로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동'); dlog('[Focus] 개선된 방식으로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동');
console.log('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (improved fallback)', { dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (improved fallback)', {
currentSpot: panelInfo.currentSpot, currentSpot: panelInfo.currentSpot,
mode: currentMode, mode: currentMode,
fromSearchResult: currentMode === SEARCH_PANEL_MODES.SEARCH_RESULT, fromSearchResult: currentMode === SEARCH_PANEL_MODES.SEARCH_RESULT,
@@ -908,7 +879,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// - 위의 DETAIL_PANEL_RETURN이 아닌 경우 (= currentSpot이 없거나 모드가 검색 결과 아님) // - 위의 DETAIL_PANEL_RETURN이 아닌 경우 (= currentSpot이 없거나 모드가 검색 결과 아님)
if (isOnTop && !isOnTopRef.current) { if (isOnTop && !isOnTopRef.current) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 🎯 Scenario: INITIAL_OPEN', { dlog('[FOCUS] 🎯 Scenario: INITIAL_OPEN', {
currentSpot: panelInfo?.currentSpot, currentSpot: panelInfo?.currentSpot,
mode: currentMode, mode: currentMode,
}); });
@@ -927,7 +898,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
currentModeRef.current !== SEARCH_PANEL_MODES.SEARCH_RESULT currentModeRef.current !== SEARCH_PANEL_MODES.SEARCH_RESULT
) { ) {
if (DEBUG_MODE) { 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, themeCount: searchDatas?.theme?.length || 0,
itemCount: searchDatas?.item?.length || 0, itemCount: searchDatas?.item?.length || 0,
showCount: searchDatas?.show?.length || 0, showCount: searchDatas?.show?.length || 0,
@@ -948,10 +919,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
currentMode === SEARCH_PANEL_MODES.VOICE_RESULT && currentMode === SEARCH_PANEL_MODES.VOICE_RESULT &&
shopperHouseData && shopperHouseData &&
// 🎯 [개선] 모드 변경 OR 새로운 데이터 도착 감지 // 🎯 [개선] 모드 변경 OR 새로운 데이터 도착 감지
(currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT || shopperHouseDataRef.current !== shopperHouseData) (currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT ||
shopperHouseDataRef.current !== shopperHouseData)
) { ) {
if (DEBUG_MODE) { 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, itemCount: shopperHouseData?.results?.[0]?.docs?.length || 0,
prevMode: currentModeRef.current, prevMode: currentModeRef.current,
nextMode: currentMode, nextMode: currentMode,
@@ -966,7 +938,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// Voice Overlay가 닫힌 상황 // Voice Overlay가 닫힌 상황
if (!isVoiceOverlayVisible && isVoiceOverlayVisibleRef.current) { if (!isVoiceOverlayVisible && isVoiceOverlayVisibleRef.current) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 🎯 Scenario: VOICE_OVERLAY_CLOSED', { dlog('[FOCUS] 🎯 Scenario: VOICE_OVERLAY_CLOSED', {
hasShopperHouseData: !!shopperHouseData, hasShopperHouseData: !!shopperHouseData,
}); });
} }
@@ -974,7 +946,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// 이렇게 하면 VOICE_OVERLAY_CLOSED 시나리오에서 TInput으로 가는 것을 방지 // 이렇게 하면 VOICE_OVERLAY_CLOSED 시나리오에서 TInput으로 가는 것을 방지
if (shopperHouseData && currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) { if (shopperHouseData && currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) {
if (DEBUG_MODE) { 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'; return 'NEW_SEARCH_LOADED';
} }
@@ -986,7 +958,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// - 검색이 수행되지 않았거나 SearchPanel이 SEARCH_RESULT 모드가 아닌 경우 // - 검색이 수행되지 않았거나 SearchPanel이 SEARCH_RESULT 모드가 아닌 경우
if (!isSearchOverlayVisible && isSearchOverlayVisibleRef.current) { if (!isSearchOverlayVisible && isSearchOverlayVisibleRef.current) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED', { dlog('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED', {
isSearchOverlayVisible, isSearchOverlayVisible,
prevIsSearchOverlayVisible: isSearchOverlayVisibleRef.current, prevIsSearchOverlayVisible: isSearchOverlayVisibleRef.current,
currentMode, currentMode,
@@ -1040,25 +1012,25 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
if (isReturningFromDetailPanel && currentPanel?.panelInfo?.currentSpot) { if (isReturningFromDetailPanel && currentPanel?.panelInfo?.currentSpot) {
currentSpot = currentPanel.panelInfo.currentSpot; currentSpot = currentPanel.panelInfo.currentSpot;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 🎯 usePanelHistory currentSpot 사용:', currentSpot); dlog('[FOCUS] 🎯 usePanelHistory currentSpot 사용:', currentSpot);
} }
} }
// 2. fallback: 기존 panelInfo.currentSpot 사용 // 2. fallback: 기존 panelInfo.currentSpot 사용
else if (panelInfo?.currentSpot) { else if (panelInfo?.currentSpot) {
currentSpot = panelInfo.currentSpot; currentSpot = panelInfo.currentSpot;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 🔄 fallback으로 panelInfo.currentSpot 사용:', currentSpot); dlog('[FOCUS] 🔄 fallback으로 panelInfo.currentSpot 사용:', currentSpot);
} }
} }
if (currentSpot && currentSpot.startsWith('searchItemContents')) { if (currentSpot && currentSpot.startsWith('searchItemContents')) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 🎯 DETAIL_PANEL_RETURN: 이전 상품으로 포커스 복원:', currentSpot); dlog('[FOCUS] 🎯 DETAIL_PANEL_RETURN: 이전 상품으로 포커스 복원:', currentSpot);
} }
return currentSpot; return currentSpot;
} else { } else {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'[FOCUS] ⚠️ DETAIL_PANEL_RETURN: currentSpot이 유효하지 않음, fallback으로 이동:', '[FOCUS] ⚠️ DETAIL_PANEL_RETURN: currentSpot이 유효하지 않음, fallback으로 이동:',
{ {
currentSpot, currentSpot,
@@ -1094,7 +1066,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// SearchInputOverlay에서 검색을 실행하면 isSearchOverlayVisible이 false로 설정되고 // SearchInputOverlay에서 검색을 실행하면 isSearchOverlayVisible이 false로 설정되고
// 동시에 검색 결과에 따라 모드가 변경되므로, 이 케이스는 검색어 선택 후 닫을 때만 발생 // 동시에 검색 결과에 따라 모드가 변경되므로, 이 케이스는 검색어 선택 후 닫을 때만 발생
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED - TInputSimple으로 포커스'); dlog('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED - TInputSimple으로 포커스');
} }
return SPOTLIGHT_IDS.SEARCH_INPUT_BOX; return SPOTLIGHT_IDS.SEARCH_INPUT_BOX;
@@ -1118,7 +1090,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
*/ */
const handleTransitionToSearchInput = useCallback(() => { const handleTransitionToSearchInput = useCallback(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[SearchPanel] 🔄 handleTransitionToSearchInput 호출'); dlog('[SearchPanel] 🔄 handleTransitionToSearchInput 호출');
} }
// Redux Thunk 액션으로 모든 전환 로직 처리 // Redux Thunk 액션으로 모든 전환 로직 처리
@@ -1135,12 +1107,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
* Search overlay close handler * Search overlay close handler
*/ */
const handleSearchOverlayClose = useCallback(() => { const handleSearchOverlayClose = useCallback(() => {
console.log('[DEBUG] 🚪 handleSearchOverlayClose 호출됨 - 직접 확인!', { dlog('[DEBUG] 🚪 handleSearchOverlayClose 호출됨 - 직접 확인!', {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG] 🚪 SearchInputOverlay closing'); dlog('[DEBUG] 🚪 SearchInputOverlay closing');
} }
const hasSearchResults = const hasSearchResults =
@@ -1149,11 +1121,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
(searchDatas?.show?.length || 0) > 0; (searchDatas?.show?.length || 0) > 0;
// 🎯 SearchInputOverlay 닫힘 후 TInputSimple으로 포커스 이동을 위한 플래그 설정 // 🎯 SearchInputOverlay 닫힘 후 TInputSimple으로 포커스 이동을 위한 플래그 설정
console.log('[DEBUG] setShouldFocusSearchInput(true) 설정 직전', { dlog('[DEBUG] setShouldFocusSearchInput(true) 설정 직전', {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
setShouldFocusSearchInput(true); setShouldFocusSearchInput(true);
console.log('[DEBUG] setShouldFocusSearchInput(true) 설정됨!', { dlog('[DEBUG] setShouldFocusSearchInput(true) 설정됨!', {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
@@ -1171,7 +1143,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
*/ */
const handleVoiceOverlayClose = useCallback(() => { const handleVoiceOverlayClose = useCallback(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'🚪 [DEBUG][SearchPanel] handleVoiceOverlayClose called, setting isVoiceOverlayVisible to FALSE' '🚪 [DEBUG][SearchPanel] handleVoiceOverlayClose called, setting isVoiceOverlayVisible to FALSE'
); );
} }
@@ -1213,7 +1185,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// Redux state 업데이트를 위해 약간의 지연 후 API 호출 // Redux state 업데이트를 위해 약간의 지연 후 API 호출
setTimeout(() => { setTimeout(() => {
console.log('[HowAboutThese] 🔄 Redux 업데이트 후 API 호출'); dlog('[HowAboutThese] 🔄 Redux 업데이트 후 API 호출');
dispatch(getShopperHouseSearch(trimmedQuery, shopperHouseSearchId)); dispatch(getShopperHouseSearch(trimmedQuery, shopperHouseSearchId));
}, 50); // 50ms 지연 }, 50); // 50ms 지연
@@ -1337,40 +1309,40 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
[hotPicksForYou, dispatch, SafeImage] [hotPicksForYou, dispatch, SafeImage]
); );
const handleClick = useCallback((patnrId, prdtId) => { const handleClick = useCallback(
dispatch( (patnrId, prdtId) => {
pushPanel({ dispatch(
name: panel_names.DETAIL_PANEL, pushPanel({
panelInfo: { patnrId, prdtId }, name: panel_names.DETAIL_PANEL,
}) panelInfo: { patnrId, prdtId },
); })
},[dispatch]) );
},
[dispatch]
);
const renderTsvItem = useCallback( const renderTsvItem = useCallback(
({ index, ...rest }) => { ({ index, ...rest }) => {
const { offerInfo, prdtId, imgUrl, patnrId, prdtNm, priceInfo } = const { offerInfo, prdtId, imgUrl, patnrId, prdtNm, priceInfo } = tsvInfo[index];
tsvInfo[index];
return (
<TItemCardNew
imageAlt={prdtNm}
imageSource={imgUrl}
onClick={()=>{handleClick(patnrId,prdtId)}}
offerInfo={offerInfo}
priceInfo={priceInfo}
productId={prdtId}
productName={prdtNm}
spotlightId={
"searchMain-tsvInfo-spotlightId-" + removeDotAndColon(prdtId)
}
{...rest}
/>
);
},
[tsvInfo,handleClick]
);
return (
<TItemCardNew
imageAlt={prdtNm}
imageSource={imgUrl}
onClick={() => {
handleClick(patnrId, prdtId);
}}
offerInfo={offerInfo}
priceInfo={priceInfo}
productId={prdtId}
productName={prdtNm}
spotlightId={'searchMain-tsvInfo-spotlightId-' + removeDotAndColon(prdtId)}
{...rest}
/>
);
},
[tsvInfo, handleClick]
);
/** /**
* ✨ [Phase 2] 모드별 콘텐츠 렌더링 (VoiceInputOverlay의 renderModeContent와 동일한 패턴) * ✨ [Phase 2] 모드별 콘텐츠 렌더링 (VoiceInputOverlay의 renderModeContent와 동일한 패턴)
@@ -1561,11 +1533,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
spacing={18} spacing={18}
/> />
</div> </div>
</SectionContainer> </SectionContainer>
</> </>
)} )}
</ContainerBasic> </ContainerBasic>
); );
} }
@@ -1606,7 +1576,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
let nextMode = SEARCH_PANEL_MODES.INITIAL; let nextMode = SEARCH_PANEL_MODES.INITIAL;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-MODE DECISION useEffect running', { dlog('[DEBUG]-MODE DECISION useEffect running', {
isVoiceOverlayVisible, isVoiceOverlayVisible,
hasShopperHouseData: !!shopperHouseData, hasShopperHouseData: !!shopperHouseData,
shopperHouseData_detail: shopperHouseData ? 'EXISTS' : 'NULL', shopperHouseData_detail: shopperHouseData ? 'EXISTS' : 'NULL',
@@ -1626,14 +1596,14 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// 우선순위 1: 음성 입력 오버레이가 열려있으면 VOICE_INPUT 모드 // 우선순위 1: 음성 입력 오버레이가 열려있으면 VOICE_INPUT 모드
if (isVoiceOverlayVisible) { if (isVoiceOverlayVisible) {
if (DEBUG_MODE) { 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; nextMode = SEARCH_PANEL_MODES.VOICE_INPUT;
} }
// 우선순위 2: 음성 검색 결과가 있으면 VOICE_RESULT 모드 // 우선순위 2: 음성 검색 결과가 있으면 VOICE_RESULT 모드
else if (shopperHouseData || isShopperHousePending) { else if (shopperHouseData || isShopperHousePending) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-MODE: shopperHouseData EXISTS or pending VOICE_RESULT', { dlog('[DEBUG]-MODE: shopperHouseData EXISTS or pending VOICE_RESULT', {
hasData: !!shopperHouseData, hasData: !!shopperHouseData,
isPending: isShopperHousePending, isPending: isShopperHousePending,
}); });
@@ -1648,21 +1618,21 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
searchDatas?.show?.length > 0 searchDatas?.show?.length > 0
) { ) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-MODE: searchResults EXISTS SEARCH_RESULT'); dlog('[DEBUG]-MODE: searchResults EXISTS SEARCH_RESULT');
} }
nextMode = SEARCH_PANEL_MODES.SEARCH_RESULT; nextMode = SEARCH_PANEL_MODES.SEARCH_RESULT;
} }
// 우선순위 4: 검색 입력 오버레이가 열려있으면 SEARCH_INPUT 모드 // 우선순위 4: 검색 입력 오버레이가 열려있으면 SEARCH_INPUT 모드
else if (isSearchOverlayVisible) { else if (isSearchOverlayVisible) {
if (DEBUG_MODE) { 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; nextMode = SEARCH_PANEL_MODES.SEARCH_INPUT;
} }
// 우선순위 5: 초기 상태 (기본값) // 우선순위 5: 초기 상태 (기본값)
else { else {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-MODE: No condition met INITIAL'); dlog('[DEBUG]-MODE: No condition met INITIAL');
} }
nextMode = SEARCH_PANEL_MODES.INITIAL; nextMode = SEARCH_PANEL_MODES.INITIAL;
} }
@@ -1670,7 +1640,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// 모드가 변경되었을 때만 업데이트 // 모드가 변경되었을 때만 업데이트
if (nextMode !== currentMode) { if (nextMode !== currentMode) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log(`[DEBUG]-VOICE_RESULT 🔀 Mode changed: ${currentMode} → ${nextMode}`, { dlog(`[DEBUG]-VOICE_RESULT 🔀 Mode changed: ${currentMode} → ${nextMode}`, {
isVoiceOverlayVisible, isVoiceOverlayVisible,
shopperHouseData: !!shopperHouseData, shopperHouseData: !!shopperHouseData,
isShopperHousePending, isShopperHousePending,
@@ -1688,7 +1658,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
setCurrentMode(nextMode); setCurrentMode(nextMode);
} else { } else {
if (DEBUG_MODE) { 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(() => { useEffect(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-searchQuery useEffect:', { searchQuery }); dlog('[DEBUG]-searchQuery useEffect:', { searchQuery });
} }
if (!searchQuery) { if (!searchQuery) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG]-VOICE_RESULT: searchQuery is empty, calling resetSearch'); dlog('[DEBUG]-VOICE_RESULT: searchQuery is empty, calling resetSearch');
} }
dispatch(resetSearch()); dispatch(resetSearch());
} }
@@ -1757,7 +1727,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
*/ */
useEffect(() => { useEffect(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[DEBUG][Focus] Focus useEffect 호출됨 - 상태값 확인:', { dlog('[DEBUG][Focus] Focus useEffect 호출됨 - 상태값 확인:', {
isOnTop, isOnTop,
panelInfo: panelInfo, panelInfo: panelInfo,
currentMode, currentMode,
@@ -1809,9 +1779,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// NEW_SEARCH_LOADED: 음성 검색 결과 로드 시 VoiceInputOverlay와 충돌 방지 // NEW_SEARCH_LOADED: 음성 검색 결과 로드 시 VoiceInputOverlay와 충돌 방지
// 다른 시나리오에서는 기존과 같은 지연 시간 (100ms) // 다른 시나리오에서는 기존과 같은 지연 시간 (100ms)
const focusDelay = const focusDelay =
scenario === 'DETAIL_PANEL_RETURN' || scenario === 'NEW_SEARCH_LOADED' scenario === 'DETAIL_PANEL_RETURN' || scenario === 'NEW_SEARCH_LOADED' ? 50 : 100;
? 50
: 100;
unifiedFocusTimerRef.current = setTimeout(() => { unifiedFocusTimerRef.current = setTimeout(() => {
const targetElement = document.querySelector(`[data-spotlight-id="${targetId}"]`); 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) { if (targetElement || targetId === SPOTLIGHT_IDS.SEARCH_INPUT_BOX) {
Spotlight.focus(targetId); Spotlight.focus(targetId);
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 포커스 이동 완료:', { dlog('[FOCUS] 포커스 이동 완료:', {
targetId, targetId,
scenario, scenario,
hasElement: !!targetElement, hasElement: !!targetElement,
@@ -1829,7 +1797,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
} }
} else { } else {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 포커스 대상 요소를 찾지 못했습니다:', { dlog('[FOCUS] 포커스 대상 요소를 찾지 못했습니다:', {
targetId, targetId,
scenario, scenario,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
@@ -1842,7 +1810,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
const fallbackElement = document.querySelector(`[data-spotlight-id="${fallbackTarget}"]`); const fallbackElement = document.querySelector(`[data-spotlight-id="${fallbackTarget}"]`);
if (fallbackElement) { if (fallbackElement) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'[FOCUS] 🔄 DETAIL_PANEL_RETURN fallback: 번째 상품으로 포커스:', '[FOCUS] 🔄 DETAIL_PANEL_RETURN fallback: 번째 상품으로 포커스:',
fallbackTarget fallbackTarget
); );
@@ -1860,7 +1828,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
if (scenario === 'NEW_SEARCH_LOADED' && targetId === 'searchItemContents0') { if (scenario === 'NEW_SEARCH_LOADED' && targetId === 'searchItemContents0') {
setTimeout(() => { setTimeout(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 🔄 NEW_SEARCH_LOADED: 1 번째 상품으로 다시 포커스 이동'); dlog('[FOCUS] 🔄 NEW_SEARCH_LOADED: 1 번째 상품으로 다시 포커스 이동');
} }
Spotlight.focus('searchItemContents0'); Spotlight.focus('searchItemContents0');
}, 500); // 0.5초 후 }, 500); // 0.5초 후
@@ -1908,7 +1876,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
* - 자동으로 플래그 초기화 * - 자동으로 플래그 초기화
*/ */
useEffect(() => { useEffect(() => {
console.log('[DEBUG] shouldFocusSearchInput useEffect 실행됨!', { dlog('[DEBUG] shouldFocusSearchInput useEffect 실행됨!', {
shouldFocusSearchInput, shouldFocusSearchInput,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
@@ -1916,12 +1884,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
if (shouldFocusSearchInput) { if (shouldFocusSearchInput) {
let focusTimer = null; let focusTimer = null;
console.log('[DEBUG] shouldFocusSearchInput === true, 타이머 설정 ...', { dlog('[DEBUG] shouldFocusSearchInput === true, 타이머 설정 ...', {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 🎯 SearchInputOverlay 닫힘 포커스 관리 useEffect 실행', { dlog('[FOCUS] 🎯 SearchInputOverlay 닫힘 포커스 관리 useEffect 실행', {
shouldFocusSearchInput, shouldFocusSearchInput,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
@@ -1929,65 +1897,62 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// 500ms 후 TInputSimple에 포커스 이동 // 500ms 후 TInputSimple에 포커스 이동
focusTimer = setTimeout(() => { focusTimer = setTimeout(() => {
console.log('[DEBUG] 500ms 타이머 콜백 실행!', { dlog('[DEBUG] 500ms 타이머 콜백 실행!', {
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX, targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] 500ms 타이머 콜백 실행 - TInputSimple으로 포커스 이동', { dlog('[FOCUS] 500ms 타이머 콜백 실행 - TInputSimple으로 포커스 이동', {
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX, targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
} }
console.log('[DEBUG] Spotlight.focus() 호출 직전', { dlog('[DEBUG] Spotlight.focus() 호출 직전', {
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX, targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
}); });
Spotlight.focus(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, targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[FOCUS] Spotlight.focus() 호출 완료', { dlog('[FOCUS] Spotlight.focus() 호출 완료', {
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX, targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
} }
console.log('[DEBUG] setShouldFocusSearchInput(false) 호출 직전', { dlog('[DEBUG] setShouldFocusSearchInput(false) 호출 직전', {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
// 포커스 이동 완료 후 플래그 초기화 // 포커스 이동 완료 후 플래그 초기화
setShouldFocusSearchInput(false); setShouldFocusSearchInput(false);
console.log('[DEBUG] setShouldFocusSearchInput(false) 호출 완료!', { dlog('[DEBUG] setShouldFocusSearchInput(false) 호출 완료!', {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
}, 500); }, 500);
console.log('[DEBUG] 타이머 설정 완료 - 500ms 포커스 이동 예약됨', { dlog('[DEBUG] 타이머 설정 완료 - 500ms 포커스 이동 예약됨', {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
// Cleanup: 타이머 정리 // Cleanup: 타이머 정리
return () => { return () => {
console.log('[DEBUG] shouldFocusSearchInput useEffect cleanup 실행', { dlog('[DEBUG] shouldFocusSearchInput useEffect cleanup 실행', {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
if (focusTimer) { if (focusTimer) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog('[FOCUS] 🧹 SearchInputOverlay 포커스 관리 useEffect cleanup - 타이머 정리', {
'[FOCUS] 🧹 SearchInputOverlay 포커스 관리 useEffect cleanup - 타이머 정리', timestamp: new Date().toISOString(),
{ });
timestamp: new Date().toISOString(),
}
);
} }
clearTimeout(focusTimer); clearTimeout(focusTimer);
} }
@@ -2149,7 +2114,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
} // ✅ INITIAL, VOICE_RESULT & SEARCH_RESULT 모드에서 TInputSimple 내부 포커스 활성화 } // ✅ INITIAL, VOICE_RESULT & SEARCH_RESULT 모드에서 TInputSimple 내부 포커스 활성화
onKeyDown={handleKeydown} onKeyDown={handleKeydown}
onMouseDown={() => { onMouseDown={() => {
console.log( dlog(
'[DEBUG] [SearchPanel] TInputSimple에서 마우스 클릭 감지 SearchInputOverlay 오픈' '[DEBUG] [SearchPanel] TInputSimple에서 마우스 클릭 감지 SearchInputOverlay 오픈'
); );
setIsSearchOverlayVisible(true); setIsSearchOverlayVisible(true);

View File

@@ -34,6 +34,7 @@ import { readLocalStorage, writeLocalStorage } from '../../../utils/helperMethod
import TInputSimple, { ICONS, KINDS } from '../TInput/TInputSimple'; import TInputSimple, { ICONS, KINDS } from '../TInput/TInputSimple';
import ApiStatusDisplay from './ApiStatusDisplay'; import ApiStatusDisplay from './ApiStatusDisplay';
import VoiceApiError from './modes/VoiceApiError'; import VoiceApiError from './modes/VoiceApiError';
import { createDebugHelpers } from '../../../utils/debug';
import VoiceListening from './modes/VoiceListening'; import VoiceListening from './modes/VoiceListening';
import VoiceNotRecognized from './modes/VoiceNotRecognized'; import VoiceNotRecognized from './modes/VoiceNotRecognized';
import VoiceNotRecognizedCircle from './modes/VoiceNotRecognizedCircle'; import VoiceNotRecognizedCircle from './modes/VoiceNotRecognizedCircle';
@@ -56,6 +57,7 @@ const SpottableDebugButton = Spottable('div');
// Debug mode constant - 항상 디버그 화면 표시 // Debug mode constant - 항상 디버그 화면 표시
const DEBUG_MODE = false; const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
// Voice overlay 모드 상수 // Voice overlay 모드 상수
export const VOICE_MODES = { export const VOICE_MODES = {
@@ -150,7 +152,7 @@ const VoiceInputOverlay = ({
externalShopperHouseData = null, // 🎯 [포커스 충돌 해결] 외부 음성 검색 결과 데이터 externalShopperHouseData = null, // 🎯 [포커스 충돌 해결] 외부 음성 검색 결과 데이터
}) => { }) => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔄 [DEBUG] VoiceInputOverlay render - isVisible:', isVisible, 'mode:', mode); dlog('🔄 [DEBUG] VoiceInputOverlay render - isVisible:', isVisible, 'mode:', mode);
} }
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -198,7 +200,7 @@ const VoiceInputOverlay = ({
const [webSpeechEventLogs, setWebSpeechEventLogs] = useState(() => { const [webSpeechEventLogs, setWebSpeechEventLogs] = useState(() => {
const persisted = readLocalStorage(VOICE_EVENT_LOGS_KEY, []); const persisted = readLocalStorage(VOICE_EVENT_LOGS_KEY, []);
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('📚 [DEBUG] Loaded webSpeechEventLogs from localStorage:', persisted); dlog('📚 [DEBUG] Loaded webSpeechEventLogs from localStorage:', persisted);
} }
return persisted; return persisted;
}); });
@@ -222,7 +224,7 @@ const VoiceInputOverlay = ({
const [legacySearchHistory, setLegacySearchHistory] = useState(() => { const [legacySearchHistory, setLegacySearchHistory] = useState(() => {
const history = readLocalStorage(SEARCH_HISTORY_KEY, DEFAULT_SUGGESTIONS); const history = readLocalStorage(SEARCH_HISTORY_KEY, DEFAULT_SUGGESTIONS);
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('📚 [DEBUG] Loaded legacy searchHistory from localStorage:', history); dlog('📚 [DEBUG] Loaded legacy searchHistory from localStorage:', history);
} }
return history; return history;
}); });
@@ -231,11 +233,11 @@ const VoiceInputOverlay = ({
// ⭐ isVisible prop 변경 추적 // ⭐ isVisible prop 변경 추적
useEffect(() => { useEffect(() => {
console.log('👁️ [DEBUG][VoiceInputOverlay] isVisible prop changed to:', isVisible); dlog('👁️ [DEBUG][VoiceInputOverlay] isVisible prop changed to:', isVisible);
if (isVisible) { if (isVisible) {
console.log('📺 [DEBUG][VoiceInputOverlay] Overlay is now VISIBLE, mode:', currentMode); dlog('📺 [DEBUG][VoiceInputOverlay] Overlay is now VISIBLE, mode:', currentMode);
} else { } else {
console.log('🙈 [DEBUG][VoiceInputOverlay] Overlay is now HIDDEN'); dlog('🙈 [DEBUG][VoiceInputOverlay] Overlay is now HIDDEN');
} }
}, [isVisible, currentMode]); }, [isVisible, currentMode]);
@@ -268,7 +270,7 @@ const VoiceInputOverlay = ({
}; };
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log(`[WebSpeech Event] ${event}: ${details}`); dlog(`[WebSpeech Event] ${event}: ${details}`);
} }
setWebSpeechEventLogs((prev) => { setWebSpeechEventLogs((prev) => {
@@ -281,11 +283,9 @@ const VoiceInputOverlay = ({
// ⭐ currentMode 변경 추적 // ⭐ currentMode 변경 추적
useEffect(() => { useEffect(() => {
console.log('🔀 [DEBUG][VoiceInputOverlay] currentMode changed to:', currentMode); dlog('🔀 [DEBUG][VoiceInputOverlay] currentMode changed to:', currentMode);
if (isVisible) { if (isVisible) {
console.log( dlog(`📍 [DEBUG][VoiceInputOverlay] Current state: isVisible=true, mode=${currentMode}`);
`📍 [DEBUG][VoiceInputOverlay] Current state: isVisible=true, mode=${currentMode}`
);
// 모드 변경 이벤트 로깅 // 모드 변경 이벤트 로깅
addWebSpeechEventLog('MODE_CHANGE', `Mode switched to: ${currentMode}`); addWebSpeechEventLog('MODE_CHANGE', `Mode switched to: ${currentMode}`);
} }
@@ -296,7 +296,7 @@ const VoiceInputOverlay = ({
if (webSpeechEventLogs.length > 0) { if (webSpeechEventLogs.length > 0) {
writeLocalStorage(VOICE_EVENT_LOGS_KEY, webSpeechEventLogs); writeLocalStorage(VOICE_EVENT_LOGS_KEY, webSpeechEventLogs);
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'💾 [DEBUG] Saved webSpeechEventLogs to localStorage:', '💾 [DEBUG] Saved webSpeechEventLogs to localStorage:',
webSpeechEventLogs.length, webSpeechEventLogs.length,
'logs' 'logs'
@@ -329,14 +329,14 @@ const VoiceInputOverlay = ({
writeLocalStorage(SEARCH_HISTORY_KEY, newHistory); writeLocalStorage(SEARCH_HISTORY_KEY, newHistory);
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('💾 [VoiceInputOverlay.v2] Legacy search history updated:', newHistory); dlog('💾 [VoiceInputOverlay.v2] Legacy search history updated:', newHistory);
} }
return newHistory; return newHistory;
}); });
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'🎤 [VoiceInputOverlay] Voice search added to history:', '🎤 [VoiceInputOverlay] Voice search added to history:',
trimmedText, trimmedText,
'searchId:', 'searchId:',
@@ -383,12 +383,12 @@ const VoiceInputOverlay = ({
// 🎯 API 실패 감지 - APIERROR 모드로 전환 // 🎯 API 실패 감지 - APIERROR 모드로 전환
useEffect(() => { useEffect(() => {
if (shopperHouseErrorPopup?.visible && shopperHouseErrorPopup?.type === 'API_FAILURE') { if (shopperHouseErrorPopup?.visible && shopperHouseErrorPopup?.type === 'API_FAILURE') {
console.log('[VoiceInputOverlay] 🚨 API 실패 감지 - APIERROR 모드로 전환'); dlog('[VoiceInputOverlay] 🚨 API 실패 감지 - APIERROR 모드로 전환');
setCurrentMode(VOICE_MODES.APIERROR); setCurrentMode(VOICE_MODES.APIERROR);
// 3초 후 자동으로 PROMPT 모드로 복귀 // 3초 후 자동으로 PROMPT 모드로 복귀
const timer = setTimeout(() => { const timer = setTimeout(() => {
console.log('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀'); dlog('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀');
setCurrentMode(VOICE_MODES.PROMPT); setCurrentMode(VOICE_MODES.PROMPT);
}, 3000); }, 3000);
@@ -398,15 +398,15 @@ const VoiceInputOverlay = ({
// 🎯 VoiceResponse 에러 핸들러 - 빈 query 처리 // 🎯 VoiceResponse 에러 핸들러 - 빈 query 처리
const handleVoiceResponseError = useCallback((error) => { const handleVoiceResponseError = useCallback((error) => {
console.log('[VoiceInputOverlay] ⚠️ VoiceResponse 에러 수신:', error); dlog('[VoiceInputOverlay] ⚠️ VoiceResponse 에러 수신:', error);
if (error.type === 'EMPTY_QUERY') { if (error.type === 'EMPTY_QUERY') {
console.log('[VoiceInputOverlay] 🚨 빈 query 에러 - NOTRECOGNIZED 모드로 전환'); dlog('[VoiceInputOverlay] 🚨 빈 query 에러 - NOTRECOGNIZED 모드로 전환');
setCurrentMode(VOICE_MODES.NOTRECOGNIZED); setCurrentMode(VOICE_MODES.NOTRECOGNIZED);
// 3초 후 자동으로 PROMPT 모드로 복귀 // 3초 후 자동으로 PROMPT 모드로 복귀
const timer = setTimeout(() => { const timer = setTimeout(() => {
console.log('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (빈 query 에러)'); dlog('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (빈 query 에러)');
setCurrentMode(VOICE_MODES.PROMPT); setCurrentMode(VOICE_MODES.PROMPT);
}, 3000); }, 3000);
@@ -421,14 +421,14 @@ const VoiceInputOverlay = ({
useEffect(() => { useEffect(() => {
shopperHouseSearchIdRef.current = shopperHouseSearchId; shopperHouseSearchIdRef.current = shopperHouseSearchId;
if (shopperHouseSearchId) { if (shopperHouseSearchId) {
console.log('[VoiceInput] 🔄 searchId ref 업데이트:', shopperHouseSearchId); dlog('[VoiceInput] 🔄 searchId ref 업데이트:', shopperHouseSearchId);
} }
}, [shopperHouseSearchId]); }, [shopperHouseSearchId]);
// 🔍 DEBUG: shopperHouseData 변경 추적 // 🔍 DEBUG: shopperHouseData 변경 추적
useEffect(() => { useEffect(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔍 [DEBUG] shopperHouseData changed:', { dlog('🔍 [DEBUG] shopperHouseData changed:', {
shopperHouseData, shopperHouseData,
refValue: shopperHouseDataRef.current, refValue: shopperHouseDataRef.current,
isVisible, isVisible,
@@ -440,7 +440,7 @@ const VoiceInputOverlay = ({
// 🔄 WebSpeech 에러 재시작 함수 // 🔄 WebSpeech 에러 재시작 함수
const restartWebSpeech = useCallback(() => { const restartWebSpeech = useCallback(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[VoiceInput] 🔄 Restarting WebSpeech after error'); dlog('[VoiceInput] 🔄 Restarting WebSpeech after error');
} }
// 이벤트 로그 기록 // 이벤트 로그 기록
@@ -479,7 +479,7 @@ const VoiceInputOverlay = ({
// 약간의 지연 후 새로 시작 (안정성을 위해) // 약간의 지연 후 새로 시작 (안정성을 위해)
setTimeout(() => { setTimeout(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[VoiceInput] ✅ Restart complete - ready for new input'); dlog('[VoiceInput] ✅ Restart complete - ready for new input');
} }
}, 300); }, 300);
}, [dispatch, onSearchChange, addWebSpeechEventLog]); }, [dispatch, onSearchChange, addWebSpeechEventLog]);
@@ -489,7 +489,7 @@ const VoiceInputOverlay = ({
// Bubble 클릭으로 검색이 시작된 경우 WebSpeech 오류를 우회 // Bubble 클릭으로 검색이 시작된 경우 WebSpeech 오류를 우회
if (isBubbleClickSearch) { if (isBubbleClickSearch) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[VoiceInput] 🔇 WebSpeech 오류 우회 (bubble click search)'); dlog('[VoiceInput] 🔇 WebSpeech 오류 우회 (bubble click search)');
} }
return; return;
} }
@@ -540,20 +540,20 @@ const VoiceInputOverlay = ({
// 음성 인식 중지 // 음성 인식 중지
if (isListening) { if (isListening) {
console.log('[VoiceInput] 🛑 Stopping listening due to error'); dlog('[VoiceInput] 🛑 Stopping listening due to error');
stopListening(); stopListening();
} }
// 에러 모드로 전환 (NOTRECOGNIZED) - 사용자에게 에러 상태 표시 // 에러 모드로 전환 (NOTRECOGNIZED) - 사용자에게 에러 상태 표시
if (isVisible) { 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); setCurrentMode(VOICE_MODES.NOTRECOGNIZED);
setVoiceInputMode(null); setVoiceInputMode(null);
// 일부 에러 타입은 자동 재시작 시도 // 일부 에러 타입은 자동 재시작 시도
const autoRetryErrors = ['network', 'aborted', 'service-not-allowed']; const autoRetryErrors = ['network', 'aborted', 'service-not-allowed'];
if (autoRetryErrors.includes(errorType)) { if (autoRetryErrors.includes(errorType)) {
console.log('[VoiceInput] 🔄 Auto-restarting for error type:', errorType); dlog('[VoiceInput] 🔄 Auto-restarting for error type:', errorType);
setTimeout(() => { setTimeout(() => {
restartWebSpeech(); restartWebSpeech();
}, 2000); // 2초 후 자동 재시작 }, 2000); // 2초 후 자동 재시작
@@ -640,7 +640,7 @@ const VoiceInputOverlay = ({
// ⚠️ 최적화: 실시간 상태 표시 제거, 결과만 표시 // ⚠️ 최적화: 실시간 상태 표시 제거, 결과만 표시
useEffect(() => { useEffect(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔍 [DEBUG] shopperHouseError useEffect running:', { dlog('🔍 [DEBUG] shopperHouseError useEffect running:', {
isVisible, isVisible,
hasError: !!shopperHouseError, hasError: !!shopperHouseError,
error: shopperHouseError, error: shopperHouseError,
@@ -650,7 +650,7 @@ const VoiceInputOverlay = ({
// ✨ 초기화 중에는 오류 처리 안 함 // ✨ 초기화 중에는 오류 처리 안 함
if (isInitializingRef.current) { if (isInitializingRef.current) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔍 [DEBUG] Skipping error check - overlay is initializing'); dlog('🔍 [DEBUG] Skipping error check - overlay is initializing');
} }
return; return;
} }
@@ -658,7 +658,7 @@ const VoiceInputOverlay = ({
// 오류가 발생한 경우 - API 상태 표시 업데이트만 (팝업 대신) // 오류가 발생한 경우 - API 상태 표시 업데이트만 (팝업 대신)
if (shopperHouseError && isVisible) { if (shopperHouseError && isVisible) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[VoiceInputOverlay] ❌ API error - showing result only:', shopperHouseError); dlog('[VoiceInputOverlay] ❌ API error - showing result only:', shopperHouseError);
} }
// ⚠️ 심각한 오류만 표시 (5xx 서버 오류, 커스텀 메시지) // ⚠️ 심각한 오류만 표시 (5xx 서버 오류, 커스텀 메시지)
@@ -676,7 +676,7 @@ const VoiceInputOverlay = ({
// ShopperHouse API 응답 수신 시 overlay 닫기 및 API 상태 업데이트 // ShopperHouse API 응답 수신 시 overlay 닫기 및 API 상태 업데이트
useEffect(() => { useEffect(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔍 [DEBUG] shopperHouseData useEffect running:', { dlog('🔍 [DEBUG] shopperHouseData useEffect running:', {
isVisible, isVisible,
hasData: !!shopperHouseData, hasData: !!shopperHouseData,
refValue: shopperHouseDataRef.current, refValue: shopperHouseDataRef.current,
@@ -688,7 +688,7 @@ const VoiceInputOverlay = ({
// ✨ 초기화 중에는 close 로직 실행 안 함 (중복 닫기 방지) // ✨ 초기화 중에는 close 로직 실행 안 함 (중복 닫기 방지)
if (isInitializingRef.current) { if (isInitializingRef.current) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔍 [DEBUG] Skipping close check - overlay is initializing'); dlog('🔍 [DEBUG] Skipping close check - overlay is initializing');
} }
return; return;
} }
@@ -696,7 +696,7 @@ const VoiceInputOverlay = ({
// 이전 값과 비교하여 새로운 데이터가 들어왔을 때만 닫기 // 이전 값과 비교하여 새로운 데이터가 들어왔을 때만 닫기
if (isVisible && shopperHouseData && shopperHouseData !== shopperHouseDataRef.current) { if (isVisible && shopperHouseData && shopperHouseData !== shopperHouseDataRef.current) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[VoiceInputOverlay.v2] ShopperHouse data received, closing overlay', { dlog('[VoiceInputOverlay.v2] ShopperHouse data received, closing overlay', {
oldRef: shopperHouseDataRef.current, oldRef: shopperHouseDataRef.current,
newData: shopperHouseData, newData: shopperHouseData,
}); });
@@ -708,7 +708,7 @@ const VoiceInputOverlay = ({
setApiStatusMessage('Success'); setApiStatusMessage('Success');
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[VoiceInputOverlay] 📍 API Status updated:', { dlog('[VoiceInputOverlay] 📍 API Status updated:', {
status: 'success', status: 'success',
message: 'Success', message: 'Success',
}); });
@@ -723,7 +723,7 @@ const VoiceInputOverlay = ({
// STT 텍스트 수신 시 처리 // STT 텍스트 수신 시 처리
// useEffect(() => { // useEffect(() => {
// if (lastSTTText && sttTimestamp && isVisible) { // 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) { // if (onSearchChange) {
@@ -745,7 +745,7 @@ const VoiceInputOverlay = ({
if (!ENABLE_WAKE_WORD) return; if (!ENABLE_WAKE_WORD) return;
if (DEBUG_MODE) { 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초 타이머 설정 // 15초 타이머 설정
listeningTimerRef.current = setTimeout(() => { listeningTimerRef.current = setTimeout(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - PROMPT 모드로 복귀'); dlog('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - PROMPT 모드로 복귀');
} }
setCurrentMode(VOICE_MODES.PROMPT); setCurrentMode(VOICE_MODES.PROMPT);
setVoiceInputMode(null); setVoiceInputMode(null);
@@ -789,7 +789,7 @@ const VoiceInputOverlay = ({
if (detected) { if (detected) {
if (DEBUG_MODE) { 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(); handleWakeWordDetected();
} }
@@ -799,7 +799,7 @@ const VoiceInputOverlay = ({
useEffect(() => { useEffect(() => {
if (isVisible && voiceVersion === VOICE_VERSION.WEB_SPEECH && isSupported === false) { if (isVisible && voiceVersion === VOICE_VERSION.WEB_SPEECH && isSupported === false) {
if (DEBUG_MODE) { 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); setCurrentMode(VOICE_MODES.NOINIT);
} }
@@ -808,7 +808,7 @@ const VoiceInputOverlay = ({
// 🎤 음성 입력 최종 처리 함수 (15초 타이머 & 3초 silence detection 공통 사용) // 🎤 음성 입력 최종 처리 함수 (15초 타이머 & 3초 silence detection 공통 사용)
const processFinalVoiceInput = useCallback( const processFinalVoiceInput = useCallback(
(source) => { (source) => {
console.log(`[VoiceInput] 🏁 음성 입력 종료 (${source})`); dlog(`[VoiceInput] 🏁 음성 입력 종료 (${source})`);
// 모든 타이머 정리 및 ref 초기화 // 모든 타이머 정리 및 ref 초기화
clearTimerRef(listeningTimerRef); clearTimerRef(listeningTimerRef);
@@ -833,9 +833,9 @@ const VoiceInputOverlay = ({
? reduxFinalText.trim() ? reduxFinalText.trim()
: interimRefText; : interimRefText;
console.log('[VoiceInput] ├─ Redux finalText:', reduxFinalText || '(없음)'); dlog('[VoiceInput] ├─ Redux finalText:', reduxFinalText || '(없음)');
console.log('[VoiceInput] ├─ interimRef:', interimRefText || '(없음)'); dlog('[VoiceInput] ├─ interimRef:', interimRefText || '(없음)');
console.log('[VoiceInput] └─ 사용할 텍스트:', finalText, `(길이: ${finalText.length})`); dlog('[VoiceInput] └─ 사용할 텍스트:', finalText, `(길이: ${finalText.length})`);
if (finalText && finalText.length >= 3) { if (finalText && finalText.length >= 3) {
// STT 텍스트 저장 // STT 텍스트 저장
@@ -851,11 +851,11 @@ const VoiceInputOverlay = ({
// ✨ ShopperHouse API 자동 호출 (2차 발화 시 searchId 포함) // ✨ ShopperHouse API 자동 호출 (2차 발화 시 searchId 포함)
const query = finalText.trim(); const query = finalText.trim();
console.log('[VoiceInput] 📤 API 요청 전송'); dlog('[VoiceInput] 📤 API 요청 전송');
console.log('[VoiceInput] ├─ query:', query); dlog('[VoiceInput] ├─ query:', query);
console.log('[VoiceInput] ├─ ref 값:', shopperHouseSearchIdRef.current); dlog('[VoiceInput] ├─ ref 값:', shopperHouseSearchIdRef.current);
console.log('[VoiceInput] ├─ currentSearchId:', currentSearchId); dlog('[VoiceInput] ├─ currentSearchId:', currentSearchId);
console.log('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)'); dlog('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)');
// API 호출 이벤트 로깅 // API 호출 이벤트 로깅
const searchIdInfo = currentSearchId const searchIdInfo = currentSearchId
@@ -878,14 +878,14 @@ const VoiceInputOverlay = ({
); );
} else { } else {
// 입력이 없거나 너무 짧으면 NOTRECOGNIZED 모드로 전환 // 입력이 없거나 너무 짧으면 NOTRECOGNIZED 모드로 전환
console.log('[VoiceInput] ⚠️ 입력 없음 또는 너무 짧음 - NOTRECOGNIZED 모드로 전환'); dlog('[VoiceInput] ⚠️ 입력 없음 또는 너무 짧음 - NOTRECOGNIZED 모드로 전환');
console.log('[VoiceInput] └─ finalText 길이:', finalText.length); dlog('[VoiceInput] └─ finalText 길이:', finalText.length);
setCurrentMode(VOICE_MODES.NOTRECOGNIZED); setCurrentMode(VOICE_MODES.NOTRECOGNIZED);
setVoiceInputMode(null); setVoiceInputMode(null);
// 3초 후 자동으로 PROMPT 모드로 복귀 // 3초 후 자동으로 PROMPT 모드로 복귀
const timer = setTimeout(() => { const timer = setTimeout(() => {
console.log('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (NOTRECOGNIZED)'); dlog('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (NOTRECOGNIZED)');
setCurrentMode(VOICE_MODES.PROMPT); setCurrentMode(VOICE_MODES.PROMPT);
}, 3000); }, 3000);
@@ -913,13 +913,13 @@ const VoiceInputOverlay = ({
// currentMode === VOICE_MODES.LISTENING && // currentMode === VOICE_MODES.LISTENING &&
// !isListening // !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(); // startListening();
// //
// // 15초 타이머 설정 // // 15초 타이머 설정
// clearTimerRef(listeningTimerRef); // clearTimerRef(listeningTimerRef);
// listeningTimerRef.current = setTimeout(() => { // listeningTimerRef.current = setTimeout(() => {
// console.log('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - WebSpeech 자동 종료'); // dlog('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - WebSpeech 자동 종료');
// stopListening(); // stopListening();
// setCurrentMode(VOICE_MODES.PROMPT); // setCurrentMode(VOICE_MODES.PROMPT);
// setVoiceInputMode(null); // setVoiceInputMode(null);
@@ -961,7 +961,7 @@ const VoiceInputOverlay = ({
const trimmedText = interimText?.trim() || ''; const trimmedText = interimText?.trim() || '';
if (!hasReached5Chars && trimmedText.length >= 5) { if (!hasReached5Chars && trimmedText.length >= 5) {
// 처음 5글자에 도달했을 때만 활성화 // 처음 5글자에 도달했을 때만 활성화
console.log('[VoiceInput] 🎯 5글자 도달! SilenceCheck 활성화'); dlog('[VoiceInput] 🎯 5글자 도달! SilenceCheck 활성화');
setHasReached5Chars(true); setHasReached5Chars(true);
setIsSilenceCheckActive(true); setIsSilenceCheckActive(true);
} else if (hasReached5Chars && trimmedText.length < 5) { } else if (hasReached5Chars && trimmedText.length < 5) {
@@ -983,7 +983,7 @@ const VoiceInputOverlay = ({
// 3초 silence detection: 마지막 입력 후 3초 동안 추가 입력이 없으면 자동 종료 // 3초 silence detection: 마지막 입력 후 3초 동안 추가 입력이 없으면 자동 종료
clearTimerRef(silenceDetectionTimerRef); clearTimerRef(silenceDetectionTimerRef);
silenceDetectionTimerRef.current = setTimeout(() => { silenceDetectionTimerRef.current = setTimeout(() => {
console.log('[VoiceInput] 🔇 3초 동안 입력 없음 - 자동 종료'); dlog('[VoiceInput] 🔇 3초 동안 입력 없음 - 자동 종료');
addWebSpeechEventLog('SILENCE_3S', 'No input detected for 3 seconds - auto finish'); addWebSpeechEventLog('SILENCE_3S', 'No input detected for 3 seconds - auto finish');
processFinalVoiceInput('3초 silence detection'); processFinalVoiceInput('3초 silence detection');
}, 3000); // 3초 }, 3000); // 3초
@@ -1010,9 +1010,7 @@ const VoiceInputOverlay = ({
if (currentMode !== VOICE_MODES.PROMPT) return; if (currentMode !== VOICE_MODES.PROMPT) return;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog('🎙️ [VoiceInputOverlay.v2] Starting background listening for wake word detection');
'🎙️ [VoiceInputOverlay.v2] Starting background listening for wake word detection'
);
} }
// PROMPT 모드에서 백그라운드 리스닝 시작 // PROMPT 모드에서 백그라운드 리스닝 시작
@@ -1037,7 +1035,7 @@ const VoiceInputOverlay = ({
setSilenceSeconds(0); setSilenceSeconds(0);
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('⏱️ [VoiceInputOverlay.v2] Starting countdown from 15'); dlog('⏱️ [VoiceInputOverlay.v2] Starting countdown from 15');
} }
// 1초마다 카운트다운 // 1초마다 카운트다운
@@ -1045,7 +1043,7 @@ const VoiceInputOverlay = ({
setCountdown((prev) => { setCountdown((prev) => {
const next = prev - 1; const next = prev - 1;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('⏱️ [VoiceInputOverlay.v2] Countdown:', next); dlog('⏱️ [VoiceInputOverlay.v2] Countdown:', next);
} }
if (next <= 0) { if (next <= 0) {
clearIntervalRef(countdownIntervalRef); clearIntervalRef(countdownIntervalRef);
@@ -1080,7 +1078,7 @@ const VoiceInputOverlay = ({
setSilenceSeconds((prev) => { setSilenceSeconds((prev) => {
const next = prev + 1; const next = prev + 1;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🚦 [VoiceInput] Silence Detection Progress:', next); dlog('🚦 [VoiceInput] Silence Detection Progress:', next);
} }
if (next >= 3) { if (next >= 3) {
// 3초 도달하면 interval 정리 (타이머가 곧 종료됨) // 3초 도달하면 interval 정리 (타이머가 곧 종료됨)
@@ -1098,11 +1096,11 @@ const VoiceInputOverlay = ({
// VoiceInputOverlay 언마운트 시에만 초기화 // VoiceInputOverlay 언마운트 시에만 초기화
useEffect(() => { useEffect(() => {
console.log('[VoiceInput] 🚀 VoiceInputOverlay 마운트됨 (searchId 유지)'); dlog('[VoiceInput] 🚀 VoiceInputOverlay 마운트됨 (searchId 유지)');
// Cleanup: 언마운트 시에만 초기화 // Cleanup: 언마운트 시에만 초기화
return () => { return () => {
console.log('[VoiceInput] 🔚 VoiceInputOverlay 언마운트 - searchId 초기화'); dlog('[VoiceInput] 🔚 VoiceInputOverlay 언마운트 - searchId 초기화');
// dispatch(clearShopperHouseData()); // dispatch(clearShopperHouseData());
dispatch(clearSTTText()); dispatch(clearSTTText());
}; };
@@ -1111,34 +1109,34 @@ const VoiceInputOverlay = ({
// Overlay가 열릴 때 포커스를 overlay 내부로 이동 // Overlay가 열릴 때 포커스를 overlay 내부로 이동
useEffect(() => { useEffect(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('👁️ [DEBUG] isVisible useEffect triggered - isVisible:', isVisible); dlog('👁️ [DEBUG] isVisible useEffect triggered - isVisible:', isVisible);
} }
if (isVisible) { if (isVisible) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('✅ [DEBUG] ===== OVERLAY OPENING ====='); dlog('✅ [DEBUG] ===== OVERLAY OPENING =====');
} }
// ✨ 초기화 시작 플래그 설정 (close 로직 일시 차단) // ✨ 초기화 시작 플래그 설정 (close 로직 일시 차단)
isInitializingRef.current = true; isInitializingRef.current = true;
if (DEBUG_MODE) { 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(); lastFocusedElement.current = Spotlight.getCurrent();
console.log('[lastFocusedElement] 🚀 Current focused element:', Spotlight.getCurrent()); dlog('[lastFocusedElement] 🚀 Current focused element:', Spotlight.getCurrent());
// ✨ shopperHouseDataRef를 null로 초기화 // ✨ shopperHouseDataRef를 null로 초기화
shopperHouseDataRef.current = null; shopperHouseDataRef.current = null;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔍 [DEBUG] shopperHouseDataRef initialized to NULL'); dlog('🔍 [DEBUG] shopperHouseDataRef initialized to NULL');
} }
// 모드 초기화 (항상 prompt 모드로 시작) // 모드 초기화 (항상 prompt 모드로 시작)
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔀 [DEBUG] Setting currentMode to:', mode); dlog('🔀 [DEBUG] Setting currentMode to:', mode);
} }
setCurrentMode(mode); setCurrentMode(mode);
setVoiceInputMode(null); setVoiceInputMode(null);
@@ -1149,12 +1147,12 @@ const VoiceInputOverlay = ({
// ✨ 초기화 완료 - close 로직 활성화 // ✨ 초기화 완료 - close 로직 활성화
isInitializingRef.current = false; isInitializingRef.current = false;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('✅ [DEBUG] Initialization complete, close logic ENABLED'); dlog('✅ [DEBUG] Initialization complete, close logic ENABLED');
} }
}, 100); }, 100);
} else { } else {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('❌ [DEBUG] ===== OVERLAY CLOSING ====='); dlog('❌ [DEBUG] ===== OVERLAY CLOSING =====');
} }
// Overlay가 닫힐 때 원래 포커스 복원 및 상태 초기화 // Overlay가 닫힐 때 원래 포커스 복원 및 상태 초기화
@@ -1168,7 +1166,8 @@ const VoiceInputOverlay = ({
// 🎯 [포커스 충돌 해결] VoiceInputOverlay를 통한 음성 검색 후에는 TInput으로 포커스 복원하지 않음 // 🎯 [포커스 충돌 해결] VoiceInputOverlay를 통한 음성 검색 후에는 TInput으로 포커스 복원하지 않음
// SearchResults의 첫 번째 상품으로 포커스가 가도록 SearchPanel에 위임 // SearchResults의 첫 번째 상품으로 포커스가 가도록 SearchPanel에 위임
// shopperHouseData가 있으면 (음성 검색 결과가 있으면) 포커스 복원하지 않음 // shopperHouseData가 있으면 (음성 검색 결과가 있으면) 포커스 복원하지 않음
const hasVoiceSearchResult = shopperHouseData && shopperHouseData.results && shopperHouseData.results.length > 0; const hasVoiceSearchResult =
shopperHouseData && shopperHouseData.results && shopperHouseData.results.length > 0;
if (lastFocusedElement.current && !isVoiceResultMode && !hasVoiceSearchResult) { if (lastFocusedElement.current && !isVoiceResultMode && !hasVoiceSearchResult) {
focusRestoreTimerRef.current = setTimeout(() => { focusRestoreTimerRef.current = setTimeout(() => {
Spotlight.focus(lastFocusedElement.current); Spotlight.focus(lastFocusedElement.current);
@@ -1195,7 +1194,7 @@ const VoiceInputOverlay = ({
// 🔄 API 에러 재시작 함수 // 🔄 API 에러 재시작 함수
const handleApiErrorRestart = useCallback(() => { const handleApiErrorRestart = useCallback(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[VoiceInput] 🔄 Restarting after API error'); dlog('[VoiceInput] 🔄 Restarting after API error');
} }
// ShopperHouse 에러 상태 정리 (Redux) // ShopperHouse 에러 상태 정리 (Redux)
@@ -1216,14 +1215,14 @@ const VoiceInputOverlay = ({
setIsBubbleClickSearch(false); // bubble 클릭 상태 초기화 setIsBubbleClickSearch(false); // bubble 클릭 상태 초기화
if (DEBUG_MODE) { 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]); }, [dispatch, onSearchChange]);
// API 재시도 함수 // API 재시도 함수
const handleApiErrorRetry = useCallback(() => { const handleApiErrorRetry = useCallback(() => {
if (DEBUG_MODE) { 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; const currentSearchId = shopperHouseSearchIdRef.current;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[VoiceInput] 🔄 Retrying API call'); dlog('[VoiceInput] 🔄 Retrying API call');
console.log('[VoiceInput] ├─ query:', query); dlog('[VoiceInput] ├─ query:', query);
console.log('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)'); dlog('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)');
} }
// 다시 API 호출 // 다시 API 호출
@@ -1267,7 +1266,7 @@ const VoiceInputOverlay = ({
// Overlay 닫기 핸들러 (모든 닫기 동작을 통합) // Overlay 닫기 핸들러 (모든 닫기 동작을 통합)
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🚪 [DEBUG] handleClose called - closing overlay'); dlog('🚪 [DEBUG] handleClose called - closing overlay');
} }
// 1. 타이머 정리 // 1. 타이머 정리
@@ -1309,7 +1308,7 @@ const VoiceInputOverlay = ({
const handleSuggestionClick = useCallback( const handleSuggestionClick = useCallback(
(suggestion) => { (suggestion) => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('💡 [DEBUG] handleSuggestionClick called with:', suggestion); dlog('💡 [DEBUG] handleSuggestionClick called with:', suggestion);
} }
// Bubble 클릭으로 검색 시작 상태 설정 // Bubble 클릭으로 검색 시작 상태 설정
@@ -1339,9 +1338,9 @@ const VoiceInputOverlay = ({
const currentSearchId = shopperHouseSearchIdRef.current; const currentSearchId = shopperHouseSearchIdRef.current;
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔍 [DEBUG] Calling ShopperHouse API from bubble click'); dlog('🔍 [DEBUG] Calling ShopperHouse API from bubble click');
console.log('[VoiceInput] ├─ query:', query); dlog('[VoiceInput] ├─ query:', query);
console.log('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)'); dlog('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)');
} }
// API 호출 이벤트 로깅 (Bubble 클릭) // API 호출 이벤트 로깅 (Bubble 클릭)
@@ -1379,7 +1378,7 @@ const VoiceInputOverlay = ({
// Input 창에서 API 호출 핸들러 (돋보기 아이콘 클릭 시에만) // Input 창에서 API 호출 핸들러 (돋보기 아이콘 클릭 시에만)
const handleSearchSubmit = useCallback(() => { const handleSearchSubmit = useCallback(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[VoiceInputOverlay.v2] Search submit:', searchQuery); dlog('[VoiceInputOverlay.v2] Search submit:', searchQuery);
} }
if (searchQuery && searchQuery.trim()) { if (searchQuery && searchQuery.trim()) {
// ShopperHouse API 호출 // ShopperHouse API 호출
@@ -1423,7 +1422,7 @@ const VoiceInputOverlay = ({
(e) => { (e) => {
if (e.key === 'Enter' || e.keyCode === 13) { if (e.key === 'Enter' || e.keyCode === 13) {
e.preventDefault(); e.preventDefault();
console.log( dlog(
'[VoiceInputOverlay] 🔄 TInputSimple에서 Enter 키 감지 → SearchInputOverlay 전환 시작' '[VoiceInputOverlay] 🔄 TInputSimple에서 Enter 키 감지 → SearchInputOverlay 전환 시작'
); );
// 🎯 SearchPanel의 콜백 호출 // 🎯 SearchPanel의 콜백 호출
@@ -1439,7 +1438,7 @@ const VoiceInputOverlay = ({
// ⚠️ [251031] 마우스 클릭 시 프리징 발생 - 추후 원인 분석 후 활성화 필요 // ⚠️ [251031] 마우스 클릭 시 프리징 발생 - 추후 원인 분석 후 활성화 필요
// const handleInputMouseDown = useCallback((e) => { // const handleInputMouseDown = useCallback((e) => {
// e.preventDefault(); // e.preventDefault();
// console.log('[VoiceInputOverlay] 🖱️ TInputSimple에서 마우스 클릭 감지 → SearchInputOverlay 전환 시작'); // dlog('[VoiceInputOverlay] 🖱️ TInputSimple에서 마우스 클릭 감지 → SearchInputOverlay 전환 시작');
// // 🎯 SearchPanel의 콜백 호출 // // 🎯 SearchPanel의 콜백 호출
// if (onTransitionToSearchInput) { // if (onTransitionToSearchInput) {
// onTransitionToSearchInput(); // onTransitionToSearchInput();
@@ -1450,14 +1449,14 @@ const VoiceInputOverlay = ({
const handleInputModeChange = useCallback( const handleInputModeChange = useCallback(
(inputMode) => { (inputMode) => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('[VoiceInputOverlay] TInput 모드 변경:', inputMode); dlog('[VoiceInputOverlay] TInput 모드 변경:', inputMode);
} }
if (inputMode === 'input') { if (inputMode === 'input') {
// Input 모드로 전환되면 Overlay 닫기 // Input 모드로 전환되면 Overlay 닫기
// SearchPanel의 TInput으로 자연스럽게 전환됨 // SearchPanel의 TInput으로 자연스럽게 전환됨
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('⌨️ [DEBUG] Input 모드로 전환됨 - VoiceInputOverlay 닫고 SearchPanel로 이동'); dlog('⌨️ [DEBUG] Input 모드로 전환됨 - VoiceInputOverlay 닫고 SearchPanel로 이동');
} }
handleClose(); handleClose();
} }
@@ -1469,7 +1468,7 @@ const VoiceInputOverlay = ({
const handleToggleDashboard = useCallback(() => { const handleToggleDashboard = useCallback(() => {
setShowDashboard((prev) => !prev); setShowDashboard((prev) => !prev);
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔧 [DEBUG] Dashboard toggled:', !showDashboard); dlog('🔧 [DEBUG] Dashboard toggled:', !showDashboard);
} }
}, [showDashboard]); }, [showDashboard]);
@@ -1484,7 +1483,7 @@ const VoiceInputOverlay = ({
// 모드에 따른 컨텐츠 렌더링 - Memoized // 모드에 따른 컨텐츠 렌더링 - Memoized
const renderModeContent = useMemo(() => { const renderModeContent = useMemo(() => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'🎬 [DEBUG][VoiceInputOverlay] renderModeContent called', '🎬 [DEBUG][VoiceInputOverlay] renderModeContent called',
'| currentMode:', '| currentMode:',
currentMode, currentMode,
@@ -1527,8 +1526,8 @@ const VoiceInputOverlay = ({
} }
// ✨ [DEBUG] Redux 상태 확인 로그 // ✨ [DEBUG] Redux 상태 확인 로그
console.log('[VoiceInput]-shopperHouseRelativeQueries'); dlog('[VoiceInput]-shopperHouseRelativeQueries');
console.log( dlog(
JSON.stringify( JSON.stringify(
{ {
hasRelativeQueries: hasRelativeQueries, hasRelativeQueries: hasRelativeQueries,
@@ -1549,25 +1548,19 @@ const VoiceInputOverlay = ({
); );
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'✅ [DEBUG][VoiceInputOverlay] MODE = PROMPT | Rendering VoicePromptScreen', '✅ [DEBUG][VoiceInputOverlay] MODE = PROMPT | Rendering VoicePromptScreen',
hasRelativeQueries ? '(with relativeQueries)' : '(with legacySearchHistory)', hasRelativeQueries ? '(with relativeQueries)' : '(with legacySearchHistory)',
promptSuggestions.length, promptSuggestions.length,
'suggestions' 'suggestions'
); );
if (hasRelativeQueries) { if (hasRelativeQueries) {
console.log( dlog('[DEBUG][VoiceInputOverlay] ├─ searchId:', shopperHouseSearchId || '(없음)');
'[DEBUG][VoiceInputOverlay] ├─ searchId:', dlog('[DEBUG][VoiceInputOverlay] ├─ relativeQueries:', promptSuggestions);
shopperHouseSearchId || '(없음)' dlog('[DEBUG][VoiceInputOverlay] └─ promptTitle:', promptTitle);
);
console.log('[DEBUG][VoiceInputOverlay] ├─ relativeQueries:', promptSuggestions);
console.log('[DEBUG][VoiceInputOverlay] └─ promptTitle:', promptTitle);
} else { } else {
console.log('[DEBUG][VoiceInputOverlay] ├─ relativeQueries가 없음'); dlog('[DEBUG][VoiceInputOverlay] ├─ relativeQueries가 없음');
console.log( dlog('[DEBUG][VoiceInputOverlay] └─ legacySearchHistory 사용:', promptSuggestions);
'[DEBUG][VoiceInputOverlay] └─ legacySearchHistory 사용:',
promptSuggestions
);
} }
} }
return ( return (
@@ -1581,7 +1574,7 @@ const VoiceInputOverlay = ({
} }
case VOICE_MODES.LISTENING: case VOICE_MODES.LISTENING:
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'🎤 [DEBUG][VoiceInputOverlay] MODE = LISTENING | Rendering VoiceListening (15초 타이머)' '🎤 [DEBUG][VoiceInputOverlay] MODE = LISTENING | Rendering VoiceListening (15초 타이머)'
); );
} }
@@ -1595,7 +1588,7 @@ const VoiceInputOverlay = ({
); );
case VOICE_MODES.RESPONSE: case VOICE_MODES.RESPONSE:
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'💬 [DEBUG][VoiceInputOverlay] MODE = RESPONSE | Rendering VoiceResponse with text:', '💬 [DEBUG][VoiceInputOverlay] MODE = RESPONSE | Rendering VoiceResponse with text:',
sttResponseText, sttResponseText,
'isLoading: true (항상 로딩 애니메이션 표시)' 'isLoading: true (항상 로딩 애니메이션 표시)'
@@ -1614,12 +1607,12 @@ const VoiceInputOverlay = ({
); );
case VOICE_MODES.NOINIT: case VOICE_MODES.NOINIT:
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('⚠️ [DEBUG][VoiceInputOverlay] MODE = NOINIT | Rendering VoiceNotRecognized'); dlog('⚠️ [DEBUG][VoiceInputOverlay] MODE = NOINIT | Rendering VoiceNotRecognized');
} }
return <VoiceNotRecognized prompt={NOINIT_ERROR_MESSAGE} />; return <VoiceNotRecognized prompt={NOINIT_ERROR_MESSAGE} />;
case VOICE_MODES.NOTRECOGNIZED: case VOICE_MODES.NOTRECOGNIZED:
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'❌ [DEBUG][VoiceInputOverlay] MODE = NOTRECOGNIZED | Rendering VoiceNotRecognized with error message' '❌ [DEBUG][VoiceInputOverlay] MODE = NOTRECOGNIZED | Rendering VoiceNotRecognized with error message'
); );
} }
@@ -1631,7 +1624,7 @@ const VoiceInputOverlay = ({
); );
case VOICE_MODES.APIERROR: case VOICE_MODES.APIERROR:
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log( dlog(
'💥 [DEBUG][VoiceInputOverlay] MODE = APIERROR | Rendering VoiceApiError with error details', '💥 [DEBUG][VoiceInputOverlay] MODE = APIERROR | Rendering VoiceApiError with error details',
shopperHouseError shopperHouseError
); );
@@ -1651,7 +1644,7 @@ const VoiceInputOverlay = ({
return <VoiceNotRecognizedCircle />; return <VoiceNotRecognizedCircle />;
default: { default: {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🔄 [DEBUG][VoiceInputOverlay] MODE = DEFAULT | Rendering VoicePromptScreen'); dlog('🔄 [DEBUG][VoiceInputOverlay] MODE = DEFAULT | Rendering VoicePromptScreen');
} }
// default 케이스에서도 promptSuggestions 계산 // default 케이스에서도 promptSuggestions 계산
const hasRelativeQueries = !!shopperHouseRelativeQueries; const hasRelativeQueries = !!shopperHouseRelativeQueries;
@@ -1712,8 +1705,8 @@ const VoiceInputOverlay = ({
const handleWebSpeechMicClick = useCallback( const handleWebSpeechMicClick = useCallback(
(e) => { (e) => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🎤 [DEBUG] handleWebSpeechMicClick called, currentMode:', currentMode); dlog('🎤 [DEBUG] handleWebSpeechMicClick called, currentMode:', currentMode);
console.log('🎤 [DEBUG] CURRENT_WEBSPEECH_VERSION:', CURRENT_WEBSPEECH_VERSION); dlog('🎤 [DEBUG] CURRENT_WEBSPEECH_VERSION:', CURRENT_WEBSPEECH_VERSION);
} }
// 이벤트 전파 방지 - dim 레이어의 onClick 실행 방지 // 이벤트 전파 방지 - dim 레이어의 onClick 실행 방지
@@ -1730,9 +1723,9 @@ const VoiceInputOverlay = ({
if (CURRENT_WEBSPEECH_VERSION === 'v1') { if (CURRENT_WEBSPEECH_VERSION === 'v1') {
if (currentMode === VOICE_MODES.PROMPT) { if (currentMode === VOICE_MODES.PROMPT) {
// ✨ PROMPT 모드에서만 LISTENING으로 전환 가능 // ✨ PROMPT 모드에서만 LISTENING으로 전환 가능
console.log('\n🎤 ════════════════════════════════════════════════════════════════'); dlog('\n🎤 ════════════════════════════════════════════════════════════════');
console.log('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V1)'); dlog('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V1)');
console.log('🎤 ════════════════════════════════════════════════════════════════\n'); dlog('🎤 ════════════════════════════════════════════════════════════════\n');
// 📋 단계별 처리: // 📋 단계별 처리:
// 1. WebSpeech 완전 cleanup (이전 상태 제거) // 1. WebSpeech 완전 cleanup (이전 상태 제거)
@@ -1743,56 +1736,56 @@ const VoiceInputOverlay = ({
// ============================================================ // ============================================================
// 1⃣ WebSpeech 완전 cleanup (이전 상태 제거) // 1⃣ WebSpeech 완전 cleanup (이전 상태 제거)
// ============================================================ // ============================================================
console.log('[VoiceInput] 📍 [STEP 1] WebSpeech Cleanup'); dlog('[VoiceInput] 📍 [STEP 1] WebSpeech Cleanup');
console.log('[VoiceInput] ├─ Dispatching: cleanupWebSpeech()'); dlog('[VoiceInput] ├─ Dispatching: cleanupWebSpeech()');
dispatch(cleanupWebSpeech()); dispatch(cleanupWebSpeech());
console.log('[VoiceInput] └─ ✅ Cleanup dispatched\n'); dlog('[VoiceInput] └─ ✅ Cleanup dispatched\n');
// ============================================================ // ============================================================
// 2⃣ Redux STT 데이터 초기화 (searchId는 Redux에서 유지됨) // 2⃣ Redux STT 데이터 초기화 (searchId는 Redux에서 유지됨)
// ============================================================ // ============================================================
console.log('[VoiceInput] 📍 [STEP 2] STT Data Clear'); dlog('[VoiceInput] 📍 [STEP 2] STT Data Clear');
console.log('[VoiceInput] ├─ Dispatching: clearSTTText()'); dlog('[VoiceInput] ├─ Dispatching: clearSTTText()');
dispatch(clearSTTText()); dispatch(clearSTTText());
console.log('[VoiceInput] ├─ Clearing interim text buffer'); dlog('[VoiceInput] ├─ Clearing interim text buffer');
// ✅ TInput 초기화 // ✅ TInput 초기화
if (onSearchChange) { if (onSearchChange) {
onSearchChange({ value: '' }); onSearchChange({ value: '' });
console.log('[VoiceInput] ├─ TInput cleared'); dlog('[VoiceInput] ├─ TInput cleared');
} }
// ✅ Interim text ref 초기화 // ✅ Interim text ref 초기화
interimTextRef.current = ''; interimTextRef.current = '';
console.log('[VoiceInput] ├─ Interim text ref cleared'); dlog('[VoiceInput] ├─ Interim text ref cleared');
// 기존 타이머 정리 // 기존 타이머 정리
clearTimerRef(listeningTimerRef); clearTimerRef(listeningTimerRef);
clearTimerRef(silenceDetectionTimerRef); clearTimerRef(silenceDetectionTimerRef);
console.log('[VoiceInput] ├─ Previous timers cleared'); dlog('[VoiceInput] ├─ Previous timers cleared');
// UI 모드 업데이트 // UI 모드 업데이트
setVoiceInputMode(VOICE_INPUT_MODE.WEBSPEECH); setVoiceInputMode(VOICE_INPUT_MODE.WEBSPEECH);
setCurrentMode(VOICE_MODES.LISTENING); setCurrentMode(VOICE_MODES.LISTENING);
console.log('[VoiceInput] ├─ UI mode set to WEBSPEECH / LISTENING'); dlog('[VoiceInput] ├─ UI mode set to WEBSPEECH / LISTENING');
// ✅ LISTENING 모드 진입 시 로그 초기화 (새로운 음성 입력 시작) // ✅ LISTENING 모드 진입 시 로그 초기화 (새로운 음성 입력 시작)
setWebSpeechEventLogs([]); setWebSpeechEventLogs([]);
writeLocalStorage(VOICE_EVENT_LOGS_KEY, []); writeLocalStorage(VOICE_EVENT_LOGS_KEY, []);
console.log('[VoiceInput] └─ ✅ Event logs cleared\n'); dlog('[VoiceInput] └─ ✅ Event logs cleared\n');
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🧹 [DEBUG] Cleared webSpeechEventLogs on mic click'); dlog('🧹 [DEBUG] Cleared webSpeechEventLogs on mic click');
} }
// ============================================================ // ============================================================
// 3⃣ WebSpeech 재초기화 (약간의 지연 후) // 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(() => { const reinitializeWebSpeech = setTimeout(() => {
console.log('[VoiceInput] 📍 [STEP 3] WebSpeech Initialization'); dlog('[VoiceInput] 📍 [STEP 3] WebSpeech Initialization');
console.log('[VoiceInput] ├─ Dispatching: initializeWebSpeech()'); dlog('[VoiceInput] ├─ Dispatching: initializeWebSpeech()');
console.log('[VoiceInput] │ └─ lang: en-US, continuous: true, interimResults: true'); dlog('[VoiceInput] │ └─ lang: en-US, continuous: true, interimResults: true');
// Redux 상태 업데이트를 기다리기 위해 약간의 지연 // Redux 상태 업데이트를 기다리기 위해 약간의 지연
dispatch( dispatch(
@@ -1803,37 +1796,37 @@ const VoiceInputOverlay = ({
}) })
); );
console.log('[VoiceInput] └─ ✅ Initialize dispatched\n'); dlog('[VoiceInput] └─ ✅ Initialize dispatched\n');
// ============================================================ // ============================================================
// 4⃣ WebSpeech 즉시 시작 // 4⃣ WebSpeech 즉시 시작
// ============================================================ // ============================================================
console.log('[VoiceInput] 📍 [STEP 4] WebSpeech Start'); dlog('[VoiceInput] 📍 [STEP 4] WebSpeech Start');
console.log('[VoiceInput] ├─ Dispatching: startWebSpeech()'); dlog('[VoiceInput] ├─ Dispatching: startWebSpeech()');
dispatch(startWebSpeech()); dispatch(startWebSpeech());
console.log('[VoiceInput] └─ ✅ Start dispatched\n'); dlog('[VoiceInput] └─ ✅ Start dispatched\n');
// ============================================================ // ============================================================
// 5⃣ 15초 타이머 설정 및 준비 완료 // 5⃣ 15초 타이머 설정 및 준비 완료
// ============================================================ // ============================================================
console.log('[VoiceInput] 📍 [STEP 5] Setup MaxListeningTime Timer'); dlog('[VoiceInput] 📍 [STEP 5] Setup MaxListeningTime Timer');
console.log('[VoiceInput] ├─ Setting 15-second listening timeout'); dlog('[VoiceInput] ├─ Setting 15-second listening timeout');
listeningTimerRef.current = setTimeout(() => { 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'); addWebSpeechEventLog('TIMEOUT_15S', '15 second timeout reached - finishing input');
processFinalVoiceInput('15초 타임아웃'); processFinalVoiceInput('15초 타임아웃');
}, 15000); // 15초 }, 15000); // 15초
console.log('[VoiceInput] ├─ ⏲️ Timer started (15000ms)'); dlog('[VoiceInput] ├─ ⏲️ Timer started (15000ms)');
console.log('[VoiceInput] └─ ✅ Timer configured\n'); dlog('[VoiceInput] └─ ✅ Timer configured\n');
// ============================================================ // ============================================================
// ✨ INITIALIZATION COMPLETE // ✨ INITIALIZATION COMPLETE
// ============================================================ // ============================================================
console.log('🎤 ════════════════════════════════════════════════════════════════'); dlog('🎤 ════════════════════════════════════════════════════════════════');
console.log('✨ [VoiceInput] 음성 인식 준비 완료! (Voice Recognition Ready!)'); dlog('✨ [VoiceInput] 음성 인식 준비 완료! (Voice Recognition Ready!)');
console.log('✨ [VoiceInput] Waiting for voice input... (max 15 seconds)'); dlog('✨ [VoiceInput] Waiting for voice input... (max 15 seconds)');
console.log('🎤 ════════════════════════════════════════════════════════════════\n'); dlog('🎤 ════════════════════════════════════════════════════════════════\n');
}, 100); }, 100);
// Cleanup: 언마운트 시 타이머 정리 // Cleanup: 언마운트 시 타이머 정리
@@ -1843,7 +1836,7 @@ const VoiceInputOverlay = ({
} else { } else {
// listening 모드 또는 기타 모드에서 클릭 시 -> overlay 닫기 // listening 모드 또는 기타 모드에서 클릭 시 -> overlay 닫기
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🎤 [DEBUG] Closing overlay (not in PROMPT mode)'); dlog('🎤 [DEBUG] Closing overlay (not in PROMPT mode)');
} }
handleClose(); handleClose();
} }
@@ -1852,12 +1845,12 @@ const VoiceInputOverlay = ({
// V2: Promise 체인 비동기 방식 (미래 - 아직 구현 대기 중) // V2: Promise 체인 비동기 방식 (미래 - 아직 구현 대기 중)
// ============================================================ // ============================================================
else if (CURRENT_WEBSPEECH_VERSION === 'v2') { else if (CURRENT_WEBSPEECH_VERSION === 'v2') {
console.log('\n🎤 ════════════════════════════════════════════════════════════════'); dlog('\n🎤 ════════════════════════════════════════════════════════════════');
console.log('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V2)'); dlog('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V2)');
console.log('🎤 ════════════════════════════════════════════════════════════════\n'); dlog('🎤 ════════════════════════════════════════════════════════════════\n');
if (DEBUG_MODE) { if (DEBUG_MODE) {
console.log('🎤 [DEBUG] V2 flow triggered'); dlog('🎤 [DEBUG] V2 flow triggered');
} }
// ⚠️ V2 로직은 여기에 구현됨 // ⚠️ V2 로직은 여기에 구현됨
@@ -2135,7 +2128,7 @@ const VoiceInputOverlay = ({
inputFocus={inputFocused} inputFocus={inputFocused}
onKeyDown={handleInputKeyDown} onKeyDown={handleInputKeyDown}
onMouseDown={() => { onMouseDown={() => {
console.log('[InputSimple] 마우스 클릭 감지'); dlog('[InputSimple] 마우스 클릭 감지');
if (onTransitionToSearchInput) { if (onTransitionToSearchInput) {
onTransitionToSearchInput(); onTransitionToSearchInput();
} }