🕐 커밋 시간: 2025. 11. 24. 12:43:58 📊 변경 통계: • 총 파일: 40개 • 추가: +774줄 • 삭제: -581줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/appDataActions.js ~ com.twin.app.shoptime/src/actions/billingActions.js ~ com.twin.app.shoptime/src/actions/brandActions.js ~ com.twin.app.shoptime/src/actions/cancelActions.js ~ com.twin.app.shoptime/src/actions/cardActions.js ~ com.twin.app.shoptime/src/actions/cartActions.js ~ com.twin.app.shoptime/src/actions/checkoutActions.js ~ com.twin.app.shoptime/src/actions/commonActions.js ~ com.twin.app.shoptime/src/actions/convertActions.js ~ com.twin.app.shoptime/src/actions/couponActions.js ~ com.twin.app.shoptime/src/actions/deviceActions.js ~ com.twin.app.shoptime/src/actions/empActions.js ~ com.twin.app.shoptime/src/actions/eventActions.js ~ com.twin.app.shoptime/src/actions/forYouActions.js ~ com.twin.app.shoptime/src/actions/homeActions.js ~ com.twin.app.shoptime/src/actions/logActions.js ~ com.twin.app.shoptime/src/actions/mediaActions.js ~ com.twin.app.shoptime/src/actions/mockCartActions.js ~ com.twin.app.shoptime/src/actions/myPageActions.js ~ com.twin.app.shoptime/src/actions/onSaleActions.js ~ com.twin.app.shoptime/src/actions/orderActions.js ~ com.twin.app.shoptime/src/actions/panelActions.js ~ com.twin.app.shoptime/src/actions/panelNavigationActions.js ~ com.twin.app.shoptime/src/actions/pinCodeActions.js ~ com.twin.app.shoptime/src/actions/productActions.js ~ com.twin.app.shoptime/src/actions/queuedPanelActions.js ~ com.twin.app.shoptime/src/actions/searchActions.js ~ com.twin.app.shoptime/src/actions/shippingActions.js ~ com.twin.app.shoptime/src/actions/voiceActions.js ~ com.twin.app.shoptime/src/actions/webSpeechActions.js ~ com.twin.app.shoptime/src/reducers/localSettingsReducer.js ~ com.twin.app.shoptime/src/reducers/mediaOverlayReducer.js ~ com.twin.app.shoptime/src/reducers/mockCartReducer.js ~ com.twin.app.shoptime/src/reducers/playReducer.js ~ com.twin.app.shoptime/src/reducers/productReducer.js ~ com.twin.app.shoptime/src/reducers/videoOverlayReducer.js ~ com.twin.app.shoptime/src/views/UserReview/ShowUserReviews.jsx ~ com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx ~ com.twin.app.shoptime/src/views/UserReview/components/UserReviewsList.jsx ~ com.twin.app.shoptime/src/views/UserReview/components/VirtualScrollBar.jsx 🔧 함수 변경 내용: 📊 Function-level changes summary across 40 files: • Functions added: 14 • Functions modified: 34 • Functions deleted: 18 📋 By language: • javascript: 40 files, 66 function changes 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • 로깅 시스템 개선 • 설정 관리 시스템 개선 • UI 컴포넌트 아키텍처 개선
538 lines
18 KiB
JavaScript
538 lines
18 KiB
JavaScript
import { URLS } from '../api/apiConfig';
|
|
import { TAxios } from '../api/TAxios';
|
|
import { SEARCH_DATA_MAX_RESULTS_LIMIT } from '../utils/Config';
|
|
import { types } from './actionTypes';
|
|
import { changeAppStatus } from './commonActions';
|
|
import { createDebugHelpers } from '../utils/debug';
|
|
|
|
// 디버그 헬퍼 설정
|
|
const DEBUG_MODE = false;
|
|
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
|
|
|
// Search 통합검색 (IBS) 데이터 조회 IF-LGSP-090
|
|
let getSearchKey = null;
|
|
let lastSearchedParams = {};
|
|
export const getSearch =
|
|
(params, startIndex = 1, key) =>
|
|
(dispatch, getState) => {
|
|
const { service, query, domain } = params;
|
|
if (startIndex === 1) {
|
|
lastSearchedParams = params;
|
|
}
|
|
const maxResults =
|
|
startIndex === 1 ? SEARCH_DATA_MAX_RESULTS_LIMIT * 2 : SEARCH_DATA_MAX_RESULTS_LIMIT;
|
|
|
|
let currentKey = key;
|
|
const onSuccess = (response) => {
|
|
dlog('getSearch onSuccess: ', response.data);
|
|
|
|
if (startIndex === 1) {
|
|
getSearchKey = new Date();
|
|
currentKey = getSearchKey;
|
|
|
|
dispatch({
|
|
type: types.GET_SEARCH,
|
|
payload: response.data,
|
|
});
|
|
|
|
dispatch(updateSearchTimestamp());
|
|
} else if (getSearchKey === currentKey) {
|
|
dispatch({
|
|
type: types.GET_SEARCH,
|
|
payload: response.data,
|
|
append: true,
|
|
startIndex: startIndex - 1,
|
|
});
|
|
}
|
|
};
|
|
|
|
const onFail = (error) => {
|
|
derror('getSearch onFail: ', error);
|
|
};
|
|
|
|
TAxios(
|
|
dispatch,
|
|
getState,
|
|
'post',
|
|
URLS.GET_SEARCH,
|
|
{},
|
|
{ service, query, startIndex, maxResults, domain },
|
|
onSuccess,
|
|
onFail
|
|
);
|
|
};
|
|
|
|
export const continueSearch =
|
|
(key, startIndex = 1) =>
|
|
(dispatch, getState) => {
|
|
const searchDatas = getState().search.searchDatas;
|
|
const totalCount = getState().search.totalCount;
|
|
if (
|
|
(startIndex <= 1 && !searchDatas[key]) ||
|
|
searchDatas[key][startIndex - 1] ||
|
|
!totalCount[key] ||
|
|
totalCount[key] < startIndex
|
|
) {
|
|
//ignore search
|
|
return;
|
|
}
|
|
dispatch(getSearch({ ...lastSearchedParams, domain: key }, startIndex, getSearchKey));
|
|
};
|
|
|
|
export const resetSearch = (status) => {
|
|
getSearchKey = null;
|
|
return { type: types.RESET_SEARCH, payload: status };
|
|
};
|
|
|
|
export const resetVoiceSearch = () => {
|
|
getShopperHouseSearchKey = null;
|
|
return { type: types.RESET_VOICE_SEARCH };
|
|
};
|
|
|
|
export const setInitPerformed = (performed) => ({
|
|
type: types.SET_SEARCH_INIT_PERFORMED,
|
|
payload: performed,
|
|
});
|
|
|
|
export const updateSearchTimestamp = () => ({
|
|
type: types.UPDATE_SEARCH_TIMESTAMP,
|
|
});
|
|
|
|
// ShopperHouse 검색 조회 IF-LGSP-098
|
|
let getShopperHouseSearchKey = null;
|
|
|
|
export const getShopperHouseSearch =
|
|
(query, searchId = null, sortingType = null) =>
|
|
(dispatch, getState) => {
|
|
// ✅ 빈 query 체크 - API 호출 방지
|
|
if (!query || query.trim() === '') {
|
|
dlog('[ShopperHouse] ⚠️ 빈 쿼리 - API 호출 건너뜀');
|
|
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';
|
|
|
|
dlog('[ShopperHouse]-DIFF shopperHouseKey:', currentKey, '| preShopperHouseKey:', preKey);
|
|
|
|
if (currentShopperHouseData) {
|
|
dispatch({
|
|
type: types.BACKUP_SHOPPERHOUSE_DATA,
|
|
payload: currentShopperHouseData,
|
|
});
|
|
}
|
|
|
|
// 이전 데이터 초기화 -> shopperHouseData만 초기화
|
|
// dispatch({ type: types.CLEAR_SHOPPERHOUSE_DATA });
|
|
|
|
// 새로운 검색 시작 - 고유 키 생성
|
|
const currentSearchKey = new Date().getTime();
|
|
getShopperHouseSearchKey = currentSearchKey;
|
|
|
|
dlog('[ShopperHouse] 🔍 [DEBUG] API 호출 시작 - key:', currentSearchKey, 'query:', query);
|
|
|
|
const onSuccess = (response) => {
|
|
dlog('[ShopperHouse] 📥 [DEBUG] API 응답 도착 - key:', currentSearchKey);
|
|
dlog('[ShopperHouse] 🔑 [DEBUG] 현재 유효한 key:', getShopperHouseSearchKey);
|
|
|
|
// ✨ 현재 요청이 최신 요청인지 확인
|
|
if (currentSearchKey === getShopperHouseSearchKey) {
|
|
dlog('[ShopperHouse] ✅ [DEBUG] 유효한 응답 - Redux 업데이트');
|
|
dlog('[ShopperHouse] getShopperHouseSearch onSuccess: ', JSON.stringify(response.data));
|
|
|
|
// ✅ API 성공 여부 확인
|
|
const retCode = response.data?.retCode;
|
|
if (retCode !== 0) {
|
|
derror(
|
|
'[ShopperHouse] ❌ API 실패 - retCode:',
|
|
retCode,
|
|
'retMsg:',
|
|
response.data?.retMsg
|
|
);
|
|
dlog('[VoiceInput] 📥 API 응답 실패');
|
|
dlog('[VoiceInput] ├─ retCode:', retCode);
|
|
dlog('[VoiceInput] └─ retMsg:', response.data?.retMsg);
|
|
|
|
// ✨ API 실패 응답을 Redux 에러 상태에 저장
|
|
dispatch(
|
|
setShopperHouseError({
|
|
message: `API 실패: ${response.data?.retMsg || '알 수 없는 오류'}`,
|
|
status: 'API_ERROR',
|
|
retCode: retCode,
|
|
retMsg: response.data?.retMsg,
|
|
timestamp: Date.now(),
|
|
endpoint: URLS.GET_SHOPPERHOUSE_SEARCH,
|
|
query: query,
|
|
searchId: searchId,
|
|
response: response.data,
|
|
})
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// ✅ result 데이터 존재 확인
|
|
if (!response.data?.data?.result) {
|
|
derror('[ShopperHouse] ❌ API 응답에 result 데이터 없음');
|
|
dlog('[VoiceInput] 📥 API 응답 실패 (result 데이터 없음)');
|
|
|
|
// ✨ result 데이터 없음 에러를 Redux 에러 상태에 저장
|
|
dispatch(
|
|
setShopperHouseError({
|
|
message: 'API 응답에 result 데이터가 없습니다',
|
|
status: 'NO_RESULT_DATA',
|
|
timestamp: Date.now(),
|
|
endpoint: URLS.GET_SHOPPERHOUSE_SEARCH,
|
|
query: query,
|
|
searchId: searchId,
|
|
response: response.data,
|
|
})
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// 📥 [ShopperHouseAPI] API 응답 성공 로그
|
|
const resultData = response.data.data.result;
|
|
const results = resultData.results || [];
|
|
const receivedSearchId = results.length > 0 ? results[0].searchId : null;
|
|
const relativeQueries = results.length > 0 ? results[0].relativeQueries : null;
|
|
|
|
// 상품 개수 계산: results[0].docs 배열 길이
|
|
const productCount = results.length > 0 && results[0].docs ? results[0].docs.length : 0;
|
|
|
|
const elapsedTime = ((new Date().getTime() - currentSearchKey) / 1000).toFixed(2);
|
|
|
|
dlog('*[ShopperHouseAPI] ✅ onSuccess - API 응답 성공');
|
|
dlog(
|
|
'*[ShopperHouseAPI] ├─ searchId:',
|
|
receivedSearchId === null ? '(NULL)' : receivedSearchId
|
|
);
|
|
dlog('*[ShopperHouseAPI] ├─ 상품 개수:', productCount);
|
|
dlog('*[ShopperHouseAPI] ├─ relativeQueries:', relativeQueries || '(없음)');
|
|
dlog('*[ShopperHouseAPI] ├─ 소요 시간:', elapsedTime + '초');
|
|
dlog('*[ShopperHouseAPI] └─ timestamp:', new Date().toISOString());
|
|
|
|
dispatch({
|
|
type: types.GET_SHOPPERHOUSE_SEARCH,
|
|
payload: response.data,
|
|
});
|
|
|
|
dispatch(updateSearchTimestamp());
|
|
} else {
|
|
dlog('[ShopperHouse] ❌ [DEBUG] 오래된 응답 무시 - Redux 업데이트 안함');
|
|
}
|
|
};
|
|
|
|
const onFail = (error) => {
|
|
derror('[ShopperHouse] getShopperHouseSearch onFail: ', JSON.stringify(error));
|
|
|
|
// ✨ 현재 요청이 최신 요청인지 확인
|
|
if (currentSearchKey === getShopperHouseSearchKey) {
|
|
dlog('[ShopperHouse] ❌ [DEBUG] 유효한 에러 응답 - Redux 에러 상태 업데이트');
|
|
|
|
const retCode = error?.data?.retCode;
|
|
const status = error?.status;
|
|
const errorMessage = error?.message;
|
|
|
|
// ✅ TAxios 재인증 오류 필터링 (기존 방식 그대로 활용)
|
|
if (retCode === 401) {
|
|
dlog('*[ShopperHouseAPI] ⚠️ onFail - Access Token 만료 (401)');
|
|
dlog('*[ShopperHouseAPI] └─ TAxios가 자동으로 재인증하고 재시도합니다');
|
|
// 401 에러는 Redux에 저장하지 않음 (TAxios 자동 재시도 대기)
|
|
return;
|
|
}
|
|
|
|
if (retCode === 402 || retCode === 501) {
|
|
dlog('*[ShopperHouseAPI] ⚠️ onFail - RefreshToken 만료 (' + retCode + ')');
|
|
dlog('*[ShopperHouseAPI] └─ TAxios가 자동으로 토큰 재발급하고 재시도합니다');
|
|
// 402/501 에러는 Redux에 저장하지 않음 (TAxios 자동 재시도 대기)
|
|
return;
|
|
}
|
|
|
|
// ✅ 네트워크 연결 관련 오류 중 일시적인 오류 필터링
|
|
if (
|
|
status === 0 ||
|
|
errorMessage?.includes('Network Error') ||
|
|
errorMessage?.includes('timeout')
|
|
) {
|
|
dlog('*[ShopperHouseAPI] ⚠️ onFail - 일시적인 네트워크 오류');
|
|
dlog('*[ShopperHouseAPI] ├─ status:', status);
|
|
dlog('*[ShopperHouseAPI] └─ errorMessage:', errorMessage);
|
|
// 일시적인 네트워크 오류는 Redux에 저장하지 않음
|
|
return;
|
|
}
|
|
|
|
// ✨ 그 외의 실제 API 오류들만 Redux에 저장
|
|
dlog('*[ShopperHouseAPI] ❌ onFail - 실제 API 오류 발생');
|
|
dlog('*[ShopperHouseAPI] ├─ retCode:', retCode);
|
|
dlog('*[ShopperHouseAPI] ├─ status:', status);
|
|
dlog('*[ShopperHouseAPI] ├─ errorMessage:', errorMessage);
|
|
dlog('*[ShopperHouseAPI] └─ retMsg:', error?.data?.retMsg || '(없음)');
|
|
|
|
// ✅ API 실패 시 모든 데이터 정리
|
|
dlog('*[ShopperHouseAPI] 🧹 API 실패 - shopperHouse 데이터 정리');
|
|
dispatch(clearShopperHouseData());
|
|
|
|
// ✅ 사용자에게 실패 알림 표시
|
|
dispatch(
|
|
showShopperHouseError({
|
|
message: '검색에 실패했습니다. 다시 시도해주세요.',
|
|
type: 'API_FAILURE',
|
|
originalError: {
|
|
status: status,
|
|
retCode: retCode,
|
|
retMsg: error?.data?.retMsg,
|
|
errorMessage: errorMessage,
|
|
},
|
|
})
|
|
);
|
|
|
|
// 기존 에러 정보도 저장 (디버깅용)
|
|
dispatch(
|
|
setShopperHouseError({
|
|
message: errorMessage || 'Unknown API error',
|
|
status: status,
|
|
statusText: error?.statusText,
|
|
retCode: retCode,
|
|
retMsg: error?.data?.retMsg,
|
|
timestamp: Date.now(),
|
|
endpoint: URLS.GET_SHOPPERHOUSE_SEARCH,
|
|
query: query,
|
|
searchId: searchId,
|
|
originalError: error,
|
|
})
|
|
);
|
|
} else {
|
|
dlog('[ShopperHouse] ❌ [DEBUG] 오래된 에러 응답 무시 - Redux 업데이트 안함');
|
|
}
|
|
};
|
|
|
|
const params = { query };
|
|
if (searchId) {
|
|
params.searchId = searchId;
|
|
}
|
|
if (sortingType) {
|
|
params.sortingType = sortingType;
|
|
}
|
|
dlog('*[ShopperHouseAPI] getShopperHouseSearch params: ', JSON.stringify(params));
|
|
dlog('*[ShopperHouseAPI] ├─ query:', query);
|
|
dlog('*[ShopperHouseAPI] ├─ searchId:', searchId === null ? '(NULL)' : searchId);
|
|
dlog('*[ShopperHouseAPI] ├─ sortingType:', sortingType === null ? '(NULL)' : sortingType);
|
|
dlog('*[ShopperHouseAPI] └─ timestamp:', new Date().toISOString());
|
|
|
|
// 🔧 [테스트용] API 실패 시뮬레이션 스위치
|
|
const SIMULATE_API_FAILURE = false; // ⭐ 이 값을 true로 변경하면 실패 시뮬레이션
|
|
|
|
if (SIMULATE_API_FAILURE) {
|
|
dlog('🧪 [TEST] API 실패 시뮬레이션 활성화 - 2초 후 실패 응답');
|
|
|
|
// 2초 후 실패 시뮬레이션
|
|
setTimeout(() => {
|
|
const simulatedError = {
|
|
status: 500,
|
|
retCode: 500,
|
|
retMsg: '시뮬레이션된 API 실패',
|
|
message: 'Simulated API failure for testing',
|
|
data: {
|
|
retCode: 500,
|
|
retMsg: '시뮬레이션된 서버 오류',
|
|
},
|
|
};
|
|
|
|
dlog('🧪 [TEST] 시뮬레이션된 실패 응답 전송');
|
|
onFail(simulatedError);
|
|
}, 2000); // 2초 딜레이
|
|
|
|
return; // 실제 API 호출하지 않음
|
|
}
|
|
|
|
TAxios(dispatch, getState, 'post', URLS.GET_SHOPPERHOUSE_SEARCH, {}, params, onSuccess, onFail);
|
|
};
|
|
|
|
// ShopperHouse API 에러 처리 액션
|
|
export const setShopperHouseError = (error) => {
|
|
dlog('[ShopperHouse] ❌ [DEBUG] setShopperHouseError - 에러 정보 저장');
|
|
dlog('[ShopperHouse] └─ error:', error);
|
|
|
|
return {
|
|
type: types.SET_SHOPPERHOUSE_ERROR,
|
|
payload: error,
|
|
};
|
|
};
|
|
|
|
// ShopperHouse 에러 표시 액션 (사용자에게 팝업으로 알림)
|
|
export const showShopperHouseError = (error) => {
|
|
dlog('[ShopperHouse] 🔴 [DEBUG] showShopperHouseError - 에러 팝업 표시');
|
|
dlog('[ShopperHouse] └─ error:', error);
|
|
|
|
return {
|
|
type: types.SHOW_SHOPPERHOUSE_ERROR,
|
|
payload: {
|
|
message: error.message,
|
|
type: error.type || 'API_FAILURE',
|
|
timestamp: Date.now(),
|
|
visible: true,
|
|
originalError: error.originalError || null,
|
|
},
|
|
};
|
|
};
|
|
|
|
// ShopperHouse 에러 숨김 액션 (팝업 닫기)
|
|
export const hideShopperHouseError = () => {
|
|
dlog('[ShopperHouse] ✅ [DEBUG] hideShopperHouseError - 에러 팝업 숨김');
|
|
|
|
return {
|
|
type: types.HIDE_SHOPPERHOUSE_ERROR,
|
|
};
|
|
};
|
|
|
|
// ShopperHouse 검색 데이터 초기화
|
|
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';
|
|
|
|
dlog(
|
|
'[ShopperHouse]-DIFF (before clear) shopperHouseKey:',
|
|
currentKey,
|
|
'| preShopperHouseKey:',
|
|
preKey
|
|
);
|
|
|
|
if (currentShopperHouseData) {
|
|
dispatch({
|
|
type: types.BACKUP_SHOPPERHOUSE_DATA,
|
|
payload: currentShopperHouseData,
|
|
});
|
|
}
|
|
|
|
// ✨ 검색 키 초기화 - 진행 중인 요청의 응답을 무시하도록
|
|
getShopperHouseSearchKey = null;
|
|
|
|
dispatch({
|
|
type: types.CLEAR_SHOPPERHOUSE_DATA,
|
|
});
|
|
};
|
|
// 기존 코드 뒤에 추가
|
|
|
|
// Search Main 조회 IF-LGSP-097
|
|
export const getSearchMain = () => (dispatch, getState) => {
|
|
const onSuccess = (response) => {
|
|
dlog('getSearchMain onSuccess: ', response.data);
|
|
|
|
dispatch({
|
|
type: types.GET_SEARCH_MAIN,
|
|
payload: response.data,
|
|
});
|
|
};
|
|
|
|
const onFail = (error) => {
|
|
derror('getSearchMain onFail: ', error);
|
|
};
|
|
|
|
TAxios(dispatch, getState, 'post', URLS.GET_SEARCH_MAIN, {}, {}, onSuccess, onFail);
|
|
};
|
|
|
|
// 검색 메인 데이터 초기화
|
|
export const clearSearchMainData = () => ({
|
|
type: types.CLEAR_SEARCH_MAIN_DATA,
|
|
});
|
|
|
|
// 🎯 [Phase 1] SearchPanel 모드 제어 명령 - VoiceInputOverlay에서 SearchInputOverlay로 전환
|
|
/**
|
|
* VoiceInputOverlay의 TInputSimple에서 Enter 키 또는 마우스 클릭 감지 시
|
|
* SearchInputOverlay로 전환하도록 신호를 보냅니다.
|
|
*
|
|
* 흐름:
|
|
* VoiceInputOverlay (TInputSimple)
|
|
* ↓ (Enter/Click 감지)
|
|
* dispatch(switchToSearchInputOverlay())
|
|
* ↓
|
|
* Redux state 업데이트
|
|
* ↓
|
|
* SearchPanel useSelector로 감지
|
|
* ↓
|
|
* SearchPanel이 VoiceOverlay 닫고 SearchInputOverlay 오픈
|
|
*
|
|
* @param {string} source - 명령 발생 출처 (기본값: 'VoiceInputOverlay')
|
|
* @returns {object} Redux action
|
|
*/
|
|
export const switchToSearchInputOverlay = (source = 'VoiceInputOverlay') => {
|
|
dlog('[searchActions] 🔄 switchToSearchInputOverlay 명령 발송', {
|
|
source,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
|
|
return {
|
|
type: types.SWITCH_TO_SEARCH_INPUT_OVERLAY,
|
|
payload: {
|
|
source,
|
|
},
|
|
};
|
|
};
|
|
|
|
// 🎯 [Phase 1] SearchPanel 명령 초기화
|
|
/**
|
|
* SearchPanel에서 명령을 처리한 후 명령 상태를 초기화합니다.
|
|
* 이를 통해 다음 명령이 제대로 감지되도록 합니다.
|
|
*
|
|
* @returns {object} Redux action
|
|
*/
|
|
export const clearPanelCommand = () => {
|
|
dlog('[searchActions] 🧹 clearPanelCommand 호출 - 명령 초기화');
|
|
|
|
return {
|
|
type: types.CLEAR_PANEL_COMMAND,
|
|
};
|
|
};
|
|
|
|
// 🎯 [Phase 2] 부드러운 전환: VoiceInputOverlay → SearchInputOverlay
|
|
/**
|
|
* Promise 기반의 비동기 전환 로직
|
|
* 1. VoiceInputOverlay 닫기
|
|
* 2. 애니메이션 대기
|
|
* 3. SearchInputOverlay 열기
|
|
* 4. 렌더링 대기
|
|
* 5. Spotlight 포커스 설정
|
|
*
|
|
* @param {object} options - { setIsVoiceOverlayVisible, setIsSearchOverlayVisible, dispatch, Spotlight }
|
|
* @returns {Promise}
|
|
*/
|
|
export const transitionToSearchInputOverlay = (options) => async (dispatch) => {
|
|
const { setIsVoiceOverlayVisible, setIsSearchOverlayVisible, Spotlight } = options;
|
|
|
|
dlog('[searchActions] 🔄 transitionToSearchInputOverlay 시작');
|
|
dlog('[searchActions] ├─ Step 1: VoiceInputOverlay 닫기');
|
|
|
|
// Step 1: VoiceInputOverlay 닫기
|
|
setIsVoiceOverlayVisible(false);
|
|
|
|
// Step 2: 애니메이션 대기 (300ms - VoiceInputOverlay 닫기 애니메이션)
|
|
dlog('[searchActions] ├─ Step 2: 300ms 대기 (VoiceOverlay 애니메이션)');
|
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
|
|
// Step 3: SearchInputOverlay 열기
|
|
dlog('[searchActions] ├─ Step 3: SearchInputOverlay 열기');
|
|
setIsSearchOverlayVisible(true);
|
|
|
|
// Step 4: 렌더링 대기 (100ms - SearchInputOverlay 렌더링 및 마운트)
|
|
dlog('[searchActions] ├─ Step 4: 100ms 대기 (SearchInputOverlay 렌더링)');
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
// Step 5: Spotlight 포커스 설정
|
|
dlog('[searchActions] ├─ Step 5: Spotlight 포커스 설정 (search_overlay_input_box)');
|
|
Spotlight.focus('search_overlay_input_box');
|
|
|
|
// Step 6: 명령 초기화
|
|
dlog('[searchActions] └─ Step 6: panelCommand 초기화');
|
|
dispatch(clearPanelCommand());
|
|
|
|
dlog('[searchActions] ✅ transitionToSearchInputOverlay 완료');
|
|
};
|