[251104] feat: preShopperHouseData
🕐 커밋 시간: 2025. 11. 04. 19:09:24 📊 변경 통계: • 총 파일: 6개 • 추가: +89줄 • 삭제: -29줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/actionTypes.js ~ com.twin.app.shoptime/src/actions/searchActions.js ~ com.twin.app.shoptime/src/reducers/searchReducer.js ~ com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx 🔧 주요 변경 내용: • 타입 시스템 안정성 강화 • 핵심 비즈니스 로직 개선 • 소규모 기능 개선 • 모듈 구조 개선
This commit is contained in:
@@ -181,6 +181,7 @@ export const types = {
|
||||
GET_SEARCH_MAIN: 'GET_SEARCH_MAIN',
|
||||
CLEAR_SEARCH_MAIN_DATA: 'CLEAR_SEARCH_MAIN_DATA',
|
||||
GET_SHOPPERHOUSE_SEARCH: 'GET_SHOPPERHOUSE_SEARCH',
|
||||
BACKUP_SHOPPERHOUSE_DATA: 'BACKUP_SHOPPERHOUSE_DATA',
|
||||
CLEAR_SHOPPERHOUSE_DATA: 'CLEAR_SHOPPERHOUSE_DATA',
|
||||
RESET_SEARCH: 'RESET_SEARCH',
|
||||
RESET_VOICE_SEARCH: 'RESET_VOICE_SEARCH',
|
||||
|
||||
@@ -105,6 +105,21 @@ export const getShopperHouseSearch =
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔄 API 호출 전에 현재 데이터를 백업 (한 번만!)
|
||||
const currentShopperHouseData = getState().search.shopperHouseData;
|
||||
const preShopperHouseData = getState().search.preShopperHouseData;
|
||||
const currentKey = currentShopperHouseData?.results?.[0]?.searchId || 'null';
|
||||
const preKey = preShopperHouseData?.results?.[0]?.searchId || 'null';
|
||||
|
||||
console.log('[ShopperHouse]-DIFF shopperHouseKey:', currentKey, '| preShopperHouseKey:', preKey);
|
||||
|
||||
if (currentShopperHouseData) {
|
||||
dispatch({
|
||||
type: types.BACKUP_SHOPPERHOUSE_DATA,
|
||||
payload: currentShopperHouseData,
|
||||
});
|
||||
}
|
||||
|
||||
// 이전 데이터 초기화 -> shopperHouseData만 초기화
|
||||
// dispatch({ type: types.CLEAR_SHOPPERHOUSE_DATA });
|
||||
|
||||
@@ -379,14 +394,28 @@ export const hideShopperHouseError = () => {
|
||||
};
|
||||
|
||||
// ShopperHouse 검색 데이터 초기화
|
||||
export const clearShopperHouseData = () => {
|
||||
export const clearShopperHouseData = () => (dispatch, getState) => {
|
||||
// 🔄 초기화 전에 현재 데이터를 백업!
|
||||
const currentShopperHouseData = getState().search.shopperHouseData;
|
||||
const preShopperHouseData = getState().search.preShopperHouseData;
|
||||
const currentKey = currentShopperHouseData?.results?.[0]?.searchId || 'null';
|
||||
const preKey = preShopperHouseData?.results?.[0]?.searchId || 'null';
|
||||
|
||||
console.log('[ShopperHouse]-DIFF (before clear) shopperHouseKey:', currentKey, '| preShopperHouseKey:', preKey);
|
||||
|
||||
if (currentShopperHouseData) {
|
||||
dispatch({
|
||||
type: types.BACKUP_SHOPPERHOUSE_DATA,
|
||||
payload: currentShopperHouseData,
|
||||
});
|
||||
}
|
||||
|
||||
// ✨ 검색 키 초기화 - 진행 중인 요청의 응답을 무시하도록
|
||||
console.log('[ShopperHouse] 🧹 [DEBUG] clearShopperHouseData - 이전 요청 무효화');
|
||||
getShopperHouseSearchKey = null;
|
||||
|
||||
return {
|
||||
dispatch({
|
||||
type: types.CLEAR_SHOPPERHOUSE_DATA,
|
||||
};
|
||||
});
|
||||
};
|
||||
// 기존 코드 뒤에 추가
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ const initialState = {
|
||||
shopperHouseData: null,
|
||||
shopperHouseSearchId: null,
|
||||
shopperHouseRelativeQueries: null, // ✨ relativeQueries는 독립적으로 저장 (searchId와 별개)
|
||||
preShopperHouseData: null, // 🔄 이전 shopperHouseData 저장 (sortingType 변경 시 사용)
|
||||
|
||||
// 🔽 검색 메인 데이터 추가
|
||||
searchMainData: {
|
||||
@@ -64,6 +65,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
// 일반 검색 시 ShopperHouse 데이터 초기화
|
||||
shopperHouseData: null,
|
||||
shopperHouseSearchId: null,
|
||||
preShopperHouseData: null, // 🔄 이전 데이터도 초기화
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,6 +88,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
action: 'RESET_VOICE_SEARCH',
|
||||
shopperHouseData_cleared: true,
|
||||
shopperHouseSearchId_cleared: true,
|
||||
preShopperHouseData_cleared: true,
|
||||
shopperHouseRelativeQueries_preserved: state.shopperHouseRelativeQueries || '(없음)',
|
||||
relativeQueries_length: state.shopperHouseRelativeQueries?.length || 0,
|
||||
},
|
||||
@@ -97,6 +100,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
...state,
|
||||
shopperHouseData: null,
|
||||
shopperHouseSearchId: null,
|
||||
preShopperHouseData: null, // 🔄 이전 데이터도 초기화
|
||||
// ✨ relativeQueries는 유지 (다음 PROMPT 모드에서 표시하기 위해)
|
||||
};
|
||||
|
||||
@@ -112,6 +116,18 @@ export const searchReducer = (state = initialState, action) => {
|
||||
searchTimestamp: Date.now(),
|
||||
};
|
||||
|
||||
case types.BACKUP_SHOPPERHOUSE_DATA: {
|
||||
const newPreKey = action.payload?.results?.[0]?.searchId || 'null';
|
||||
const oldPreKey = state.preShopperHouseData?.results?.[0]?.searchId || 'null';
|
||||
|
||||
console.log('[ShopperHouse]-DIFF (after backup) preShopperHouseKey:', oldPreKey, '→', newPreKey);
|
||||
|
||||
return {
|
||||
...state,
|
||||
preShopperHouseData: action.payload,
|
||||
};
|
||||
}
|
||||
|
||||
case types.GET_SHOPPERHOUSE_SEARCH: {
|
||||
// ✅ 안전한 데이터 접근
|
||||
const resultData = action.payload?.data?.result;
|
||||
@@ -127,30 +143,17 @@ export const searchReducer = (state = initialState, action) => {
|
||||
const searchId = results.length > 0 ? results[0].searchId : null;
|
||||
const relativeQueries = results.length > 0 ? results[0].relativeQueries : null;
|
||||
|
||||
// Key 정보
|
||||
const newKey = searchId || 'null';
|
||||
const preKey = state.preShopperHouseData?.results?.[0]?.searchId || 'null';
|
||||
|
||||
// [VoiceInput] Redux에 저장 로그
|
||||
console.log('[VoiceInput] 💾 Redux에 저장');
|
||||
console.log('[VoiceInput] ├─ searchId:', searchId || '(없음)');
|
||||
console.log('[VoiceInput] ├─ relativeQueries:', relativeQueries || '(없음)');
|
||||
console.log('[VoiceInput]-searchReducer-GET_SHOPPERHOUSE_SEARCH');
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
searchId: searchId,
|
||||
relativeQueries: relativeQueries,
|
||||
relativeQueries_type: typeof relativeQueries,
|
||||
relativeQueries_isArray: Array.isArray(relativeQueries),
|
||||
relativeQueries_length: relativeQueries?.length || 0,
|
||||
results_length: results.length,
|
||||
firstResult_keys: results.length > 0 ? Object.keys(results[0]) : [],
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
console.log('[ShopperHouse]-DIFF (after API) shopperHouseKey:', newKey, '| preShopperHouseKey:', preKey);
|
||||
|
||||
return {
|
||||
...state,
|
||||
shopperHouseData: resultData,
|
||||
// 🔄 preShopperHouseData는 건드리지 않음 (API 호출 전에 이미 백업됨)
|
||||
shopperHouseSearchId: searchId,
|
||||
shopperHouseRelativeQueries: relativeQueries, // ✨ relativeQueries 별도 저장
|
||||
// ShopperHouse 검색 시 일반 검색 데이터 초기화
|
||||
@@ -168,6 +171,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
shopperHouseError: action.payload,
|
||||
shopperHouseData: null, // 오류 발생 시 데이터 초기화
|
||||
shopperHouseSearchId: null,
|
||||
preShopperHouseData: null, // 🔄 이전 데이터도 초기화
|
||||
};
|
||||
|
||||
case types.SHOW_SHOPPERHOUSE_ERROR:
|
||||
@@ -203,13 +207,14 @@ export const searchReducer = (state = initialState, action) => {
|
||||
new Error().stack?.split('\n')[1]?.trim() || '(스택 추적 불가)'
|
||||
);
|
||||
console.log(
|
||||
'[VoiceInput] 🧹 Redux shopperHouseData 초기화 (searchId & relativeQueries는 유지)'
|
||||
'[VoiceInput] 🧹 Redux shopperHouseData 초기화 (searchId & relativeQueries & preShopperHouseData는 유지)'
|
||||
);
|
||||
console.log('[VoiceInput]-searchReducer-CLEAR_SHOPPERHOUSE_DATA');
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
shopperHouseData_cleared: true,
|
||||
preShopperHouseData_preserved: !!state.preShopperHouseData,
|
||||
shopperHouseSearchId_preserved: state.shopperHouseSearchId || '(없음)',
|
||||
shopperHouseRelativeQueries_preserved: state.shopperHouseRelativeQueries || '(없음)',
|
||||
relativeQueries_length: state.shopperHouseRelativeQueries?.length || 0,
|
||||
@@ -221,6 +226,8 @@ export const searchReducer = (state = initialState, action) => {
|
||||
return {
|
||||
...state,
|
||||
shopperHouseData: null,
|
||||
// 🔄 preShopperHouseData는 searchId처럼 유지! (다음 정렬 변경 시 사용)
|
||||
// preShopperHouseData: state.preShopperHouseData, // 명시적으로 유지 (spread로 자동 유지됨)
|
||||
// ✅ searchId는 2번째 발화에서 필요하므로 유지!
|
||||
// shopperHouseSearchId: null, // ❌ 제거됨 - searchId를 유지해야 2차 발화에서 searchId 포함 가능
|
||||
// ✨ relativeQueries도 PROMPT 모드에서 표시하기 위해 유지!
|
||||
|
||||
@@ -187,6 +187,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
const shopperHouseRelativeQueries = useSelector(
|
||||
(state) => state.search.shopperHouseRelativeQueries
|
||||
);
|
||||
// 🔄 이전 shopperHouseData (sortingType 변경 시 사용)
|
||||
const preShopperHouseData = useSelector((state) => state.search.preShopperHouseData);
|
||||
// 0hun: 검색 메인, Hot Picks for you 영역에 대한 전역 상태 값
|
||||
const hotPicksForYou = useSelector((state) => state.search.searchMainData.hotPicksForYou);
|
||||
// 0hun: 검색 메인, Popular Brands 영역에 대한 전역 상태 값
|
||||
@@ -1397,6 +1399,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
itemInfo={searchDatas.item}
|
||||
showInfo={searchDatas.show}
|
||||
shopperHouseInfo={shopperHouseData}
|
||||
preShopperHouseInfo={preShopperHouseData}
|
||||
fallbackShopperHouseData={shopperHouseDataRef.current}
|
||||
shopperHouseSearchId={shopperHouseSearchId}
|
||||
shopperHouseRelativeQueries={shopperHouseRelativeQueries}
|
||||
|
||||
@@ -218,6 +218,7 @@ const SearchResultsNew = ({
|
||||
showInfo,
|
||||
themeInfo,
|
||||
shopperHouseInfo,
|
||||
preShopperHouseInfo = null,
|
||||
fallbackShopperHouseData = null,
|
||||
shopperHouseSearchId = null,
|
||||
shopperHouseRelativeQueries = [],
|
||||
@@ -254,9 +255,28 @@ const SearchResultsNew = ({
|
||||
|
||||
// ShopperHouse 데이터를 ItemCard 형식으로 변환
|
||||
const convertedShopperHouseItems = useMemo(() => {
|
||||
// 🎯 Fallback 로직: HowAboutThese 로딩 중에만 fallbackShopperHouseData 허용
|
||||
const targetData =
|
||||
shopperHouseInfo || (isHowAboutTheseLoading ? fallbackShopperHouseData : null);
|
||||
// 🔄 sortingType에 따른 데이터 선택 로직
|
||||
let targetData;
|
||||
let dataSource = '';
|
||||
|
||||
if (dropDownTab === 0) {
|
||||
// LG_Recommended: 현재 API 응답 사용
|
||||
targetData = shopperHouseInfo || (isHowAboutTheseLoading ? fallbackShopperHouseData : null);
|
||||
dataSource = shopperHouseInfo ? 'shopperHouseInfo' : (isHowAboutTheseLoading ? 'fallbackShopperHouseData' : 'null');
|
||||
} else {
|
||||
// 다른 정렬: 이전 API 응답 사용 (없으면 현재 데이터 사용)
|
||||
targetData = preShopperHouseInfo || shopperHouseInfo || (isHowAboutTheseLoading ? fallbackShopperHouseData : null);
|
||||
dataSource = preShopperHouseInfo ? 'preShopperHouseInfo' : (shopperHouseInfo ? 'shopperHouseInfo' : (isHowAboutTheseLoading ? 'fallbackShopperHouseData' : 'null'));
|
||||
}
|
||||
|
||||
console.log('[SearchResultsNew] 🔄 데이터 소스 선택:', {
|
||||
dropDownTab,
|
||||
dataSource,
|
||||
hasPreData: !!preShopperHouseInfo,
|
||||
hasCurrentData: !!shopperHouseInfo,
|
||||
hasFallbackData: !!fallbackShopperHouseData,
|
||||
isLoading: isHowAboutTheseLoading
|
||||
});
|
||||
|
||||
if (!targetData || !targetData.results || targetData.results.length === 0) {
|
||||
return null;
|
||||
@@ -319,7 +339,7 @@ const SearchResultsNew = ({
|
||||
rangeType: resultData.rangeType || '',
|
||||
};
|
||||
});
|
||||
}, [shopperHouseInfo, fallbackShopperHouseData, isHowAboutTheseLoading]);
|
||||
}, [shopperHouseInfo, preShopperHouseInfo, fallbackShopperHouseData, isHowAboutTheseLoading, dropDownTab]);
|
||||
|
||||
const getButtonTabList = () => {
|
||||
// ShopperHouse 데이터가 있으면 그것을 사용, 없으면 기존 검색 결과 사용
|
||||
|
||||
@@ -213,7 +213,7 @@ const VoiceInputOverlay = ({
|
||||
const [isSilenceCheckActive, setIsSilenceCheckActive] = useState(false);
|
||||
const [hasReached5Chars, setHasReached5Chars] = useState(false); // 처음 5글자 도달 추적
|
||||
// 💬 Bubble 버튼 상태 (true: 버튼 O, false: 텍스트만)
|
||||
const [isBubbleButton, setIsBubbleButton] = useState(false); // 첫 발화때는 true (Try Saying)
|
||||
const [isBubbleButton, setIsBubbleButton] = useState(true); // 첫 발화때는 true (Try Saying)
|
||||
// useSearchHistory Hook 적용 (음성검색 기록 관리)
|
||||
const { addVoiceSearch } = useSearchHistory();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user