[251022] fix: VoiceInputOverlay 조기종료 해결
🕐 커밋 시간: 2025. 10. 22. 14:55:17 📊 변경 통계: • 총 파일: 2개 • 추가: +21줄 • 삭제: -12줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx (javascript): 🔄 Modified: clearAllTimers()
This commit is contained in:
@@ -1,43 +1,27 @@
|
||||
// src/views/SearchPanel/SearchPanel.new.jsx
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import micIcon from '../../../assets/images/searchpanel/image-mic.png';
|
||||
import hotPicksImage from '../../../assets/images/searchpanel/img-hotpicks.png';
|
||||
import hotPicksBrandImage
|
||||
from '../../../assets/images/searchpanel/img-search-hotpicks.png';
|
||||
import {
|
||||
sendLogGNB,
|
||||
sendLogTotalRecommend,
|
||||
} from '../../actions/logActions';
|
||||
import hotPicksBrandImage from '../../../assets/images/searchpanel/img-search-hotpicks.png';
|
||||
import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
|
||||
import { getMyRecommandedKeyword } from '../../actions/myPageActions';
|
||||
import {
|
||||
popPanel,
|
||||
pushPanel,
|
||||
updatePanel,
|
||||
} from '../../actions/panelActions';
|
||||
import { popPanel, pushPanel, updatePanel } from '../../actions/panelActions';
|
||||
import {
|
||||
getSearch,
|
||||
getSearchMain,
|
||||
resetSearch,
|
||||
resetVoiceSearch,
|
||||
clearShopperHouseData,
|
||||
} from '../../actions/searchActions';
|
||||
import { clearSTTText } from '../../actions/webSpeechActions';
|
||||
// import {
|
||||
// showErrorToast,
|
||||
// showInfoToast,
|
||||
@@ -48,63 +32,42 @@ import {
|
||||
// } from '../../actions/toastActions';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
import TVerticalPagenator
|
||||
from '../../components/TVerticalPagenator/TVerticalPagenator';
|
||||
import TVirtualGridList
|
||||
from '../../components/TVirtualGridList/TVirtualGridList';
|
||||
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
|
||||
import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList';
|
||||
// import VirtualKeyboardContainer from "../../components/TToast/VirtualKeyboardContainer";
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import {
|
||||
LOG_CONTEXT_NAME,
|
||||
LOG_MENU,
|
||||
LOG_MESSAGE_ID,
|
||||
panel_names,
|
||||
} from '../../utils/Config';
|
||||
import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../utils/Config';
|
||||
import SearchInputOverlay from './SearchInpuOverlay';
|
||||
import css from './SearchPanel.new.module.less';
|
||||
import SearchResultsNew from './SearchResults.new';
|
||||
import TInput, {
|
||||
ICONS,
|
||||
KINDS,
|
||||
} from './TInput/TInput';
|
||||
import VoiceInputOverlay, {
|
||||
VOICE_MODES,
|
||||
} from './VoiceInputOverlay/VoiceInputOverlay';
|
||||
import TInput, { ICONS, KINDS } from './TInput/TInput';
|
||||
import VoiceInputOverlay, { VOICE_MODES } from './VoiceInputOverlay/VoiceInputOverlay';
|
||||
|
||||
const ContainerBasic = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
const ContainerBasic = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
|
||||
// 검색 입력 영역 컨테이너
|
||||
const InputContainer = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
const InputContainer = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
|
||||
// 콘텐츠 섹션 컨테이너
|
||||
const SectionContainer = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
const SectionContainer = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
|
||||
// Spottable 컴포넌트들
|
||||
const SpottableMicButton = Spottable("div");
|
||||
const SpottableKeyword = Spottable("div");
|
||||
const SpottableProduct = Spottable("div");
|
||||
const SpottableMicButton = Spottable('div');
|
||||
const SpottableKeyword = Spottable('div');
|
||||
const SpottableProduct = Spottable('div');
|
||||
|
||||
const ITEMS_PER_PAGE = 9;
|
||||
|
||||
// Spotlight ID 상수
|
||||
const SPOTLIGHT_IDS = {
|
||||
SEARCH_INPUT_LAYER: "search-input-layer",
|
||||
SEARCH_INPUT_BOX: "search-input-box",
|
||||
MICROPHONE_BUTTON: "microphone-button",
|
||||
RECENT_SEARCHES_SECTION: "recent-searches-section",
|
||||
TOP_SEARCHES_SECTION: "top-searches-section",
|
||||
POPULAR_BRANDS_SECTION: "popular-brands-section",
|
||||
HOT_PICKS_SECTION: "hot-picks-section",
|
||||
SEARCH_VERTICAL_PAGENATOR: "search_verticalPagenator",
|
||||
SEARCH_INPUT_LAYER: 'search-input-layer',
|
||||
SEARCH_INPUT_BOX: 'search-input-box',
|
||||
MICROPHONE_BUTTON: 'microphone-button',
|
||||
RECENT_SEARCHES_SECTION: 'recent-searches-section',
|
||||
TOP_SEARCHES_SECTION: 'top-searches-section',
|
||||
POPULAR_BRANDS_SECTION: 'popular-brands-section',
|
||||
HOT_PICKS_SECTION: 'hot-picks-section',
|
||||
SEARCH_VERTICAL_PAGENATOR: 'search_verticalPagenator',
|
||||
};
|
||||
|
||||
export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
@@ -116,27 +79,17 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
const { searchDatas: searchDatas } = useSelector((state) => state.search);
|
||||
const searchPerformed = useSelector((state) => state.search.searchPerformed);
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
const shopperHouseData = useSelector(
|
||||
(state) => state.search.shopperHouseData
|
||||
);
|
||||
const shopperHouseData = useSelector((state) => state.search.shopperHouseData);
|
||||
//검색 메인
|
||||
const hotPicksForYou = useSelector(
|
||||
(state) => state.search.searchMainData.hotPicksForYou
|
||||
);
|
||||
const popularBrands = useSelector(
|
||||
(state) => state.search.searchMainData.popularBrands
|
||||
);
|
||||
const topSearchs = useSelector(
|
||||
(state) => state.search.searchMainData.topSearchs
|
||||
);
|
||||
const hotPicksForYou = useSelector((state) => state.search.searchMainData.hotPicksForYou);
|
||||
const popularBrands = useSelector((state) => state.search.searchMainData.popularBrands);
|
||||
const topSearchs = useSelector((state) => state.search.searchMainData.topSearchs);
|
||||
|
||||
const [firstSpot, setFirstSpot] = useState(false);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [paginatedKeywords, setPaginatedKeywords] = useState([]);
|
||||
const [pageChanged, setPageChanged] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState(
|
||||
panelInfo.searchVal ? panelInfo.searchVal : null
|
||||
);
|
||||
const [searchQuery, setSearchQuery] = useState(panelInfo.searchVal ? panelInfo.searchVal : null);
|
||||
const [position, setPosition] = useState(null);
|
||||
const [showVirtualKeyboard, setShowVirtualKeyboard] = useState(false);
|
||||
const [isVoiceOverlayVisible, setIsVoiceOverlayVisible] = useState(false);
|
||||
@@ -144,10 +97,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
// isVoiceOverlayVisible 상태 변화 추적
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
"🔄 [DEBUG][SearchPanel] isVoiceOverlayVisible changed to:",
|
||||
isVoiceOverlayVisible
|
||||
);
|
||||
console.log('🔄 [DEBUG][SearchPanel] isVoiceOverlayVisible changed to:', isVoiceOverlayVisible);
|
||||
}, [isVoiceOverlayVisible]);
|
||||
|
||||
//인풋창 포커스 구분을 위함
|
||||
@@ -163,8 +113,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
const [isInputModeActive, setIsInputModeActive] = useState(false);
|
||||
const handleInputModeChange = useCallback((isActive) => {
|
||||
console.log(
|
||||
"[SearchPanel] TInput 입력 모드:",
|
||||
isActive ? "활성화 (키보드 표시)" : "비활성화 (키보드 숨김)"
|
||||
'[SearchPanel] TInput 입력 모드:',
|
||||
isActive ? '활성화 (키보드 표시)' : '비활성화 (키보드 숨김)'
|
||||
);
|
||||
setIsInputModeActive(isActive);
|
||||
}, []);
|
||||
@@ -172,11 +122,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
let searchQueryRef = usePrevious(searchQuery);
|
||||
let isOnTopRef = usePrevious(isOnTop);
|
||||
|
||||
const firstButtonSpotlightId = "first-keyword-button";
|
||||
const firstButtonSpotlightId = 'first-keyword-button';
|
||||
const cbChangePageRef = useRef(null);
|
||||
const [focusedContainerId, setFocusedContainerId] = useState(
|
||||
panelInfo?.focusedContainerId
|
||||
);
|
||||
const [focusedContainerId, setFocusedContainerId] = useState(panelInfo?.focusedContainerId);
|
||||
const focusedContainerIdRef = usePrevious(focusedContainerId);
|
||||
|
||||
// Timer refs for cleanup
|
||||
@@ -190,10 +138,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// }, [isVoiceOverlayVisible]);
|
||||
|
||||
// 가짜 데이터 - 실제로는 Redux store나 API에서 가져와야 함
|
||||
const recentSearches = useMemo(
|
||||
() => ["Puppy food", "Dog toy", "Fitness"],
|
||||
[]
|
||||
);
|
||||
const recentSearches = useMemo(() => ['Puppy food', 'Dog toy', 'Fitness'], []);
|
||||
|
||||
// Voice overlay suggestions (동적으로 변경 가능)
|
||||
const voiceSuggestions = useMemo(
|
||||
@@ -276,7 +221,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
dispatch(
|
||||
sendLogTotalRecommend({
|
||||
query: searchQuery,
|
||||
searchType: searchPerformed ? "query" : "keyword",
|
||||
searchType: searchPerformed ? 'query' : 'keyword',
|
||||
result: result,
|
||||
contextName: LOG_CONTEXT_NAME.SEARCH,
|
||||
messageId: LOG_MESSAGE_ID.SEARCH_ITEM,
|
||||
@@ -300,9 +245,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
if (query.trim()) {
|
||||
dispatch(
|
||||
getSearch({
|
||||
service: "com.lgshop.app",
|
||||
service: 'com.lgshop.app',
|
||||
query: query,
|
||||
domain: "theme,show,item",
|
||||
domain: 'theme,show,item',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -393,7 +338,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
}
|
||||
|
||||
// Enter 키 처리
|
||||
if (e.key === "Enter") {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (showVirtualKeyboard) {
|
||||
// 가상 키보드가 열려있으면 검색 실행하고 키보드 닫기
|
||||
@@ -407,32 +352,26 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
// 방향키 처리 - Spotlight 네비게이션 허용
|
||||
const arrowKeys = [
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"ArrowUp",
|
||||
"ArrowDown",
|
||||
"Left",
|
||||
"Right",
|
||||
"Up",
|
||||
"Down",
|
||||
'ArrowLeft',
|
||||
'ArrowRight',
|
||||
'ArrowUp',
|
||||
'ArrowDown',
|
||||
'Left',
|
||||
'Right',
|
||||
'Up',
|
||||
'Down',
|
||||
];
|
||||
if (arrowKeys.includes(e.key)) {
|
||||
// 입력 필드가 비어있고 왼쪽 화살표인 경우에만 방지
|
||||
if (
|
||||
position === 0 &&
|
||||
(e.key === "Left" || e.key === "ArrowLeft") &&
|
||||
!searchQuery
|
||||
) {
|
||||
if (position === 0 && (e.key === 'Left' || e.key === 'ArrowLeft') && !searchQuery) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// 오른쪽 화살표 키 처리 - 포커스 이동 허용
|
||||
if (e.key === "ArrowRight" || e.key === "Right") {
|
||||
if (e.key === 'ArrowRight' || e.key === 'Right') {
|
||||
// 커서가 텍스트 끝에 있을 때만 포커스 이동 허용
|
||||
const input = document.querySelector(
|
||||
`[data-spotlight-id="input-field-box"] > input`
|
||||
);
|
||||
const input = document.querySelector(`[data-spotlight-id="input-field-box"] > input`);
|
||||
if (input && position === input.value.length) {
|
||||
// 커서가 텍스트 끝에 있으면 포커스 이동 허용
|
||||
return;
|
||||
@@ -447,9 +386,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
);
|
||||
|
||||
const cursorPosition = useCallback(() => {
|
||||
const input = document.querySelector(
|
||||
`[data-spotlight-id="input-field-box"] > input`
|
||||
);
|
||||
const input = document.querySelector(`[data-spotlight-id="input-field-box"] > input`);
|
||||
if (input) {
|
||||
setPosition(input.selectionStart);
|
||||
}
|
||||
@@ -460,7 +397,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"🖱️ [DEBUG][SearchPanel] onClickMic called, current isVoiceOverlayVisible:",
|
||||
'🖱️ [DEBUG][SearchPanel] onClickMic called, current isVoiceOverlayVisible:',
|
||||
isVoiceOverlayVisible
|
||||
);
|
||||
setIsVoiceOverlayVisible(true);
|
||||
@@ -476,10 +413,10 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
setIsVoiceOverlayVisible(false);
|
||||
return;
|
||||
}
|
||||
if (searchQuery === null || searchQuery === "") {
|
||||
if (searchQuery === null || searchQuery === '') {
|
||||
dispatch(popPanel(panel_names.SEARCH_PANEL));
|
||||
} else {
|
||||
setSearchQuery("");
|
||||
setSearchQuery('');
|
||||
setCurrentPage(1);
|
||||
dispatch(resetSearch());
|
||||
Spotlight.focus(SPOTLIGHT_IDS.SEARCH_INPUT_BOX);
|
||||
@@ -499,7 +436,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
Spotlight.resume();
|
||||
setFirstSpot(true);
|
||||
if (panelInfo.currentSpot) {
|
||||
if (panels[panels.length - 1]?.name === "searchpanel") {
|
||||
if (panels[panels.length - 1]?.name === 'searchpanel') {
|
||||
Spotlight.focus(panelInfo.currentSpot);
|
||||
}
|
||||
}
|
||||
@@ -550,7 +487,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// Microphone button keydown handler
|
||||
const handleMicKeyDown = useCallback(
|
||||
(e) => {
|
||||
if (e.key === "Enter") {
|
||||
if (e.key === 'Enter') {
|
||||
onClickMic();
|
||||
}
|
||||
},
|
||||
@@ -560,15 +497,19 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// Voice overlay close handler
|
||||
const handleVoiceOverlayClose = useCallback(() => {
|
||||
console.log(
|
||||
"🚪 [DEBUG][SearchPanel] handleVoiceOverlayClose called, setting isVoiceOverlayVisible to FALSE"
|
||||
'🚪 [DEBUG][SearchPanel] handleVoiceOverlayClose called, setting isVoiceOverlayVisible to FALSE'
|
||||
);
|
||||
setIsVoiceOverlayVisible(false);
|
||||
|
||||
// ✅ Redux 정리 (VoiceInputOverlay도 정리하지만, 이중 정리로 안전성 보장)
|
||||
dispatch(clearShopperHouseData());
|
||||
dispatch(clearSTTText());
|
||||
|
||||
setIsVoiceOverlayVisible(false);
|
||||
// ✅ VoiceOverlay가 닫힐 때 항상 TInput으로 포커스 이동
|
||||
setTimeout(() => {
|
||||
Spotlight.focus(SPOTLIGHT_IDS.SEARCH_INPUT_BOX);
|
||||
}, 150); // Overlay 닫히는 시간을 고려한 지연
|
||||
}, []);
|
||||
}, [dispatch]);
|
||||
|
||||
// Search overlay close handler
|
||||
const handleSearchOverlayClose = useCallback(() => {
|
||||
@@ -610,27 +551,17 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
{...rest}
|
||||
>
|
||||
<div className={css.productImageWrapper}>
|
||||
<img
|
||||
src={bgImgPath}
|
||||
alt={curationNm}
|
||||
className={css.productImage}
|
||||
/>
|
||||
<img src={bgImgPath} alt={curationNm} className={css.productImage} />
|
||||
</div>
|
||||
<div className={css.productInfo}>
|
||||
{showBrandLogo && (
|
||||
<div className={css.productBrandWrapper}>
|
||||
<img
|
||||
src={patncLogoPath}
|
||||
alt={patncNm}
|
||||
className={css.brandLogo}
|
||||
/>
|
||||
<img src={patncLogoPath} alt={patncNm} className={css.brandLogo} />
|
||||
</div>
|
||||
)}
|
||||
<div className={css.productDetails}>
|
||||
{showBrandName && <div className={css.brandName}>{patncNm}</div>}
|
||||
{showProductTitle && (
|
||||
<div className={css.productTitle}>{curationNm}</div>
|
||||
)}
|
||||
{showProductTitle && <div className={css.productTitle}>{curationNm}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</SpottableProduct>
|
||||
@@ -651,11 +582,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
}, [searchPerformed, searchQuery, inputFocus]);
|
||||
|
||||
return (
|
||||
<TPanel
|
||||
className={css.container}
|
||||
handleCancel={onCancel}
|
||||
spotlightId={spotlightId}
|
||||
>
|
||||
<TPanel className={css.container} handleCancel={onCancel} spotlightId={spotlightId}>
|
||||
<TBody className={css.tBody} scrollable spotlightDisabled={!isOnTop}>
|
||||
<ContainerBasic>
|
||||
{isOnTop && (
|
||||
@@ -675,10 +602,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
className={classNames(
|
||||
css.inputContainer,
|
||||
inputFocus === true && css.inputFocus,
|
||||
searchDatas &&
|
||||
css.searchValue /* 이건 결과값 있을때만. 조건 추가필요 */,
|
||||
(isVoiceOverlayVisible || isSearchOverlayVisible) &&
|
||||
css.hidden
|
||||
searchDatas && css.searchValue /* 이건 결과값 있을때만. 조건 추가필요 */,
|
||||
(isVoiceOverlayVisible || isSearchOverlayVisible) && css.hidden
|
||||
)}
|
||||
data-wheel-point="true"
|
||||
spotlightId={SPOTLIGHT_IDS.SEARCH_INPUT_LAYER}
|
||||
@@ -719,11 +644,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
spotlightId={SPOTLIGHT_IDS.MICROPHONE_BUTTON}
|
||||
>
|
||||
<div className={css.microphoneCircle}>
|
||||
<img
|
||||
src={micIcon}
|
||||
alt="Microphone"
|
||||
className={css.microphoneIcon}
|
||||
/>
|
||||
<img src={micIcon} alt="Microphone" className={css.microphoneIcon} />
|
||||
</div>
|
||||
</SpottableMicButton>
|
||||
|
||||
@@ -789,9 +710,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
>
|
||||
<div className={css.sectionHeader}>
|
||||
<div className={css.sectionIndicator} />
|
||||
<div className={css.sectionTitle}>
|
||||
Your Recent Searches
|
||||
</div>
|
||||
<div className={css.sectionTitle}>Your Recent Searches</div>
|
||||
</div>
|
||||
<div className={css.keywordList}>
|
||||
{recentSearches.map((keyword, index) => (
|
||||
@@ -872,9 +791,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
>
|
||||
<div className={css.sectionHeader}>
|
||||
<div className={css.sectionIndicator} />
|
||||
<div className={css.sectionTitle}>
|
||||
Hot Picks for You
|
||||
</div>
|
||||
<div className={css.sectionTitle}>Hot Picks for You</div>
|
||||
</div>
|
||||
<div className={css.productList}>
|
||||
{hotPicksForYou && hotPicksForYou.length > 0 && (
|
||||
|
||||
@@ -526,16 +526,9 @@ const VoiceInputOverlay = ({
|
||||
}
|
||||
shopperHouseDataRef.current = shopperHouseData;
|
||||
|
||||
// 약간의 지연 후 닫기 (사용자가 결과를 인지할 수 있도록)
|
||||
closeTimerRef.current = setTimeout(() => {
|
||||
onClose();
|
||||
}, 200);
|
||||
// 직접 닫기 (VoiceResponse 컴포넌트에서 결과를 표시한 후 닫음)
|
||||
onClose();
|
||||
}
|
||||
|
||||
return () => {
|
||||
// Cleanup: 컴포넌트 언마운트 또는 의존성 변경 시 타이머 정리
|
||||
clearTimerRef(closeTimerRef);
|
||||
};
|
||||
}, [shopperHouseData, isVisible, onClose]);
|
||||
|
||||
// ⛔ VUI 테스트 비활성화: STT 텍스트 수신 처리
|
||||
@@ -1008,15 +1001,25 @@ const VoiceInputOverlay = ({
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🚪 [DEBUG] handleClose called - closing overlay');
|
||||
}
|
||||
|
||||
// 1. 타이머 정리
|
||||
clearTimerRef(listeningTimerRef);
|
||||
clearTimerRef(silenceDetectionTimerRef);
|
||||
|
||||
// 2. 내부 상태 초기화
|
||||
setVoiceInputMode(null);
|
||||
setCurrentMode(VOICE_MODES.PROMPT);
|
||||
setSttResponseText('');
|
||||
setErrorMessage('');
|
||||
setIsBubbleClickSearch(false); // bubble 클릭 상태 초기화
|
||||
|
||||
// 3. Redux 정리 (VoiceInputOverlay의 책임)
|
||||
dispatch(clearShopperHouseData());
|
||||
dispatch(clearSTTText());
|
||||
|
||||
// 4. Parent에 닫기 통지
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
}, [onClose, dispatch]);
|
||||
|
||||
// Suggestion 버튼 클릭 핸들러 - Input 창에 텍스트 설정 + API 자동 호출 + response 모드 전환
|
||||
const handleSuggestionClick = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user