Files
shoptime/com.twin.app.shoptime/src/reducers/searchReducer.js
optrader b95628de24 [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()

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
2025-11-24 12:19:40 +09:00

332 lines
11 KiB
JavaScript

import { types } from '../actions/actionTypes';
import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const initialState = {
searchDatas: {},
totalCount: {},
searchPerformed: false,
initPerformed: false,
searchTimestamp: null,
shopperHouseData: null,
shopperHouseSearchId: null,
shopperHouseRelativeQueries: null, // ✨ relativeQueries는 독립적으로 저장 (searchId와 별개)
preShopperHouseData: null, // 🔄 이전 shopperHouseData 저장 (sortingType 변경 시 사용)
// 🔽 검색 메인 데이터 추가
searchMainData: {
topSearchs: [],
popularBrands: [],
hotPicksForYou: [],
tsvInfo: [],
},
shopperHouseError: null, // ShopperHouse API 오류 정보 저장 (디버깅용)
shopperHouseErrorPopup: {
// ShopperHouse 에러 팝업 상태 (사용자 알림용)
message: null,
type: null, // 'API_FAILURE', 'NO_RESULTS', etc.
visible: false,
timestamp: null,
originalError: null,
},
// 🎯 [Phase 1] SearchPanel 모드 제어 명령
panelCommand: {
type: null, // 명령 타입: 'SWITCH_TO_SEARCH_INPUT', 'SWITCH_TO_VOICE_INPUT' 등
source: null, // 명령 발생 출처: 'VoiceInputOverlay', 'SearchInputOverlay' 등
timestamp: null, // 명령 발생 시간
},
};
export const searchReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_SEARCH: {
const newResults = action.payload.data.result.results;
const updatedSearchDatas = action.append ? { ...state.searchDatas } : {};
newResults.forEach(({ type, docs }) => {
if (action.append && updatedSearchDatas[type]) {
for (let i = 0; i < docs.length; i++) {
updatedSearchDatas[type][action.startIndex + i] = docs[i];
}
} else {
updatedSearchDatas[type] = docs;
}
});
const updatedTotalCount = action.append ? { ...state.totalCount } : {};
newResults.forEach(({ type, total_count }) => {
updatedTotalCount[type] = total_count;
});
return {
...state,
searchDatas: updatedSearchDatas,
totalCount: updatedTotalCount,
searchPerformed: true,
initPerformed: !action.append,
// 일반 검색 시 ShopperHouse 데이터 초기화
shopperHouseData: null,
shopperHouseSearchId: null,
preShopperHouseData: null, // 🔄 이전 데이터도 초기화
};
}
case types.RESET_SEARCH:
return {
...state,
searchDatas: {},
totalCount: {},
searchPerformed: false,
initPerformed: false,
searchTimestamp: null,
// shopperHouseData, shopperHouseSearchId 유지
};
case types.RESET_VOICE_SEARCH:
dlog('[VoiceInput]-searchReducer-RESET_VOICE_SEARCH');
dlog(
JSON.stringify(
{
action: 'RESET_VOICE_SEARCH',
shopperHouseData_cleared: true,
shopperHouseSearchId_cleared: true,
preShopperHouseData_cleared: true,
shopperHouseRelativeQueries_preserved: state.shopperHouseRelativeQueries || '(없음)',
relativeQueries_length: state.shopperHouseRelativeQueries?.length || 0,
},
null,
2
)
);
return {
...state,
shopperHouseData: null,
shopperHouseSearchId: null,
preShopperHouseData: null, // 🔄 이전 데이터도 초기화
// ✨ relativeQueries는 유지 (다음 PROMPT 모드에서 표시하기 위해)
};
case types.SET_SEARCH_INIT_PERFORMED:
return {
...state,
initPerformed: action.payload,
};
case types.UPDATE_SEARCH_TIMESTAMP:
return {
...state,
searchTimestamp: Date.now(),
};
case types.BACKUP_SHOPPERHOUSE_DATA: {
const newPreKey = action.payload?.results?.[0]?.searchId || 'null';
const oldPreKey = state.preShopperHouseData?.results?.[0]?.searchId || 'null';
dlog('[ShopperHouse]-DIFF (after backup) preShopperHouseKey:', oldPreKey, '→', newPreKey);
return {
...state,
preShopperHouseData: action.payload,
};
}
case types.GET_SHOPPERHOUSE_SEARCH: {
// ✅ 안전한 데이터 접근
const resultData = action.payload?.data?.result;
if (!resultData) {
console.error('[searchReducer] Invalid shopperHouse data structure');
return state;
}
const results = resultData.results || [];
// searchId와 relativeQueries 추출 (첫 번째 result에서)
const searchId = results.length > 0 ? results[0].searchId : null;
const relativeQueries = results.length > 0 ? results[0].relativeQueries : null;
const sortingType = results.length > 0 ? results[0].sortingType : null;
// Key 정보
const newKey = searchId || 'null';
const preKey = state.preShopperHouseData?.results?.[0]?.searchId || 'null';
const preSortingType = state.preShopperHouseData?.results?.[0]?.sortingType || 'null';
// [VoiceInput] Redux에 저장 로그
dlog(
'[ShopperHouse]-DIFF (after API) shopperHouseKey:',
newKey,
'| preShopperHouseKey:',
preKey,
'| sortingType:',
sortingType || 'null',
'| preSortingType:',
preSortingType
);
return {
...state,
shopperHouseData: resultData,
// 🔄 preShopperHouseData는 건드리지 않음 (API 호출 전에 이미 백업됨)
shopperHouseSearchId: searchId,
shopperHouseRelativeQueries: relativeQueries, // ✨ relativeQueries 별도 저장
// ShopperHouse 검색 시 일반 검색 데이터 초기화
searchDatas: {},
totalCount: {},
searchPerformed: false,
initPerformed: false,
};
}
case types.SET_SHOPPERHOUSE_ERROR:
dlog('[VoiceInput] ❌ Redux shopperHouseError 저장:', action.payload);
return {
...state,
shopperHouseError: action.payload,
shopperHouseData: null, // 오류 발생 시 데이터 초기화
shopperHouseSearchId: null,
preShopperHouseData: null, // 🔄 이전 데이터도 초기화
};
case types.SHOW_SHOPPERHOUSE_ERROR:
dlog('[ShopperHouse] 🔴 Redux shopperHouseErrorPopup 표시:', action.payload);
return {
...state,
shopperHouseErrorPopup: {
message: action.payload.message,
type: action.payload.type,
visible: action.payload.visible,
timestamp: action.payload.timestamp,
originalError: action.payload.originalError,
},
};
case types.HIDE_SHOPPERHOUSE_ERROR:
dlog('[ShopperHouse] ✅ Redux shopperHouseErrorPopup 숨김');
return {
...state,
shopperHouseErrorPopup: {
message: null,
type: null,
visible: false,
timestamp: null,
originalError: null,
},
};
case types.CLEAR_SHOPPERHOUSE_DATA:
dlog('[DEBUG] 🧹 Redux shopperHouseData 초기화 호출 - 호출 스택 추적:');
dlog('[DEBUG] 호출 위치:', new Error().stack?.split('\n')[1]?.trim() || '(스택 추적 불가)');
dlog(
'[VoiceInput] 🧹 Redux shopperHouseData 초기화 (searchId & relativeQueries & preShopperHouseData는 유지)'
);
dlog('[VoiceInput]-searchReducer-CLEAR_SHOPPERHOUSE_DATA');
dlog(
JSON.stringify(
{
shopperHouseData_cleared: true,
preShopperHouseData_preserved: !!state.preShopperHouseData,
shopperHouseSearchId_preserved: state.shopperHouseSearchId || '(없음)',
shopperHouseRelativeQueries_preserved: state.shopperHouseRelativeQueries || '(없음)',
relativeQueries_length: state.shopperHouseRelativeQueries?.length || 0,
},
null,
2
)
);
return {
...state,
shopperHouseData: null,
// 🔄 preShopperHouseData는 searchId처럼 유지! (다음 정렬 변경 시 사용)
// preShopperHouseData: state.preShopperHouseData, // 명시적으로 유지 (spread로 자동 유지됨)
// ✅ searchId는 2번째 발화에서 필요하므로 유지!
// shopperHouseSearchId: null, // ❌ 제거됨 - searchId를 유지해야 2차 발화에서 searchId 포함 가능
// ✨ relativeQueries도 PROMPT 모드에서 표시하기 위해 유지!
// shopperHouseRelativeQueries는 유지
shopperHouseError: null, // 데이터 초기화 시 오류도 초기화
};
// 🔽 검색 메인 데이터 처리
case types.GET_SEARCH_MAIN: {
dlog('🔍 [searchReducer] GET_SEARCH_MAIN 받은 payload:', action.payload);
// 여러 가능한 구조 확인
let resultData = null;
if (action.payload?.result) {
// payload.result 구조
resultData = action.payload.result;
dlog('✅ [searchReducer] payload.result 구조 확인');
} else if (action.payload?.data?.result) {
// payload.data.result 구조
resultData = action.payload.data.result;
dlog('✅ [searchReducer] payload.data.result 구조 확인');
} else if (action.payload?.data) {
// payload.data에 직접 데이터가 있는 경우
resultData = action.payload.data;
dlog('✅ [searchReducer] payload.data 직접 구조 확인');
}
if (!resultData) {
console.error('[searchReducer] ❌ Invalid searchMain data structure');
console.error('받은 payload:', JSON.stringify(action.payload, null, 2));
return state;
}
dlog('[searchReducer] ✅ GET_SEARCH_MAIN success');
return {
...state,
searchMainData: {
topSearchs: resultData.topSearchs || [],
popularBrands: resultData.popularBrands || [],
hotPicksForYou: resultData.hotPicksForYou || [],
tsvInfo: resultData.tsvInfo || [],
},
};
}
case types.CLEAR_SEARCH_MAIN_DATA:
dlog('[searchReducer] 🧹 searchMainData 초기화');
return {
...state,
searchMainData: {
topSearchs: [],
popularBrands: [],
hotPicksForYou: [],
tsvInfo: [],
},
};
// 🎯 [Phase 1] SearchPanel 모드 제어 명령
case types.SWITCH_TO_SEARCH_INPUT_OVERLAY:
dlog('[searchReducer] 🔄 SWITCH_TO_SEARCH_INPUT_OVERLAY 명령 저장', {
source: action.payload?.source,
timestamp: new Date().toISOString(),
});
return {
...state,
panelCommand: {
type: 'SWITCH_TO_SEARCH_INPUT',
source: action.payload?.source || 'unknown',
timestamp: Date.now(),
},
};
case types.CLEAR_PANEL_COMMAND:
return {
...state,
panelCommand: {
type: null,
source: null,
timestamp: null,
},
};
default:
return state;
}
};