Compare commits
27 Commits
a
...
backup-202
| Author | SHA1 | Date | |
|---|---|---|---|
| 07a042cca6 | |||
|
|
d93960f40a | ||
| 4dfa15b4c0 | |||
| d83e9d38f0 | |||
| 8589cde061 | |||
| 92964a5063 | |||
| dba79789a8 | |||
|
|
8a882c28ca | ||
| 61f67708a9 | |||
| c9b2e5daf5 | |||
| 4f4887ebdb | |||
| 9d8cafc0a9 | |||
|
|
929a9020a1 | ||
|
|
83905a092d | ||
| e4a64644dd | |||
| 55af96bd00 | |||
|
|
8325070138 | ||
|
|
bbb9e64120 | ||
| 99ea3e6595 | |||
| 3dc4699479 | |||
|
|
8a3bcc1f9c | ||
|
|
486fb5efd5 | ||
|
|
802484debd | ||
|
|
c540378cb5 | ||
|
|
cb3a4e9bc7 | ||
|
|
3ce4398e67 | ||
|
|
78153bae0c |
@@ -85,6 +85,7 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
|
||||
patnrId: patnrId,
|
||||
chanId: chanId,
|
||||
shptmBanrTpNm: "LIVE",
|
||||
modal: false, // DeepLink 진입 시 fullscreen으로 재생
|
||||
// expsOrd: expsOrd,
|
||||
};
|
||||
break;
|
||||
@@ -101,6 +102,7 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
|
||||
patnrId: patnrId,
|
||||
showId: showId,
|
||||
shptmBanrTpNm: "VOD",
|
||||
modal: false, // DeepLink 진입 시 fullscreen으로 재생
|
||||
// expsOrd: expsOrd,
|
||||
};
|
||||
break;
|
||||
@@ -274,6 +276,18 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
|
||||
const action =
|
||||
panelName === panel_names.HOME_PANEL ? updateHomeInfo : pushPanel;
|
||||
|
||||
// 🔽 LS(Live Show) 또는 VS(VOD Show)인 경우 DeepLink 진입 플래그 설정
|
||||
if ((type === 'LS' || type === 'VS') && action === pushPanel) {
|
||||
dispatch(
|
||||
updateHomeInfo({
|
||||
name: panel_names.HOME_PANEL,
|
||||
panelInfo: {
|
||||
isDeepLinkEntry: true, // DeepLink PlayerPanel 진입 플래그
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
dispatch(
|
||||
action({
|
||||
name: panelName,
|
||||
|
||||
@@ -99,17 +99,25 @@ export const finishMediaPreview = () => (dispatch, getState) => {
|
||||
export const finishModalMediaForce = () => (dispatch, getState) => {
|
||||
const panels = getState().panels.panels;
|
||||
|
||||
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] finishModalMediaForce called', {
|
||||
// panelCount: panels.length,
|
||||
// panelNames: panels.map((p) => p.name),
|
||||
// });
|
||||
|
||||
const hasProductVideoPanel = panels.some(
|
||||
(panel) =>
|
||||
panel.name === panel_names.MEDIA_PANEL &&
|
||||
(panel.panelInfo?.modal || panel.panelInfo?.modalContainerId === 'product-video-player')
|
||||
);
|
||||
|
||||
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] hasProductVideoPanel:', hasProductVideoPanel);
|
||||
|
||||
if (hasProductVideoPanel) {
|
||||
if (startMediaFocusTimer) {
|
||||
clearTimeout(startMediaFocusTimer);
|
||||
startMediaFocusTimer = null;
|
||||
}
|
||||
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] Calling popPanel(panel_names.MEDIA_PANEL)');
|
||||
dispatch(popPanel(panel_names.MEDIA_PANEL));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { updateHomeInfo } from './homeActions';
|
||||
import { createDebugHelpers } from '../utils/debug';
|
||||
|
||||
// 디버그 헬퍼 설정
|
||||
const DEBUG_MODE = true;
|
||||
const DEBUG_MODE = false;
|
||||
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
||||
|
||||
// 시작 메뉴 추적을 위한 상수
|
||||
@@ -40,12 +40,20 @@ export const pushPanel = (panel, duplicatable = false) => ({
|
||||
});
|
||||
|
||||
export const popPanel = (panelName) => {
|
||||
const stack = new Error().stack;
|
||||
const stackLines = stack?.split('\n') || [];
|
||||
|
||||
// console.log('[💜UNIQUE_PANEL_STACK💜] popPanel action dispatcher - REMOVING PANEL:', {
|
||||
// panelName,
|
||||
// timestamp: Date.now(),
|
||||
// fullStack: stackLines.slice(1, 6).map((line) => line.trim()),
|
||||
// });
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[PANEL-TRACE] popPanel action creator', {
|
||||
console.log('[💜UNIQUE_PANEL_STACK💜] popPanel action creator stack:', {
|
||||
panelName,
|
||||
caller: new Error().stack?.split('\n')[2]?.trim(),
|
||||
caller: stackLines[2]?.trim(),
|
||||
});
|
||||
console.trace('[PANEL-TRACE] popPanel stack trace');
|
||||
}
|
||||
return {
|
||||
type: types.POP_PANEL,
|
||||
|
||||
@@ -74,7 +74,7 @@ export const startVideoPlayer =
|
||||
}) =>
|
||||
(dispatch, getState) => {
|
||||
const caller = new Error().stack?.split('\n')[2]?.trim();
|
||||
console.log('[PTRACE-SP] startVideoPlayer call', {
|
||||
dlog('[PTRACE-SP] startVideoPlayer call', {
|
||||
modal,
|
||||
modalContainerId,
|
||||
modalClassName,
|
||||
@@ -115,7 +115,7 @@ export const startVideoPlayer =
|
||||
|
||||
// 기존 PlayerPanel이 어디든 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
|
||||
if (existingPlayerPanel) {
|
||||
console.log('[PTRACE-SP] startVideoPlayer: popping existing player before push', {
|
||||
dlog('[PTRACE-SP] startVideoPlayer: popping existing player before push', {
|
||||
stack: panels.map((p) => p.name),
|
||||
});
|
||||
dlog('[startVideoPlayer] 🔄 Resetting existing PLAYER_PANEL before start');
|
||||
@@ -148,14 +148,14 @@ export const startVideoPlayer =
|
||||
|
||||
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
|
||||
// if (modal && modalContainerId && !spotlightDisable) {
|
||||
// console.log('[startVideoPlayer] 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
||||
// dlog('[startVideoPlayer] 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
||||
// Spotlight.setPointerMode(false);
|
||||
// startVideoFocusTimer = setTimeout(() => {
|
||||
// console.log('[startVideoPlayer] 🔍 Spotlight.focus called');
|
||||
// dlog('[startVideoPlayer] 🔍 Spotlight.focus called');
|
||||
// Spotlight.focus(modalContainerId);
|
||||
// }, 0);
|
||||
// } else {
|
||||
// console.log('[startVideoPlayer] ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
||||
// dlog('[startVideoPlayer] ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
||||
// }
|
||||
|
||||
dlog('[startVideoPlayer] ✅ END');
|
||||
@@ -196,7 +196,7 @@ export const startVideoPlayerNew =
|
||||
}) =>
|
||||
(dispatch, getState) => {
|
||||
const caller = new Error().stack?.split('\n')[2]?.trim();
|
||||
console.log('[PTRACE-SPN] startVideoPlayerNew call', {
|
||||
dlog('[PTRACE-SPN] startVideoPlayerNew call', {
|
||||
bannerId,
|
||||
modal,
|
||||
modalContainerId,
|
||||
@@ -239,7 +239,7 @@ export const startVideoPlayerNew =
|
||||
|
||||
// 기존 PlayerPanel이 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
|
||||
if (existingPlayerPanel) {
|
||||
console.log('[PTRACE-SPN] popping existing player before push', {
|
||||
dlog('[PTRACE-SPN] popping existing player before push', {
|
||||
stack: panels.map((p) => p.name),
|
||||
});
|
||||
dlog('[startVideoPlayerNew] *** 🔄 Resetting existing PLAYER_PANEL before start');
|
||||
@@ -332,14 +332,14 @@ export const startVideoPlayerNew =
|
||||
|
||||
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
|
||||
// if (modal && modalContainerId && !spotlightDisable) {
|
||||
// console.log('[startVideoPlayerNew] *** 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
||||
// dlog('[startVideoPlayerNew] *** 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
||||
// Spotlight.setPointerMode(false);
|
||||
// startVideoFocusTimer = setTimeout(() => {
|
||||
// console.log('[startVideoPlayerNew] *** 🔍 Spotlight.focus called');
|
||||
// dlog('[startVideoPlayerNew] *** 🔍 Spotlight.focus called');
|
||||
// Spotlight.focus(modalContainerId);
|
||||
// }, 0);
|
||||
// } else {
|
||||
// console.log('[startVideoPlayerNew] *** ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
||||
// dlog('[startVideoPlayerNew] *** ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
||||
// }
|
||||
|
||||
dlog('[startVideoPlayerNew] *** ✅ END');
|
||||
@@ -352,7 +352,7 @@ export const finishVideoPreview = () => (dispatch, getState) => {
|
||||
const panels = getState().panels.panels;
|
||||
const topPanel = panels[panels.length - 1];
|
||||
if (topPanel && topPanel.name === panel_names.PLAYER_PANEL && topPanel.panelInfo.modal) {
|
||||
console.log('[PANEL-TRACE] finishVideoPreview: popping modal player', {
|
||||
dlog('[PANEL-TRACE] finishVideoPreview: popping modal player', {
|
||||
topPanelName: topPanel.name,
|
||||
modal: topPanel.panelInfo.modal,
|
||||
stack: panels.map((p) => p.name),
|
||||
@@ -417,7 +417,7 @@ export const pauseModalVideo = () => (dispatch, getState) => {
|
||||
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
|
||||
);
|
||||
|
||||
console.log('[Detail-BG] ⏸️ pauseModalVideo - Pausing modal video', {
|
||||
dlog('[Detail-BG] ⏸️ pauseModalVideo - Pausing modal video', {
|
||||
found: !!modalPlayerPanel,
|
||||
playerPanelModal: modalPlayerPanel?.panelInfo?.modal,
|
||||
currentIsPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
||||
@@ -438,11 +438,11 @@ export const pauseModalVideo = () => (dispatch, getState) => {
|
||||
})
|
||||
);
|
||||
|
||||
console.log('[Detail-BG] ✅ pauseModalVideo - Modal video paused successfully', {
|
||||
dlog('[Detail-BG] ✅ pauseModalVideo - Modal video paused successfully', {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} else {
|
||||
console.log('[Detail-BG] ⚠️ pauseModalVideo - No modal PlayerPanel found', {
|
||||
dlog('[Detail-BG] ⚠️ pauseModalVideo - No modal PlayerPanel found', {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
@@ -457,7 +457,7 @@ export const resumeModalVideo = () => (dispatch, getState) => {
|
||||
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
|
||||
);
|
||||
|
||||
console.log('[Detail-BG] ▶️ resumeModalVideo - Resuming modal video', {
|
||||
dlog('[Detail-BG] ▶️ resumeModalVideo - Resuming modal video', {
|
||||
found: !!modalPlayerPanel,
|
||||
playerPanelModal: modalPlayerPanel?.panelInfo?.modal,
|
||||
currentIsPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
||||
@@ -478,11 +478,11 @@ export const resumeModalVideo = () => (dispatch, getState) => {
|
||||
})
|
||||
);
|
||||
|
||||
console.log('[Detail-BG] ✅ resumeModalVideo - Modal video resumed successfully', {
|
||||
dlog('[Detail-BG] ✅ resumeModalVideo - Modal video resumed successfully', {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} else {
|
||||
console.log('[Detail-BG] ⚠️ resumeModalVideo - Modal video not paused or panel not found', {
|
||||
dlog('[Detail-BG] ⚠️ resumeModalVideo - Modal video not paused or panel not found', {
|
||||
found: !!modalPlayerPanel,
|
||||
isPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
||||
timestamp: Date.now(),
|
||||
@@ -499,7 +499,7 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => {
|
||||
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
|
||||
);
|
||||
|
||||
console.log('[Detail-BG] ⏸️ pauseFullscreenVideo - Pausing fullscreen video', {
|
||||
dlog('[Detail-BG] ⏸️ pauseFullscreenVideo - Pausing fullscreen video', {
|
||||
found: !!fullscreenPlayerPanel,
|
||||
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
|
||||
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
||||
@@ -517,11 +517,11 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => {
|
||||
})
|
||||
);
|
||||
|
||||
console.log('[Detail-BG] ✅ pauseFullscreenVideo - Fullscreen video paused successfully', {
|
||||
dlog('[Detail-BG] ✅ pauseFullscreenVideo - Fullscreen video paused successfully', {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} else {
|
||||
console.log('[Detail-BG] ⚠️ pauseFullscreenVideo - No fullscreen PlayerPanel found', {
|
||||
dlog('[Detail-BG] ⚠️ pauseFullscreenVideo - No fullscreen PlayerPanel found', {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
@@ -536,7 +536,7 @@ export const resumeFullscreenVideo = () => (dispatch, getState) => {
|
||||
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
|
||||
);
|
||||
|
||||
console.log('[Detail-BG] ▶️ resumeFullscreenVideo - Resuming fullscreen video', {
|
||||
dlog('[Detail-BG] ▶️ resumeFullscreenVideo - Resuming fullscreen video', {
|
||||
found: !!fullscreenPlayerPanel,
|
||||
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
|
||||
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
||||
@@ -554,11 +554,11 @@ export const resumeFullscreenVideo = () => (dispatch, getState) => {
|
||||
})
|
||||
);
|
||||
|
||||
console.log('[Detail-BG] ✅ resumeFullscreenVideo - Fullscreen video resumed successfully', {
|
||||
dlog('[Detail-BG] ✅ resumeFullscreenVideo - Fullscreen video resumed successfully', {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} else {
|
||||
console.log('[Detail-BG] ⚠️ resumeFullscreenVideo - Fullscreen video not paused or panel not found', {
|
||||
dlog('[Detail-BG] ⚠️ resumeFullscreenVideo - Fullscreen video not paused or panel not found', {
|
||||
found: !!fullscreenPlayerPanel,
|
||||
isPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
||||
timestamp: Date.now(),
|
||||
@@ -601,7 +601,7 @@ export const hideModalVideo = () => (dispatch, getState) => {
|
||||
},
|
||||
};
|
||||
|
||||
// console.log('[HomePanel] hideModalVideo: saving shrinkInfo', {
|
||||
// dlog('[HomePanel] hideModalVideo: saving shrinkInfo', {
|
||||
// shrinkInfo: updatedPlayerState.shrinkInfo,
|
||||
// modalStyle: panelInfo.modalStyle,
|
||||
// });
|
||||
@@ -1038,7 +1038,7 @@ export const resumePlayerControl = (ownerId) => (dispatch, getState) => {
|
||||
* 이 액션은 어떤 배너에서든 클릭 시 호출됩니다.
|
||||
*/
|
||||
export const goToFullScreen = () => (dispatch, getState) => {
|
||||
console.log('[Detail-BG] 🎬 goToFullScreen - Setting PlayerPanel to fullscreen mode', {
|
||||
dlog('[Detail-BG] 🎬 goToFullScreen - Setting PlayerPanel to fullscreen mode', {
|
||||
targetModal: false,
|
||||
action: 'updatePanel',
|
||||
timestamp: Date.now(),
|
||||
@@ -1055,7 +1055,7 @@ export const goToFullScreen = () => (dispatch, getState) => {
|
||||
})
|
||||
);
|
||||
|
||||
console.log('[Detail-BG] ✅ goToFullScreen - PlayerPanel modal set to false (fullscreen)', {
|
||||
dlog('[Detail-BG] ✅ goToFullScreen - PlayerPanel modal set to false (fullscreen)', {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
};
|
||||
@@ -1268,7 +1268,7 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
||||
...rest
|
||||
} = videoInfo;
|
||||
|
||||
console.log('[Detail-BG] 🎥 startBannerVideo - Starting banner video', {
|
||||
dlog('[Detail-BG] 🎥 startBannerVideo - Starting banner video', {
|
||||
modalStatus: modal,
|
||||
bannerId,
|
||||
displayMode: modal ? 'VISIBLE (modal=true)' : 'FULLSCREEN (modal=false)',
|
||||
@@ -1295,7 +1295,7 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
||||
// 기존 PlayerPanel이 있으면 초기화
|
||||
if (existingPlayerPanel) {
|
||||
dlog('[startBannerVideo] 🔄 Resetting existing PLAYER_PANEL before start');
|
||||
console.log('[Detail-BG] 🔄 startBannerVideo - Clearing existing PlayerPanel', {
|
||||
dlog('[Detail-BG] 🔄 startBannerVideo - Clearing existing PlayerPanel', {
|
||||
existingModalStatus: existingPlayerPanel.panelInfo?.modal,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
@@ -1304,7 +1304,7 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
||||
}
|
||||
|
||||
// 새로운 PlayerPanel push
|
||||
console.log('[Detail-BG] ➕ startBannerVideo - Pushing new PlayerPanel with modal status', {
|
||||
dlog('[Detail-BG] ➕ startBannerVideo - Pushing new PlayerPanel with modal status', {
|
||||
modal,
|
||||
modalContainerId,
|
||||
timestamp: Date.now(),
|
||||
@@ -1331,7 +1331,7 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
||||
)
|
||||
);
|
||||
|
||||
console.log('[Detail-BG] ✅ startBannerVideo - PlayerPanel pushed with modal=' + modal, {
|
||||
dlog('[Detail-BG] ✅ startBannerVideo - PlayerPanel pushed with modal=' + modal, {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
padding: @slider-padding-v 0;
|
||||
height: @sand-mediaplayer-slider-height;
|
||||
right: 154px;
|
||||
width: 1466px;
|
||||
width: 1558px;
|
||||
// Add a tap area that extends to the edges of the screen, to make the slider more accessible
|
||||
&::before {
|
||||
content: "";
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
position: absolute;
|
||||
font-family: @baseFont;
|
||||
width: 100%;
|
||||
right: 90px;
|
||||
right: 20px;
|
||||
bottom: -5px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
@@ -16,12 +16,12 @@
|
||||
letter-spacing: -1px;
|
||||
.separator {
|
||||
position: absolute;
|
||||
right: 105px;
|
||||
right: 95px;
|
||||
bottom: -5px;
|
||||
}
|
||||
.currentTime {
|
||||
position: absolute;
|
||||
right: 130px;
|
||||
right: 120px;
|
||||
bottom: -5px;
|
||||
}
|
||||
.totalTime {
|
||||
|
||||
@@ -483,7 +483,7 @@
|
||||
.default-style();
|
||||
|
||||
.scrollInfo {
|
||||
width: 900px;
|
||||
width: 850px;
|
||||
background-color: @BG_COLOR_01;
|
||||
color: @COLOR_GRAY03;
|
||||
display: flex;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { calculateIsPanelOnTop } from '../utils/panelUtils'; // 🎯 isOnTop 유
|
||||
|
||||
// DEBUG_MODE - true인 경우에만 로그 출력
|
||||
// ⚠️ [251122] panelHistory 로그 비활성화 - 로그 생성 차단
|
||||
const DEBUG_MODE = true;
|
||||
const DEBUG_MODE = false;
|
||||
|
||||
/**
|
||||
* Panel history middleware
|
||||
@@ -33,8 +33,8 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
||||
(action.type.includes('PANEL') || action.type === 'CLEAR_PANEL_HISTORY')
|
||||
) {
|
||||
const caller = new Error().stack.split('\n')[1]?.trim();
|
||||
console.log(`[PANEL DEBUG] ${action.type} from: ${caller}`);
|
||||
console.log(' Payload:', action.payload);
|
||||
// console.log(`[PANEL DEBUG] ${action.type} from: ${caller}`);
|
||||
// console.log(' Payload:', action.payload);
|
||||
}
|
||||
|
||||
// GNB 호출 식별을 위한 helper 함수
|
||||
@@ -81,7 +81,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
||||
const isGNB = isGNBCall();
|
||||
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
|
||||
if (DEBUG_MODE)
|
||||
console.log('[PANEL] PUSH_PANEL:', {
|
||||
console.log(`[PANEL] PUSH_PANEL: ${panelName}`, {
|
||||
panelName,
|
||||
panelInfo,
|
||||
isGNB,
|
||||
@@ -106,7 +106,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
||||
const stateAfter = store.getState();
|
||||
const panelHistoryAfter = stateAfter.panelHistory;
|
||||
const panelsAfter = stateAfter.panels.panels;
|
||||
console.log('[PANEL_HISTORY] After PUSH_PANEL:', {
|
||||
console.log(`[PANEL_HISTORY] PUSH_PANEL: ${panelName}`, {
|
||||
panelHistory: panelHistoryAfter,
|
||||
panels: panelsAfter,
|
||||
});
|
||||
@@ -126,7 +126,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
||||
if (panels.length > 0) {
|
||||
const topPanel = panels[panels.length - 1];
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[PANEL-TRACE] POP_PANEL middleware stack', {
|
||||
console.log(`[PANEL-TRACE] POP_PANEL middleware stack: ${topPanel?.name}`, {
|
||||
stack: panels.map((p) => p.name),
|
||||
topPanel: topPanel?.name,
|
||||
payload: action.payload,
|
||||
@@ -137,7 +137,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
||||
const isGNB = isGNBCall();
|
||||
const isOnTop = calculateIsOnTop(topPanel.name); // 🎯 isOnTop 계산
|
||||
if (DEBUG_MODE)
|
||||
console.log('[PANEL] POP_PANEL:', {
|
||||
console.log(`[PANEL] POP_PANEL: ${topPanel.name}`, {
|
||||
panelName: topPanel.name,
|
||||
panelInfo: topPanel.panelInfo || {},
|
||||
isGNB,
|
||||
@@ -162,7 +162,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
||||
const stateAfter = store.getState();
|
||||
const panelHistoryAfter = stateAfter.panelHistory;
|
||||
const panelsAfter = stateAfter.panels.panels;
|
||||
console.log('[PANEL_HISTORY] After POP_PANEL:', {
|
||||
console.log(`[PANEL_HISTORY] POP_PANEL: ${topPanel.name}`, {
|
||||
panelHistory: panelHistoryAfter,
|
||||
panels: panelsAfter,
|
||||
});
|
||||
@@ -185,7 +185,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
||||
const isGNB = isGNBCall();
|
||||
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
|
||||
if (DEBUG_MODE)
|
||||
console.log('[PANEL] UPDATE_PANEL:', {
|
||||
console.log(`[PANEL] UPDATE_PANEL: ${panelName}`, {
|
||||
panelName,
|
||||
panelInfo,
|
||||
isGNB,
|
||||
@@ -210,7 +210,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
||||
const stateAfter = store.getState();
|
||||
const panelHistoryAfter = stateAfter.panelHistory;
|
||||
const panelsAfter = stateAfter.panels.panels;
|
||||
console.log('[PANEL_HISTORY] After UPDATE_PANEL:', {
|
||||
console.log(`[PANEL_HISTORY] UPDATE_PANEL: ${panelName}`, {
|
||||
panelHistory: panelHistoryAfter,
|
||||
panels: panelsAfter,
|
||||
});
|
||||
@@ -226,11 +226,15 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
||||
|
||||
// RESET_PANELS: GNB 네비게이션 또는 완전 초기화
|
||||
case types.RESET_PANELS: {
|
||||
if (DEBUG_MODE)
|
||||
console.log('[PANEL] RESET_PANELS:', {
|
||||
if (DEBUG_MODE) {
|
||||
const resetPanelNameForLog = (action.payload && action.payload.length > 0)
|
||||
? action.payload[0].name
|
||||
: 'homepanel';
|
||||
console.log(`[PANEL] RESET_PANELS: ${resetPanelNameForLog}`, {
|
||||
payload: action.payload,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
if (DEBUG_MODE)
|
||||
console.log('[PANEL_HISTORY] Before RESET_PANELS:', store.getState().panelHistory);
|
||||
|
||||
@@ -292,7 +296,10 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
||||
const stateAfter = store.getState();
|
||||
const panelHistoryAfter = stateAfter.panelHistory;
|
||||
const panelsAfter = stateAfter.panels.panels;
|
||||
console.log('[PANEL_HISTORY] After RESET_PANELS:', {
|
||||
const resetPanelName = (action.payload && action.payload.length > 0)
|
||||
? action.payload[0].name
|
||||
: 'homepanel';
|
||||
console.log(`[PANEL_HISTORY] RESET_PANELS: ${resetPanelName}`, {
|
||||
panelHistory: panelHistoryAfter,
|
||||
panels: panelsAfter,
|
||||
});
|
||||
|
||||
@@ -94,6 +94,12 @@ export const panelsReducer = (state = initialState, action) => {
|
||||
}
|
||||
|
||||
case types.POP_PANEL: {
|
||||
// console.log('[💜UNIQUE_PANEL_STACK💜] POP_PANEL reducer START', {
|
||||
// targetPanel: action.payload || 'last_panel',
|
||||
// currentPanels: state.panels.map((p) => p.name),
|
||||
// timestamp: Date.now(),
|
||||
// });
|
||||
|
||||
dlog('[panelReducer] 🔴 POP_PANEL START', {
|
||||
targetPanel: action.payload || 'last_panel',
|
||||
currentPanels: state.panels.map((p) => p.name),
|
||||
@@ -118,6 +124,13 @@ export const panelsReducer = (state = initialState, action) => {
|
||||
resultPanels = state.panels.slice(0, state.panels.length - 1);
|
||||
}
|
||||
|
||||
// console.log('[💜UNIQUE_PANEL_STACK💜] POP_PANEL reducer END', {
|
||||
// resultPanels: resultPanels.map((p) => p.name),
|
||||
// panelCount: resultPanels.length,
|
||||
// lastAction,
|
||||
// timestamp: Date.now(),
|
||||
// });
|
||||
|
||||
dlog('[panelReducer] 🔴 POP_PANEL END', {
|
||||
resultPanels: resultPanels.map((p) => p.name),
|
||||
lastAction,
|
||||
|
||||
@@ -114,6 +114,7 @@ export const ACTIVE_POPUP = {
|
||||
optionalConfirm: 'optionalConfirm',
|
||||
energyPopup: 'energyPopup',
|
||||
addCartPopup: 'addCartPopup',
|
||||
scrollPopup: 'scrollPopup',
|
||||
};
|
||||
export const DEBUG_VIDEO_SUBTITLE_TEST = false;
|
||||
export const AUTO_SCROLL_DELAY = 600;
|
||||
|
||||
@@ -458,7 +458,7 @@ const tap = fp.curry((fn, value) => {
|
||||
* @param {*} value 대상 값
|
||||
*/
|
||||
const trace = fp.curry((label, value) => {
|
||||
console.log(label, value);
|
||||
// console.log(label, value);
|
||||
return value;
|
||||
});
|
||||
|
||||
|
||||
@@ -280,6 +280,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] DetailPanel cleanup - calling finishModalMediaForce');
|
||||
dispatch(finishModalMediaForce());
|
||||
};
|
||||
}, [dispatch]);
|
||||
@@ -303,26 +304,25 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
const sourcePanel = panelInfo?.sourcePanel;
|
||||
const sourceMenu = panelInfo?.sourceMenu;
|
||||
|
||||
console.log('[DP-TRACE] Detail unmount start', {
|
||||
sourcePanel,
|
||||
sourceMenu,
|
||||
panelsSnapshot: panels.map((p) => p.name),
|
||||
});
|
||||
|
||||
console.log('[Detail-BG] 306-line sourcePanel:', sourcePanel, 'sourceMenu:', sourceMenu);
|
||||
// console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel cleanup/unmount triggered', {
|
||||
// sourcePanel,
|
||||
// sourceMenu,
|
||||
// panelsSnapshot: panels.map((p) => p.name),
|
||||
// timestamp: Date.now(),
|
||||
// });
|
||||
|
||||
// DetailPanel이 unmount되는 시점
|
||||
console.log('[DetailPanel] unmount:', {
|
||||
sourcePanel,
|
||||
sourceMenu,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
// console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel unmount details:', {
|
||||
// sourcePanel,
|
||||
// sourceMenu,
|
||||
// timestamp: Date.now(),
|
||||
// });
|
||||
|
||||
// sourcePanel에 따른 상태 업데이트
|
||||
switch (sourcePanel) {
|
||||
case panel_names.PLAYER_PANEL: {
|
||||
// PlayerPanel에서 온 경우: PlayerPanel에 detailPanelClosed flag 전달
|
||||
console.log('[DetailPanel] unmount - PlayerPanel에 detailPanelClosed flag 전달');
|
||||
console.log('[PANEL][DetailPanel] unmount - PlayerPanel에 detailPanelClosed flag 전달');
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.PLAYER_PANEL,
|
||||
@@ -385,6 +385,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
const sourcePanel = panelInfo?.sourcePanel;
|
||||
const sourceMenu = panelInfo?.sourceMenu;
|
||||
|
||||
// console.log('[🟠UNIQUE_DETAIL_BACK🟠] onBackClick triggered', {
|
||||
// sourcePanel,
|
||||
// sourceMenu,
|
||||
// isCancelClick,
|
||||
// currentPanels: panels.map((p) => p.name),
|
||||
// timestamp: Date.now(),
|
||||
// });
|
||||
|
||||
fp.pipe(
|
||||
() => {
|
||||
dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거
|
||||
@@ -393,7 +401,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
switch (sourcePanel) {
|
||||
case panel_names.PLAYER_PANEL:
|
||||
// PlayerPanel에서 온 경우: 플레이어 비디오는 그대로 두고 모달만 정리
|
||||
console.log('[DetailPanel] onBackClick - PlayerPanel 출신: 모달 정리만 수행');
|
||||
console.log('[🟠UNIQUE_DETAIL_BACK🟠] PlayerPanel 출신: 모달 정리만 수행');
|
||||
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
|
||||
dispatch(finishVideoPreview());
|
||||
break;
|
||||
@@ -402,17 +410,18 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
case panel_names.SEARCH_PANEL:
|
||||
default:
|
||||
// HomePanel, SearchPanel 등에서 온 경우: 백그라운드 비디오 일시 중지
|
||||
console.log(
|
||||
'[DetailPanel] onBackClick - source panel:',
|
||||
sourcePanel,
|
||||
'백그라운드 비디오 일시 중지'
|
||||
);
|
||||
// console.log(
|
||||
// '[🟠UNIQUE_DETAIL_BACK🟠] source panel:',
|
||||
// sourcePanel,
|
||||
// '백그라운드 비디오 일시 중지'
|
||||
// );
|
||||
dispatch(pauseFullscreenVideo()); // PLAYER_PANEL 비디오 중지
|
||||
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
|
||||
dispatch(finishVideoPreview());
|
||||
break;
|
||||
}
|
||||
|
||||
// console.log('[🟠UNIQUE_DETAIL_BACK🟠] Calling popPanel(DETAIL_PANEL)');
|
||||
dispatch(popPanel(panel_names.DETAIL_PANEL));
|
||||
},
|
||||
() => {
|
||||
@@ -434,7 +443,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
if (shouldUpdatePanel) {
|
||||
console.log(
|
||||
'[DetailPanel] onBackClick - PlayerPanel에 detailPanelClosed flag 전달'
|
||||
'[PANEL][DetailPanel] onBackClick - PlayerPanel에 detailPanelClosed flag 전달'
|
||||
);
|
||||
dispatch(
|
||||
updatePanel({
|
||||
|
||||
@@ -9,30 +9,21 @@ import React, {
|
||||
import classNames from 'classnames';
|
||||
// import { throttle } from 'lodash';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
// src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import couponImg from '../../../../assets/images/icons/coupon.png';
|
||||
import arrowDownIcon from '../../../../assets/images/icons/ic-arrow-down.svg';
|
||||
// import Spottable from '@enact/spotlight/Spottable';
|
||||
//image
|
||||
import arrowDown
|
||||
from '../../../../assets/images/icons/ic_arrow_down_3x_new.png';
|
||||
import indicatorDefaultImage
|
||||
from '../../../../assets/images/img-thumb-empty-144@3x.png';
|
||||
import {
|
||||
setHidePopup,
|
||||
setShowPopup,
|
||||
} from '../../../actions/commonActions.js';
|
||||
import arrowDown from '../../../../assets/images/icons/ic_arrow_down_3x_new.png';
|
||||
import indicatorDefaultImage from '../../../../assets/images/img-thumb-empty-144@3x.png';
|
||||
import { setHidePopup, setShowPopup } from '../../../actions/commonActions.js';
|
||||
import {
|
||||
getProductCouponDownload,
|
||||
getProductCouponSearch,
|
||||
@@ -62,8 +53,7 @@ import CustomImage from '../../../components/CustomImage/CustomImage.jsx';
|
||||
// ProductInfoSection imports
|
||||
import TButton, { TYPES } from '../../../components/TButton/TButton';
|
||||
import TPopUp from '../../../components/TPopUp/TPopUp.jsx';
|
||||
import TVirtualGridList
|
||||
from '../../../components/TVirtualGridList/TVirtualGridList.jsx';
|
||||
import TVirtualGridList from '../../../components/TVirtualGridList/TVirtualGridList.jsx';
|
||||
import useReviews from '../../../hooks/useReviews/useReviews';
|
||||
import useScrollTo from '../../../hooks/useScrollTo';
|
||||
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
|
||||
@@ -89,10 +79,7 @@ import {
|
||||
tap,
|
||||
when,
|
||||
} from '../../../utils/fp';
|
||||
import {
|
||||
$L,
|
||||
formatGMTString,
|
||||
} from '../../../utils/helperMethods';
|
||||
import { $L, formatGMTString } from '../../../utils/helperMethods';
|
||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||
import ShowUserReviews from '../../UserReview/ShowUserReviews';
|
||||
// import CustomScrollbar from '../components/CustomScrollbar/CustomScrollbar';
|
||||
@@ -103,21 +90,14 @@ import StarRating from '../components/StarRating';
|
||||
// ProductContentSection imports
|
||||
import TScrollerDetail from '../components/TScroller/TScrollerDetail';
|
||||
import DetailPanelSkeleton from '../DetailPanelSkeleton/DetailPanelSkeleton';
|
||||
import ProductDescription
|
||||
from '../ProductContentSection/ProductDescription/ProductDescription';
|
||||
import ProductDetail
|
||||
from '../ProductContentSection/ProductDetail/ProductDetail.new';
|
||||
import {
|
||||
ProductVideoV2,
|
||||
} from '../ProductContentSection/ProductVideo/ProductVideo.v2.jsx';
|
||||
import ProductVideo
|
||||
from '../ProductContentSection/ProductVideo/ProductVideo.v3';
|
||||
import SeeMoreProducts
|
||||
from '../ProductContentSection/SeeMoreProducts/SeeMoreProducts';
|
||||
import ProductDescription from '../ProductContentSection/ProductDescription/ProductDescription';
|
||||
import ProductDetail from '../ProductContentSection/ProductDetail/ProductDetail.new';
|
||||
import { ProductVideoV2 } from '../ProductContentSection/ProductVideo/ProductVideo.v2.jsx';
|
||||
import ProductVideo from '../ProductContentSection/ProductVideo/ProductVideo.v3';
|
||||
import SeeMoreProducts from '../ProductContentSection/SeeMoreProducts/SeeMoreProducts';
|
||||
import UserReviews from '../ProductContentSection/UserReviews/UserReviews';
|
||||
// import ViewAllReviewsButton from '../ProductContentSection/UserReviews/ViewAllReviewsButton';
|
||||
import YouMayAlsoLike
|
||||
from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
|
||||
import YouMayAlsoLike from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
|
||||
import QRCode from '../ProductInfoSection/QRCode/QRCode';
|
||||
import ProductOverview from '../ProductOverview/ProductOverview';
|
||||
// CSS imports
|
||||
@@ -166,7 +146,8 @@ const ShopByMobileContainer = SpotlightContainerDecorator(
|
||||
spotlightDirection: 'horizontal',
|
||||
enterTo: 'last-focused',
|
||||
restrict: 'self-only',
|
||||
defaultElement: SpotlightIds?.DETAIL_SHOPBYMOBILE || 'detail_shop_by_mobile',
|
||||
defaultElement:
|
||||
SpotlightIds?.DETAIL_SHOPBYMOBILE || 'detail_shop_by_mobile',
|
||||
},
|
||||
'div'
|
||||
);
|
||||
@@ -258,17 +239,27 @@ export default function ProductAllSection({
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Redux 상태
|
||||
const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion);
|
||||
const webOSVersion = useSelector(
|
||||
(state) => state.common.appStatus.webOSVersion
|
||||
);
|
||||
const groupInfos = useSelector((state) => state.product.groupInfo);
|
||||
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
||||
|
||||
// YouMayLike 데이터는 API 응답 시간이 걸리므로 직접 구독
|
||||
const youmaylikeData = useSelector((state) => state.main.youmaylikeData);
|
||||
//coupon
|
||||
const { partnerCoupon } = useSelector((state) => state.coupon.productCouponSearchData);
|
||||
const { userNumber } = useSelector((state) => state.common.appStatus.loginUserData);
|
||||
const { popupVisible, activePopup } = useSelector((state) => state.common.popup);
|
||||
const cursorVisible = useSelector((state) => state.common.appStatus.cursorVisible);
|
||||
const { partnerCoupon } = useSelector(
|
||||
(state) => state.coupon.productCouponSearchData
|
||||
);
|
||||
const { userNumber } = useSelector(
|
||||
(state) => state.common.appStatus.loginUserData
|
||||
);
|
||||
const { popupVisible, activePopup } = useSelector(
|
||||
(state) => state.common.popup
|
||||
);
|
||||
const cursorVisible = useSelector(
|
||||
(state) => state.common.appStatus.cursorVisible
|
||||
);
|
||||
// 🆕 [251210] patnrId=21 카테고리 그룹 데이터
|
||||
const brandShopByShowCategoryGroups = useSelector(
|
||||
(state) => state.brand.brandShopByShowCategoryGroups
|
||||
@@ -293,12 +284,15 @@ export default function ProductAllSection({
|
||||
|
||||
// 출처 정보 통합 (향후 확장성 대비)
|
||||
// YouMayLike 상품이 아닐 경우 fromPanel을 초기화하여 오기 방지
|
||||
const fromPanel = useMemo(() => ({
|
||||
const fromPanel = useMemo(
|
||||
() => ({
|
||||
fromYouMayLike: panelInfo?.fromPanel?.fromYouMayLike || false,
|
||||
// 향후 다른 출처 플래그들 추가 가능
|
||||
// fromRecommendation: panelInfo?.fromPanel?.fromRecommendation || false,
|
||||
// fromSearch: panelInfo?.fromPanel?.fromSearch || false,
|
||||
}), [panelInfo?.fromPanel?.fromYouMayLike]);
|
||||
}),
|
||||
[panelInfo?.fromPanel?.fromYouMayLike]
|
||||
);
|
||||
|
||||
//구매 하단 토스트 노출 확인을 위한 용도
|
||||
const [openToast, setOpenToast] = useState(false);
|
||||
@@ -332,16 +326,13 @@ export default function ProductAllSection({
|
||||
scrollTop({ y: 0, animate: true });
|
||||
setTimeout(() => {
|
||||
if (hasVideo) {
|
||||
Spotlight.focus("product-video-player");
|
||||
Spotlight.focus('product-video-player');
|
||||
} else {
|
||||
Spotlight.focus("product-detail-container-0");
|
||||
Spotlight.focus('product-detail-container-0');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}, [
|
||||
scrollTop,
|
||||
hasVideo
|
||||
]);
|
||||
}, [scrollTop, hasVideo]);
|
||||
|
||||
const fetchCouponData = useCallback(() => {
|
||||
dispatch(
|
||||
@@ -403,7 +394,9 @@ export default function ProductAllSection({
|
||||
|
||||
const handleCouponTotDownload = useCallback(() => {
|
||||
if (selectedCoupon && userNumber) {
|
||||
const couponCodesArray = couponCodes.split(',').map((code) => parseInt(code.trim()));
|
||||
const couponCodesArray = couponCodes
|
||||
.split(',')
|
||||
.map((code) => parseInt(code.trim()));
|
||||
setDownloadCouponArr((prevArr) => [...prevArr, ...couponCodesArray]);
|
||||
|
||||
dispatch(
|
||||
@@ -474,7 +467,9 @@ export default function ProductAllSection({
|
||||
return;
|
||||
}
|
||||
setDownloadCouponArr((prevArr) => [...prevArr, cpnSno]);
|
||||
dispatch(getProductCouponDownload({ mbrNo: userNumber, cpnSno: cpnSno }));
|
||||
dispatch(
|
||||
getProductCouponDownload({ mbrNo: userNumber, cpnSno: cpnSno })
|
||||
);
|
||||
dispatch(showToast({ message: 'Your coupon download is complete.' }));
|
||||
};
|
||||
|
||||
@@ -489,7 +484,13 @@ export default function ProductAllSection({
|
||||
>
|
||||
<div
|
||||
className={css.couponItem}
|
||||
aria-label={'Purchase over ' + cpnAplyMinPurcAmt + 'up to ' + cpnAplyMaxDcAmt + ' off'}
|
||||
aria-label={
|
||||
'Purchase over ' +
|
||||
cpnAplyMinPurcAmt +
|
||||
'up to ' +
|
||||
cpnAplyMaxDcAmt +
|
||||
' off'
|
||||
}
|
||||
>
|
||||
<div className={css.couponTopContents}>
|
||||
{shptmDcTpCd === 'CPN00401' && (
|
||||
@@ -505,7 +506,9 @@ export default function ProductAllSection({
|
||||
|
||||
<div className={css.couponMiddleContents}>
|
||||
<span>
|
||||
{$L('Purchase over ${cpnAplyMinPurcAmt} (up to ${cpnAplyMaxDcAmt} off)')
|
||||
{$L(
|
||||
'Purchase over ${cpnAplyMinPurcAmt} (up to ${cpnAplyMaxDcAmt} off)'
|
||||
)
|
||||
.replace('{cpnAplyMinPurcAmt}', cpnAplyMinPurcAmt)
|
||||
.replace('{cpnAplyMaxDcAmt}', cpnAplyMaxDcAmt)}
|
||||
</span>
|
||||
@@ -518,7 +521,9 @@ export default function ProductAllSection({
|
||||
<div
|
||||
className={classNames(
|
||||
css.couponBottomButton,
|
||||
downloadCouponArr.length > 0 && downloadCouponArr.includes(cpnSno) && css.disable,
|
||||
downloadCouponArr.length > 0 &&
|
||||
downloadCouponArr.includes(cpnSno) &&
|
||||
css.disable,
|
||||
duplDwldYn === 'N' && downloadYn === 'Y' && css.disable,
|
||||
!downloadCouponArr.includes(cpnSno) &&
|
||||
index === selectedCouponIndex &&
|
||||
@@ -527,7 +532,9 @@ export default function ProductAllSection({
|
||||
)}
|
||||
aria-label="Download Button"
|
||||
>
|
||||
{downloadCouponArr.length > 0 && downloadCouponArr.includes(cpnSno) && duplDwldYn === 'N'
|
||||
{downloadCouponArr.length > 0 &&
|
||||
downloadCouponArr.includes(cpnSno) &&
|
||||
duplDwldYn === 'N'
|
||||
? $L('DOWNLOAD COMPLETED')
|
||||
: $L('DOWNLOAD')}
|
||||
</div>
|
||||
@@ -566,13 +573,25 @@ export default function ProductAllSection({
|
||||
const [activeButton, setActiveButton] = useState(null);
|
||||
|
||||
const productData = useMemo(
|
||||
() => getProductData(productType, themeProductInfo, themeProducts, selectedIndex, productInfo),
|
||||
() =>
|
||||
getProductData(
|
||||
productType,
|
||||
themeProductInfo,
|
||||
themeProducts,
|
||||
selectedIndex,
|
||||
productInfo
|
||||
),
|
||||
[productType, themeProductInfo, themeProducts, selectedIndex, productInfo]
|
||||
);
|
||||
|
||||
// ProductDescription 데이터 유무 확인
|
||||
const hasProductDescription = useMemo(() => {
|
||||
return !!productData?.prdtDesc;
|
||||
}, [productData?.prdtDesc]);
|
||||
|
||||
// 🆕 [251211] patnrId=21인 경우 QR 데이터 확인
|
||||
useEffect(() => {
|
||||
if (productData?.patnrId === 21 || productData?.patnrId === "21") {
|
||||
if (productData?.patnrId === 21 || productData?.patnrId === '21') {
|
||||
console.log('[QR-Data] patnrId=21 QR 데이터 확인:', {
|
||||
patnrId: productData?.patnrId,
|
||||
prdtId: productData?.prdtId,
|
||||
@@ -629,7 +648,9 @@ export default function ProductAllSection({
|
||||
if (webOSVersion < '6.0') {
|
||||
return (
|
||||
productData?.pmtSuptYn === 'N' ||
|
||||
(productData?.pmtSuptYn === 'Y' && productData?.grPrdtProcYn === 'N' && panelInfo?.prdtId)
|
||||
(productData?.pmtSuptYn === 'Y' &&
|
||||
productData?.grPrdtProcYn === 'N' &&
|
||||
panelInfo?.prdtId)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -650,7 +671,10 @@ export default function ProductAllSection({
|
||||
|
||||
// 여행/테마 상품 - DetailPanel.backup.jsx와 동일한 로직
|
||||
const isTravelProductVisible = useMemo(() => {
|
||||
return panelInfo?.curationId && (panelInfo?.type === 'theme' || panelInfo?.type === 'hotel');
|
||||
return (
|
||||
panelInfo?.curationId &&
|
||||
(panelInfo?.type === 'theme' || panelInfo?.type === 'hotel')
|
||||
);
|
||||
}, [panelInfo]);
|
||||
|
||||
// useReviews Hook 사용 - 모든 리뷰 관련 로직을 담당
|
||||
@@ -722,13 +746,13 @@ export default function ProductAllSection({
|
||||
if (productData && Object.keys(productData).length > 0) {
|
||||
// sendLogDetail - 제품 상세 버튼 클릭 로깅 (Source와 동일)
|
||||
const detailLogParams = {
|
||||
curationId: productData?.curationId ?? "",
|
||||
curationNm: productData?.curationNm ?? "",
|
||||
inDt: "",
|
||||
linkTpCd: panelInfo?.linkTpCd ?? "",
|
||||
curationId: productData?.curationId ?? '',
|
||||
curationNm: productData?.curationNm ?? '',
|
||||
inDt: '',
|
||||
linkTpCd: panelInfo?.linkTpCd ?? '',
|
||||
logTpNo: LOG_TP_NO.DETAIL.DETAIL_BUTTON_CLICK,
|
||||
patncNm: productData?.patncNm ?? "",
|
||||
patnrId: productData?.patnrId ?? "",
|
||||
patncNm: productData?.patncNm ?? '',
|
||||
patnrId: productData?.patnrId ?? '',
|
||||
};
|
||||
|
||||
dispatch(sendLogDetail(detailLogParams));
|
||||
@@ -745,14 +769,22 @@ export default function ProductAllSection({
|
||||
menuType = Config.LOG_MENU.DETAIL_PAGE_PRODUCT_DETAIL;
|
||||
}
|
||||
|
||||
dispatch(sendLogTotalRecommend({
|
||||
dispatch(
|
||||
sendLogTotalRecommend({
|
||||
menu: menuType,
|
||||
buttonTitle: "DESCRIPTION",
|
||||
buttonTitle: 'DESCRIPTION',
|
||||
contextName: LOG_CONTEXT_NAME.DETAILPAGE,
|
||||
messageId: LOG_MESSAGE_ID.BUTTONCLICK,
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [productData, panelInfo, isBillingProductVisible, isGroupProductVisible, isTravelProductVisible]);
|
||||
}, [
|
||||
productData,
|
||||
panelInfo,
|
||||
isBillingProductVisible,
|
||||
isGroupProductVisible,
|
||||
isTravelProductVisible,
|
||||
]);
|
||||
|
||||
// sendLogGNB 로깅 - Source의 DetailPanel 컴포넌트들과 동일한 패턴
|
||||
useEffect(() => {
|
||||
@@ -774,7 +806,9 @@ export default function ProductAllSection({
|
||||
}
|
||||
|
||||
// YouMayLike에서 상품 선택 시 메뉴 변경 (Source의 isYouMayLikeOpened와 동일 패턴)
|
||||
const menu = (fromPanel?.fromYouMayLike !== undefined && fromPanel?.fromYouMayLike === true)
|
||||
const menu =
|
||||
fromPanel?.fromYouMayLike !== undefined &&
|
||||
fromPanel?.fromYouMayLike === true
|
||||
? `${baseMenu}/${Config.LOG_MENU.DETAIL_PAGE_YOU_MAY_LIKE}`
|
||||
: baseMenu;
|
||||
|
||||
@@ -798,15 +832,17 @@ export default function ProductAllSection({
|
||||
// sendLogGNB 전송 후 플래그 초기화 (1회 사용 후 비활성화)
|
||||
useEffect(() => {
|
||||
if (fromPanel?.fromYouMayLike === true) {
|
||||
dispatch(updatePanel({
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: {
|
||||
...panelInfo,
|
||||
fromPanel: {
|
||||
fromYouMayLike: false // 플래그 초기화
|
||||
}
|
||||
}
|
||||
}));
|
||||
fromYouMayLike: false, // 플래그 초기화
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [fromPanel?.fromYouMayLike, dispatch, panelInfo]);
|
||||
|
||||
@@ -831,24 +867,24 @@ export default function ProductAllSection({
|
||||
lastProductDetailLogKeyRef.current = logKey;
|
||||
|
||||
const params = {
|
||||
befPrice: productData?.priceInfo?.split("|")[0],
|
||||
curationId: productData?.curationId ?? "",
|
||||
curationNm: productData?.curationNm ?? "",
|
||||
befPrice: productData?.priceInfo?.split('|')[0],
|
||||
curationId: productData?.curationId ?? '',
|
||||
curationNm: productData?.curationNm ?? '',
|
||||
entryMenu: entryMenuRef.current,
|
||||
expsOrd: "1",
|
||||
expsOrd: '1',
|
||||
inDt: formatGMTString(new Date()),
|
||||
lastPrice: productData?.priceInfo?.split("|")[1],
|
||||
lgCatCd: productData?.catCd ?? "",
|
||||
lgCatNm: productData?.catNm ?? "",
|
||||
linkTpCd: panelInfo?.linkTpCd ?? "",
|
||||
lastPrice: productData?.priceInfo?.split('|')[1],
|
||||
lgCatCd: productData?.catCd ?? '',
|
||||
lgCatNm: productData?.catNm ?? '',
|
||||
linkTpCd: panelInfo?.linkTpCd ?? '',
|
||||
logTpNo,
|
||||
patncNm: productData?.patncNm ?? "",
|
||||
patnrId: productData?.patnrId ?? "",
|
||||
prdtId: productData?.prdtId ?? "",
|
||||
prdtNm: productData?.prdtNm ?? "",
|
||||
revwGrd: productData?.revwGrd ?? "",
|
||||
rewdAplyFlag: productData.priceInfo?.split("|")[2],
|
||||
tsvFlag: productData?.todaySpclFlag ?? "",
|
||||
patncNm: productData?.patncNm ?? '',
|
||||
patnrId: productData?.patnrId ?? '',
|
||||
prdtId: productData?.prdtId ?? '',
|
||||
prdtNm: productData?.prdtNm ?? '',
|
||||
revwGrd: productData?.revwGrd ?? '',
|
||||
rewdAplyFlag: productData.priceInfo?.split('|')[2],
|
||||
tsvFlag: productData?.todaySpclFlag ?? '',
|
||||
};
|
||||
|
||||
dispatch(sendLogProductDetail(params));
|
||||
@@ -885,13 +921,22 @@ export default function ProductAllSection({
|
||||
|
||||
// 🚀 SingleOption.jsx의 sendLogTotalRecommend 로직 추가
|
||||
if (productData && Object.keys(productData).length > 0) {
|
||||
const { priceInfo, patncNm, prdtId, prdtNm, brndNm, catNm, showId, showNm } = productData;
|
||||
const regularPrice = priceInfo?.split("|")[0];
|
||||
const discountPrice = priceInfo?.split("|")[1];
|
||||
const discountRate = priceInfo?.split("|")[4];
|
||||
const {
|
||||
priceInfo,
|
||||
patncNm,
|
||||
prdtId,
|
||||
prdtNm,
|
||||
brndNm,
|
||||
catNm,
|
||||
showId,
|
||||
showNm,
|
||||
} = productData;
|
||||
const regularPrice = priceInfo?.split('|')[0];
|
||||
const discountPrice = priceInfo?.split('|')[1];
|
||||
const discountRate = priceInfo?.split('|')[4];
|
||||
|
||||
// Option 정보는 현재 선택된 옵션이 없으므로 기본값 사용
|
||||
const prodOptCval = ""; // 실제로는 선택된 옵션 값이 들어가야 함
|
||||
const prodOptCval = ''; // 실제로는 선택된 옵션 값이 들어가야 함
|
||||
|
||||
dispatch(
|
||||
sendLogTotalRecommend({
|
||||
@@ -906,8 +951,8 @@ export default function ProductAllSection({
|
||||
category: catNm,
|
||||
contextName: Config.LOG_CONTEXT_NAME.DETAILPAGE,
|
||||
messageId: Config.LOG_MESSAGE_ID.BUY_NOW,
|
||||
showId: showId ?? "",
|
||||
showNm: showNm ?? "",
|
||||
showId: showId ?? '',
|
||||
showNm: showNm ?? '',
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -947,23 +992,29 @@ export default function ProductAllSection({
|
||||
);
|
||||
|
||||
//닫히도록
|
||||
const handleCloseToast = useCallback((e) => {
|
||||
const handleCloseToast = useCallback(
|
||||
(e) => {
|
||||
// 팝업이 열려있으면 닫지 않음
|
||||
if (popupVisible) {
|
||||
return; // 팝업이 활성이면 무시
|
||||
}
|
||||
dispatch(clearAllToasts());
|
||||
setOpenToast(false);
|
||||
}, [dispatch, popupVisible]);
|
||||
},
|
||||
[dispatch, popupVisible]
|
||||
);
|
||||
|
||||
const handleFocus = useCallback((e)=>{
|
||||
const handleFocus = useCallback(
|
||||
(e) => {
|
||||
// 팝업이 열려있으면 닫지 않음
|
||||
if (popupVisible && cursorVisible) {
|
||||
return; // 팝업이 활성이면 무시
|
||||
}
|
||||
dispatch(clearAllToasts());
|
||||
setOpenToast(false);
|
||||
},[dispatch, popupVisible, cursorVisible])
|
||||
},
|
||||
[dispatch, popupVisible, cursorVisible]
|
||||
);
|
||||
|
||||
// 스크롤 컨테이너의 클릭 이벤트 추적용 로깅
|
||||
const handleScrollContainerClick = useCallback((e) => {
|
||||
@@ -1010,7 +1061,10 @@ export default function ProductAllSection({
|
||||
// TODO: 장바구니 추가 로직 구현
|
||||
}, []);
|
||||
|
||||
const { revwGrd, orderPhnNo } = useMemo(() => extractProductMeta(productInfo), [productInfo]);
|
||||
const { revwGrd, orderPhnNo } = useMemo(
|
||||
() => extractProductMeta(productInfo),
|
||||
[productInfo]
|
||||
);
|
||||
|
||||
const [favoriteOverride, setFavoriteOverride] = useState(null);
|
||||
const favoriteFlag = useMemo(
|
||||
@@ -1019,7 +1073,8 @@ export default function ProductAllSection({
|
||||
);
|
||||
|
||||
const [mobileSendPopupOpen, setMobileSendPopupOpen] = useState(false);
|
||||
const [isShowUserReviewsFocused, setIsShowUserReviewsFocused] = useState(false);
|
||||
const [isShowUserReviewsFocused, setIsShowUserReviewsFocused] =
|
||||
useState(false);
|
||||
// 🆕 [251210] patnrId=21 SEE MORE PRODUCTS 버튼 표시 여부
|
||||
const [hasSeeMoreProducts, setHasSeeMoreProducts] = useState(false);
|
||||
const [seeMoreProductsData, setSeeMoreProductsData] = useState([]);
|
||||
@@ -1048,8 +1103,8 @@ export default function ProductAllSection({
|
||||
const handleUserReviewsClick = useCallback(() => {
|
||||
scrollToSection('scroll-marker-user-reviews');
|
||||
setTimeout(() => {
|
||||
Spotlight.focus("user-reviews-container");
|
||||
},100)
|
||||
Spotlight.focus('user-reviews-container');
|
||||
}, 100);
|
||||
}, [scrollToSection]);
|
||||
|
||||
// ProductVideo V1 전용 - MediaPanel minimize 포함
|
||||
@@ -1125,7 +1180,10 @@ export default function ProductAllSection({
|
||||
|
||||
document.addEventListener('detailpanel-scroll-reset', handleScrollReset);
|
||||
return () => {
|
||||
document.removeEventListener('detailpanel-scroll-reset', handleScrollReset);
|
||||
document.removeEventListener(
|
||||
'detailpanel-scroll-reset',
|
||||
handleScrollReset
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
const productDetailRef = useRef(null); //높이값 변경때문
|
||||
@@ -1150,7 +1208,11 @@ export default function ProductAllSection({
|
||||
}
|
||||
|
||||
// 이미지들 추가
|
||||
if (productData && productData.imgUrls600 && productData.imgUrls600.length > 0) {
|
||||
if (
|
||||
productData &&
|
||||
productData.imgUrls600 &&
|
||||
productData.imgUrls600.length > 0
|
||||
) {
|
||||
productData.imgUrls600.forEach((image, imgIndex) => {
|
||||
items.push({
|
||||
type: 'image',
|
||||
@@ -1170,7 +1232,9 @@ export default function ProductAllSection({
|
||||
// renderItems에 Video가 존재하는지 확인하는 boolean 상태
|
||||
const hasVideo = useMemo(() => {
|
||||
return (
|
||||
renderItems && renderItems.length > 0 && renderItems.some((item) => item.type === 'video')
|
||||
renderItems &&
|
||||
renderItems.length > 0 &&
|
||||
renderItems.some((item) => item.type === 'video')
|
||||
);
|
||||
}, [renderItems]);
|
||||
|
||||
@@ -1182,10 +1246,11 @@ export default function ProductAllSection({
|
||||
const handleShopByMobileOpen = useCallback(() => {
|
||||
// sendLogShopByMobile - Source와 동일한 로깅 추가
|
||||
if (productData && Object.keys(productData).length > 0) {
|
||||
const { priceInfo, patncNm, patnrId, prdtId, prdtNm, brndNm, catNm } = productData;
|
||||
const regularPrice = priceInfo?.split("|")[0];
|
||||
const discountPrice = priceInfo?.split("|")[1];
|
||||
const discountRate = priceInfo?.split("|")[4];
|
||||
const { priceInfo, patncNm, patnrId, prdtId, prdtNm, brndNm, catNm } =
|
||||
productData;
|
||||
const regularPrice = priceInfo?.split('|')[0];
|
||||
const discountPrice = priceInfo?.split('|')[1];
|
||||
const discountRate = priceInfo?.split('|')[4];
|
||||
|
||||
const logParams = {
|
||||
prdtId,
|
||||
@@ -1233,7 +1298,8 @@ export default function ProductAllSection({
|
||||
}, [promotions, isBillingProductVisible, shopByMobileId, stackOrder]);
|
||||
|
||||
const firstCouponId = useMemo(
|
||||
() => (promotions && promotions.length > 0 ? 'detail-coupon-button-0' : null),
|
||||
() =>
|
||||
promotions && promotions.length > 0 ? 'detail-coupon-button-0' : null,
|
||||
[promotions]
|
||||
);
|
||||
|
||||
@@ -1242,7 +1308,8 @@ export default function ProductAllSection({
|
||||
[]
|
||||
);
|
||||
|
||||
const handleThemeItemButtonClick = useCallback((e) => {
|
||||
const handleThemeItemButtonClick = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
dispatch(
|
||||
showToast({
|
||||
@@ -1269,24 +1336,29 @@ export default function ProductAllSection({
|
||||
// },
|
||||
})
|
||||
);
|
||||
}, [dispatch, themeProducts, setSelectedIndex, panelInfo]);
|
||||
},
|
||||
[dispatch, themeProducts, setSelectedIndex, panelInfo]
|
||||
);
|
||||
|
||||
const handleProductDetailsClick = useCallback(() => {
|
||||
// 제품 설명 데이터가 없을 때 클릭 방지
|
||||
if (!hasProductDescription) return;
|
||||
|
||||
dispatch(minimizeModalMedia());
|
||||
scrollToSection('scroll-marker-product-details');
|
||||
|
||||
// Source의 handleIndicatorOptions와 동일한 로깅 기능 추가
|
||||
handleIndicatorOptions();
|
||||
setTimeout(() => {
|
||||
Spotlight.focus("product-description-content")
|
||||
Spotlight.focus('product-description-content');
|
||||
}, 100);
|
||||
}, [scrollToSection, dispatch, handleIndicatorOptions]);
|
||||
}, [scrollToSection, dispatch, handleIndicatorOptions, hasProductDescription]);
|
||||
|
||||
const handleYouMayAlsoLikeClick = useCallback(() => {
|
||||
dispatch(minimizeModalMedia());
|
||||
scrollToSection('scroll-marker-you-may-also-like');
|
||||
setTimeout(() => {
|
||||
Spotlight.focus("detail_youMayAlsoLike_area")
|
||||
Spotlight.focus('detail_youMayAlsoLike_area');
|
||||
}, 100);
|
||||
}, [scrollToSection, dispatch]);
|
||||
|
||||
@@ -1295,16 +1367,21 @@ export default function ProductAllSection({
|
||||
// 버튼 클릭 시 스크롤만 처리 (데이터는 useEffect에서 처리)
|
||||
scrollToSection('scroll-marker-see-more-products');
|
||||
setTimeout(() => {
|
||||
Spotlight.focus("detail_seeMoreProducts_area");
|
||||
Spotlight.focus('detail_seeMoreProducts_area');
|
||||
}, 100);
|
||||
}, [scrollToSection]);
|
||||
|
||||
// 🆕 [251210] patnrId=21인 경우 그룹 상품 데이터 미리 처리
|
||||
useEffect(() => {
|
||||
if (panelInfo?.patnrId === 21 || panelInfo?.patnrId === "21") {
|
||||
console.log('[SEE MORE PRODUCTS] patnrId=21 detected - processing group data on panel mount');
|
||||
if (panelInfo?.patnrId === 21 || panelInfo?.patnrId === '21') {
|
||||
console.log(
|
||||
'[SEE MORE PRODUCTS] patnrId=21 detected - processing group data on panel mount'
|
||||
);
|
||||
console.log('[SEE MORE PRODUCTS] panelInfo:', panelInfo);
|
||||
console.log('[SEE MORE PRODUCTS] brandShopByShowCategoryGroups:', brandShopByShowCategoryGroups);
|
||||
console.log(
|
||||
'[SEE MORE PRODUCTS] brandShopByShowCategoryGroups:',
|
||||
brandShopByShowCategoryGroups
|
||||
);
|
||||
|
||||
const patnrIdString = String(panelInfo.patnrId);
|
||||
const currentPrdtId = panelInfo.prdtId;
|
||||
@@ -1323,16 +1400,25 @@ export default function ProductAllSection({
|
||||
// 모든 contsId에서 현재 상품이 속한 그룹 찾기
|
||||
for (const contsId in categoryGroups) {
|
||||
const contsInfo = categoryGroups[contsId];
|
||||
console.log(`[SEE MORE PRODUCTS] Checking contsId: ${contsId}, contsNm: ${contsInfo.contsNm}`);
|
||||
console.log(
|
||||
`[SEE MORE PRODUCTS] Checking contsId: ${contsId}, contsNm: ${contsInfo.contsNm}`
|
||||
);
|
||||
|
||||
if (contsInfo.brandShopByShowClctInfos) {
|
||||
for (const group of contsInfo.brandShopByShowClctInfos) {
|
||||
console.log(`[SEE MORE PRODUCTS] Checking group: ${group.clctNm} (${group.clctId})`);
|
||||
console.log(
|
||||
`[SEE MORE PRODUCTS] Checking group: ${group.clctNm} (${group.clctId})`
|
||||
);
|
||||
|
||||
if (group.brandProductInfos) {
|
||||
const foundProduct = group.brandProductInfos.find(p => p.prdtId === currentPrdtId);
|
||||
const foundProduct = group.brandProductInfos.find(
|
||||
(p) => p.prdtId === currentPrdtId
|
||||
);
|
||||
if (foundProduct) {
|
||||
console.log('[SEE MORE PRODUCTS] 🎯 Found current product:', foundProduct);
|
||||
console.log(
|
||||
'[SEE MORE PRODUCTS] 🎯 Found current product:',
|
||||
foundProduct
|
||||
);
|
||||
foundGroup = group;
|
||||
foundConts = contsInfo;
|
||||
break;
|
||||
@@ -1351,22 +1437,28 @@ export default function ProductAllSection({
|
||||
console.log(' - Group ID:', foundGroup.clctId);
|
||||
|
||||
// 현재 상품을 제외한 다른 상품들 확인
|
||||
const otherProducts = foundGroup.brandProductInfos.filter(p => p.prdtId !== currentPrdtId);
|
||||
const otherProducts = foundGroup.brandProductInfos.filter(
|
||||
(p) => p.prdtId !== currentPrdtId
|
||||
);
|
||||
console.log(' - Other products count:', otherProducts.length);
|
||||
|
||||
if (otherProducts.length > 0) {
|
||||
// 다른 상품이 있을 때만 버튼 표시
|
||||
shouldShowButton = true;
|
||||
|
||||
console.log('[SEE MORE PRODUCTS] 📦 Showing button - group has other products:');
|
||||
console.log(
|
||||
'[SEE MORE PRODUCTS] 📦 Showing button - group has other products:'
|
||||
);
|
||||
otherProducts.forEach((product, index) => {
|
||||
console.log(` ${index + 1}. ${product.prdtNm} (${product.prdtId})`);
|
||||
console.log(
|
||||
` ${index + 1}. ${product.prdtNm} (${product.prdtId})`
|
||||
);
|
||||
console.log(` - Price: ${product.priceInfo}`);
|
||||
console.log(` - Brand: ${product.brndNm || 'N/A'}`);
|
||||
});
|
||||
|
||||
// 🆕 SeeMoreProducts 컴포넌트를 위한 데이터 변환
|
||||
const formattedProducts = otherProducts.map(product => ({
|
||||
const formattedProducts = otherProducts.map((product) => ({
|
||||
prdtId: product.prdtId,
|
||||
prdtNm: product.prdtNm,
|
||||
priceInfo: product.priceInfo,
|
||||
@@ -1382,14 +1474,20 @@ export default function ProductAllSection({
|
||||
// YouMayAlsoLike 데이터 형식으로 맞추기
|
||||
setSeeMoreProductsData(formattedProducts);
|
||||
} else {
|
||||
console.log('[SEE MORE PRODUCTS] ❌ No other products in group - hiding button');
|
||||
console.log(
|
||||
'[SEE MORE PRODUCTS] ❌ No other products in group - hiding button'
|
||||
);
|
||||
setSeeMoreProductsData([]);
|
||||
}
|
||||
} else {
|
||||
console.log('[SEE MORE PRODUCTS] ❌ No group found for current product - hiding button');
|
||||
console.log(
|
||||
'[SEE MORE PRODUCTS] ❌ No group found for current product - hiding button'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log('[SEE MORE PRODUCTS] ❌ No category groups found for patnrId 21 - hiding button');
|
||||
console.log(
|
||||
'[SEE MORE PRODUCTS] ❌ No category groups found for patnrId 21 - hiding button'
|
||||
);
|
||||
}
|
||||
|
||||
// 버튼 표시 여부 상태 설정
|
||||
@@ -1417,9 +1515,7 @@ export default function ProductAllSection({
|
||||
const mediaMinimizedRef = useRef(false);
|
||||
const getTotalContentHeight = useCallback(() => {
|
||||
const measuredHeight =
|
||||
contentHeightRef.current ||
|
||||
scrollContainerRef.current?.scrollHeight ||
|
||||
0;
|
||||
contentHeightRef.current || scrollContainerRef.current?.scrollHeight || 0;
|
||||
return measuredHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0);
|
||||
}, []);
|
||||
|
||||
@@ -1452,7 +1548,8 @@ export default function ProductAllSection({
|
||||
const prevScrollTop = prevScrollTopRef.current;
|
||||
|
||||
scrollPositionRef.current = currentScrollTop;
|
||||
contentHeightRef.current = e?.scrollHeight || contentHeightRef.current || 0;
|
||||
contentHeightRef.current =
|
||||
e?.scrollHeight || contentHeightRef.current || 0;
|
||||
|
||||
// 기존 bottom 체크 로직 유지
|
||||
const totalHeight = getTotalContentHeight();
|
||||
@@ -1510,7 +1607,13 @@ export default function ProductAllSection({
|
||||
}
|
||||
// v2: onScrollStop에서 처리 (기존 로직 유지)
|
||||
},
|
||||
[isBottom, productVideoVersion, isVideoPlaying, dispatch, getTotalContentHeight]
|
||||
[
|
||||
isBottom,
|
||||
productVideoVersion,
|
||||
isVideoPlaying,
|
||||
dispatch,
|
||||
getTotalContentHeight,
|
||||
]
|
||||
);
|
||||
|
||||
// 스크롤 멈추었을 때만 호출 (성능 최적화)
|
||||
@@ -1518,7 +1621,8 @@ export default function ProductAllSection({
|
||||
(e) => {
|
||||
const currentScrollTop = e.scrollTop;
|
||||
scrollPositionRef.current = currentScrollTop;
|
||||
contentHeightRef.current = e?.scrollHeight || contentHeightRef.current || 0;
|
||||
contentHeightRef.current =
|
||||
e?.scrollHeight || contentHeightRef.current || 0;
|
||||
const totalHeight = getTotalContentHeight();
|
||||
if (totalHeight) {
|
||||
const isAtBottom = currentScrollTop + 944 >= totalHeight;
|
||||
@@ -1583,7 +1687,9 @@ export default function ProductAllSection({
|
||||
// 초기 로딩 후 Skeleton 숨기기
|
||||
useEffect(() => {
|
||||
const hasDataReady =
|
||||
productType === 'theme' ? hasThemeContents && !!productData : !!productData;
|
||||
productType === 'theme'
|
||||
? hasThemeContents && !!productData
|
||||
: !!productData;
|
||||
|
||||
if (productType && hasDataReady && isInitialLoading) {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -1683,8 +1789,8 @@ export default function ProductAllSection({
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 초기 로딩 중에는 Skeleton 표시
|
||||
if (isInitialLoading) {
|
||||
// 초기 로딩 중에는 Skeleton 표시 (비활성화됨)
|
||||
if (false && isInitialLoading) {
|
||||
return (
|
||||
<div className={css.detailArea}>
|
||||
<DetailPanelSkeleton />
|
||||
@@ -1693,7 +1799,11 @@ export default function ProductAllSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<HorizontalContainer className={css.detailArea} onClick={handleCloseToast} onFocus={handleFocus}>
|
||||
<HorizontalContainer
|
||||
className={css.detailArea}
|
||||
onClick={handleCloseToast}
|
||||
onFocus={handleFocus}
|
||||
>
|
||||
{/* Left Margin Section - 60px */}
|
||||
<div className={css.leftMarginSection}></div>
|
||||
|
||||
@@ -1727,7 +1837,10 @@ export default function ProductAllSection({
|
||||
{isShowQRCode ? (
|
||||
<>
|
||||
{/* <QRCode productInfo={productData} productType={productType} kind={'detail'} /> */}
|
||||
<QRCode productInfo={productData} productType={productType} />
|
||||
<QRCode
|
||||
productInfo={productData}
|
||||
productType={productType}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className={css.qrRollingWrap}>
|
||||
@@ -1763,7 +1876,9 @@ export default function ProductAllSection({
|
||||
return (
|
||||
<div className={css.couponContainer} key={idx}>
|
||||
<div className={css.couponTitleText}>
|
||||
<div className={css.firstTitle}>SPECIAL PROMOTION</div>
|
||||
<div className={css.firstTitle}>
|
||||
SPECIAL PROMOTION
|
||||
</div>
|
||||
<div className={css.secondTitle}>
|
||||
Coupon only applicable to this product!
|
||||
</div>
|
||||
@@ -1815,7 +1930,9 @@ export default function ProductAllSection({
|
||||
className={css.shopByMobileButton}
|
||||
onClick={handleShopByMobileOpen}
|
||||
>
|
||||
<div className={css.shopByMobileText}>{$L('SHOP BY MOBILE')}</div>
|
||||
<div className={css.shopByMobileText}>
|
||||
{$L('SHOP BY MOBILE')}
|
||||
</div>
|
||||
</TButton>
|
||||
{panelInfo && (
|
||||
<div className={css.favoriteBtnWrapper}>
|
||||
@@ -1835,7 +1952,9 @@ export default function ProductAllSection({
|
||||
<div className={css.callToOrderSection}>
|
||||
{orderPhnNo && (
|
||||
<>
|
||||
<div className={css.callToOrderText}>{$L('Call to Order')}</div>
|
||||
<div className={css.callToOrderText}>
|
||||
{$L('Call to Order')}
|
||||
</div>
|
||||
<div className={css.phoneSection}>
|
||||
<div className={css.phoneIconContainer}>
|
||||
<div className={css.phoneIcon} />
|
||||
@@ -1850,6 +1969,7 @@ export default function ProductAllSection({
|
||||
className={css.actionButtonsWrapper}
|
||||
spotlightId="product-info-button-container"
|
||||
>
|
||||
{hasProductDescription && (
|
||||
<TButton
|
||||
className={classNames(
|
||||
css.productDetailsButton,
|
||||
@@ -1860,6 +1980,7 @@ export default function ProductAllSection({
|
||||
>
|
||||
{$L('PRODUCT DETAILS')}
|
||||
</TButton>
|
||||
)}
|
||||
{isReviewDataComplete && (
|
||||
<>
|
||||
{/*
|
||||
@@ -1881,7 +2002,8 @@ export default function ProductAllSection({
|
||||
</TButton>
|
||||
</>
|
||||
)}
|
||||
{(panelInfo?.patnrId !== 21 || panelInfo?.patnrId !== "21") && hasYouMayAlsoLike && (
|
||||
{(panelInfo?.patnrId !== 21 || panelInfo?.patnrId !== '21') &&
|
||||
hasYouMayAlsoLike && (
|
||||
<TButton
|
||||
className={classNames(
|
||||
css.youMayLikeButton,
|
||||
@@ -1893,7 +2015,8 @@ export default function ProductAllSection({
|
||||
</TButton>
|
||||
)}
|
||||
{/* 🆕 [251210] patnrId=21인 경우 SEE MORE PRODUCTS 버튼 */}
|
||||
{(panelInfo?.patnrId === 21 || panelInfo?.patnrId === "21") && hasSeeMoreProducts && (
|
||||
{(panelInfo?.patnrId === 21 || panelInfo?.patnrId === '21') &&
|
||||
hasSeeMoreProducts && (
|
||||
<TButton
|
||||
className={classNames(
|
||||
css.seeMoreProductButton,
|
||||
@@ -1935,7 +2058,10 @@ export default function ProductAllSection({
|
||||
}}
|
||||
>
|
||||
<div>{$L('THEME ITEM')}</div>
|
||||
<img src={arrowDownIcon} className={css.themeButtonIcon} />
|
||||
<img
|
||||
src={arrowDownIcon}
|
||||
className={css.themeButtonIcon}
|
||||
/>
|
||||
</TButton>
|
||||
</Container>
|
||||
)}
|
||||
@@ -1981,7 +2107,9 @@ export default function ProductAllSection({
|
||||
onBlur={handleButtonBlur}
|
||||
>
|
||||
{/* 비디오가 있으면 먼저 렌더링 (productVideoVersion이 3이 아닐 때만) */}
|
||||
{hasVideo && renderItems[0].type === 'video' && productVideoVersion !== 3 && (
|
||||
{hasVideo &&
|
||||
renderItems[0].type === 'video' &&
|
||||
productVideoVersion !== 3 && (
|
||||
<>
|
||||
{productVideoVersion === 1 ? (
|
||||
<ProductVideo
|
||||
@@ -2007,7 +2135,10 @@ export default function ProductAllSection({
|
||||
onScrollToImages={handleScrollToImagesV2}
|
||||
/>
|
||||
)}
|
||||
<div id="scroll-marker-after-video" className={css.scrollMarker}></div>
|
||||
<div
|
||||
id="scroll-marker-after-video"
|
||||
className={css.scrollMarker}
|
||||
></div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -2028,7 +2159,10 @@ export default function ProductAllSection({
|
||||
))
|
||||
: !hasVideo && <ProductDetail productInfo={productData} />}
|
||||
</div>
|
||||
<div id="scroll-marker-product-details" className={css.scrollMarker}></div>
|
||||
<div
|
||||
id="scroll-marker-product-details"
|
||||
className={css.scrollMarker}
|
||||
></div>
|
||||
<div
|
||||
id="product-description-section"
|
||||
ref={descriptionRef}
|
||||
@@ -2038,7 +2172,10 @@ export default function ProductAllSection({
|
||||
<ProductDescription productInfo={productData} />
|
||||
</div>
|
||||
{/* 리뷰 데이터가 완전할 때만 UserReviews 섹션 표시 */}
|
||||
<div id="scroll-marker-user-reviews" className={css.scrollMarker}></div>
|
||||
<div
|
||||
id="scroll-marker-user-reviews"
|
||||
className={css.scrollMarker}
|
||||
></div>
|
||||
{isReviewDataComplete && (
|
||||
<div
|
||||
id="user-reviews-section"
|
||||
@@ -2066,8 +2203,11 @@ export default function ProductAllSection({
|
||||
)}
|
||||
</div>
|
||||
<div ref={youMayAlsoLikelRef}>
|
||||
<div id="scroll-marker-you-may-also-like" className={css.scrollMarker}></div>
|
||||
{(panelInfo?.patnrId !== 21 || panelInfo?.patnrId !== "21") && hasYouMayAlsoLike && (
|
||||
<div
|
||||
id="scroll-marker-you-may-also-like"
|
||||
className={css.scrollMarker}
|
||||
></div>
|
||||
{panelInfo?.patnrId !== '21' && hasYouMayAlsoLike && (
|
||||
<div id="you-may-also-like-section">
|
||||
{/* {(() => {
|
||||
console.log('[YouMayLike] YouMayAlsoLike 컴포넌트 렌더링:', {
|
||||
@@ -2090,10 +2230,16 @@ export default function ProductAllSection({
|
||||
</div>
|
||||
|
||||
{/* 🆕 [251210] patnrId=21인 경우 SEE MORE PRODUCTS 섹션 */}
|
||||
{(panelInfo?.patnrId === 21 || panelInfo?.patnrId === "21") && hasSeeMoreProducts && (
|
||||
{panelInfo?.patnrId === '21' && hasSeeMoreProducts && (
|
||||
<div ref={seeMoreProductsRef}>
|
||||
<div id="scroll-marker-see-more-products" className={css.scrollMarker}></div>
|
||||
<div id="see-more-products-section" data-spotlight-id="see-more-products-area">
|
||||
<div
|
||||
id="scroll-marker-see-more-products"
|
||||
className={css.scrollMarker}
|
||||
></div>
|
||||
<div
|
||||
id="see-more-products-section"
|
||||
data-spotlight-id="see-more-products-area"
|
||||
>
|
||||
<SeeMoreProducts
|
||||
groupProducts={seeMoreProductsData}
|
||||
sponserImage={sponserImage}
|
||||
@@ -2156,7 +2302,9 @@ export default function ProductAllSection({
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
<div className={css.couponRemain}>{`1/${selectedCoupon?.length}`}</div>
|
||||
<div
|
||||
className={css.couponRemain}
|
||||
>{`1/${selectedCoupon?.length}`}</div>
|
||||
</TPopUp>
|
||||
)}
|
||||
</HorizontalContainer>
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
import React, { useCallback } from "react";
|
||||
import css from "./ProductDescription.module.less";
|
||||
import { $L, removeSpecificTags } from "../../../../utils/helperMethods";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import React, {
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import {
|
||||
setHidePopup,
|
||||
setShowPopup,
|
||||
} from '../../../../actions/commonActions';
|
||||
import TButtonScroller
|
||||
from '../../../../components/TButtonScroller/TButtonScroller';
|
||||
import TNewPopUp from '../../../../components/TPopUp/TNewPopUp';
|
||||
import * as Config from '../../../../utils/Config';
|
||||
import {
|
||||
$L,
|
||||
removeSpecificTags,
|
||||
} from '../../../../utils/helperMethods';
|
||||
import css from './ProductDescription.module.less';
|
||||
|
||||
// TVerticalPagenator 제거됨 - TScrollerNew와 충돌 문제로 인해
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
@@ -19,11 +42,22 @@ const Container = SpotlightContainerDecorator(
|
||||
);
|
||||
|
||||
export default function ProductDescription({ productInfo }) {
|
||||
const { popupVisible, activePopup } = useSelector(
|
||||
(state) => state.common.popup
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
const productDescription = useCallback(() => {
|
||||
const sanitizedString = removeSpecificTags(productInfo?.prdtDesc);
|
||||
return { __html: sanitizedString };
|
||||
}, [productInfo?.prdtDesc]);
|
||||
|
||||
const productDescriptionText = useMemo(() => {
|
||||
return removeSpecificTags(productInfo?.prdtDesc);
|
||||
}, [productInfo?.prdtDesc]);
|
||||
|
||||
// 왼쪽 화살표 키 이벤트 처리
|
||||
const handleKeyDown = useCallback((ev) => {
|
||||
if (ev.keyCode === 37) { // 왼쪽 화살표 키
|
||||
@@ -34,6 +68,20 @@ export default function ProductDescription({ productInfo }) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const descriptionClick = useCallback(() => {
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.scrollPopup));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const _onClose = useCallback(()=>{
|
||||
dispatch(setHidePopup());
|
||||
// Restore focus to the description content after popup closes
|
||||
setTimeout(() => {
|
||||
Spotlight.focus('product-description-content');
|
||||
}, 100);
|
||||
},[dispatch])
|
||||
|
||||
// ProductDescription: Container 직접 사용 패턴
|
||||
// prdtDesc가 없으면 렌더링하지 않음
|
||||
if (!productInfo?.prdtDesc) {
|
||||
@@ -41,6 +89,7 @@ export default function ProductDescription({ productInfo }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container
|
||||
className={css.descriptionContainer}
|
||||
spotlightId="product-description-container"
|
||||
@@ -61,7 +110,8 @@ export default function ProductDescription({ productInfo }) {
|
||||
<SpottableComponent
|
||||
className={css.descriptionWrapper}
|
||||
spotlightId="product-description-content"
|
||||
onClick={() => console.log("[ProductDescription] Content clicked")}
|
||||
// onClick={() => console.log("[ProductDescription] Content clicked")}
|
||||
onClick={descriptionClick}
|
||||
onFocus={() => console.log("[ProductDescription] Content focused")}
|
||||
onBlur={() => console.log("[ProductDescription] Content blurred")}
|
||||
onKeyDown={handleKeyDown}
|
||||
@@ -72,5 +122,28 @@ export default function ProductDescription({ productInfo }) {
|
||||
/>
|
||||
</SpottableComponent>
|
||||
</Container>
|
||||
{activePopup === Config.ACTIVE_POPUP.scrollPopup && (
|
||||
<TNewPopUp
|
||||
kind="scrollPopup"
|
||||
open={popupVisible}
|
||||
hasText
|
||||
title={$L("DESCRIPTION")}
|
||||
onClick={_onClose}
|
||||
hasButton
|
||||
button1Text={$L("OK")}
|
||||
>
|
||||
<TButtonScroller
|
||||
boxHeight={460}
|
||||
width={844}
|
||||
kind={"figmaTermsPopup"}
|
||||
>
|
||||
<div
|
||||
className={css.scrollContainer}
|
||||
dangerouslySetInnerHTML={{ __html: productDescriptionText }}
|
||||
/>
|
||||
</TButtonScroller>
|
||||
</TNewPopUp>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,3 +51,8 @@
|
||||
|
||||
}
|
||||
}
|
||||
.scrollContainer {
|
||||
padding: 31px;
|
||||
font-size: 26px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
position: relative;
|
||||
width: 1114px; // ProductDetail과 동일한 고정 크기
|
||||
max-width: 1114px;
|
||||
height: 740px; // ProductDetail과 동일한 고정 높이
|
||||
margin-bottom: 30px; // ProductDetail과 동일한 간격
|
||||
height: 632px !important; // ProductDetail과 동일한 고정 높이
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 1);
|
||||
border-radius: 12px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
box-sizing: border-box;
|
||||
padding: 6px; // 포커스 테두리를 위한 공간
|
||||
overflow: hidden;
|
||||
@@ -80,7 +79,7 @@
|
||||
z-index: 23; // MediaPanel(z-index: 22)보다 위에 표시되어야 비디오 재생 중에도 포커스 테두리가 보임
|
||||
border: 6px solid @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
||||
border-radius: 12px;
|
||||
border-radius: 12px 12px 0px 0px;
|
||||
content: "";
|
||||
}
|
||||
|
||||
@@ -217,15 +216,13 @@
|
||||
}
|
||||
|
||||
.notice {
|
||||
width: 100%;
|
||||
width: calc(100% - 10px);
|
||||
height: 54px;
|
||||
background: #000000;
|
||||
.flex(@justifyCenter:flex-start);
|
||||
padding: 6px 18px 18px 18px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
border-radius: 0 0 12px 12px;
|
||||
|
||||
margin-bottom: 30px; // ProductDetail과 동일한 간격
|
||||
.marquee {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -311,6 +311,7 @@ export default function ProductVideo({
|
||||
if (!canPlayVideo) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SpottableComponent
|
||||
className={css.videoContainer}
|
||||
onClick={handleVideoClick}
|
||||
@@ -330,12 +331,13 @@ export default function ProductVideo({
|
||||
<img src={playImg} alt="재생" />
|
||||
</div>
|
||||
</div>
|
||||
</SpottableComponent>
|
||||
<div className={css.notice}>
|
||||
<Marquee className={css.marquee} marqueeOn="render">
|
||||
<img src={ic_warning} alt={disclaimer} />
|
||||
<span>{disclaimer}</span>
|
||||
</Marquee>
|
||||
</div>
|
||||
</SpottableComponent>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,41 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
|
||||
import { Job } from '@enact/core/util';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import { clearThemeDetail } from '../../../../actions/homeActions';
|
||||
import { finishModalMediaForce } from '../../../../actions/mediaActions';
|
||||
import { popPanel, pushPanel, updatePanel } from '../../../../actions/panelActions';
|
||||
import {
|
||||
popPanel,
|
||||
pushPanel,
|
||||
updatePanel,
|
||||
} from '../../../../actions/panelActions';
|
||||
import { finishVideoPreview } from '../../../../actions/playActions';
|
||||
import THeader from '../../../../components/THeader/THeader';
|
||||
import TItemCardNew from '../../../../components/TItemCard/TItemCard.new';
|
||||
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 useScrollTo from '../../../../hooks/useScrollTo';
|
||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../../utils/Config';
|
||||
import {
|
||||
LOG_CONTEXT_NAME,
|
||||
LOG_MESSAGE_ID,
|
||||
panel_names,
|
||||
} from '../../../../utils/Config';
|
||||
import { $L } from '../../../../utils/helperMethods';
|
||||
import css from './YouMayAlsoLike.module.less';
|
||||
|
||||
@@ -158,7 +178,7 @@ export default function YouMayAlsoLike({
|
||||
// DetailPanel을 언마운트하지 않고 상품 정보만 업데이트
|
||||
// 이렇게 하면 백그라운드 비디오 제어 상태가 유지됨
|
||||
dispatch(
|
||||
updatePanel({
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: {
|
||||
showNm: panelInfo?.showNm,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import "../../../../../style/CommonStyle.module.less";
|
||||
@import "../../../../../style/utils.module.less";
|
||||
@import '../../../../../style/CommonStyle.module.less';
|
||||
@import '../../../../../style/utils.module.less';
|
||||
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
@@ -30,7 +30,7 @@
|
||||
}
|
||||
.name {
|
||||
font-weight: bold;
|
||||
font-size: 36px;
|
||||
font-size: 30px;
|
||||
color: @COLOR_WHITE;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import "../../../../../style/CommonStyle.module.less";
|
||||
@import "../../../../../style/utils.module.less";
|
||||
@import '../../../../../style/CommonStyle.module.less';
|
||||
@import '../../../../../style/utils.module.less';
|
||||
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
@@ -30,7 +30,7 @@
|
||||
}
|
||||
.name {
|
||||
font-weight: bold;
|
||||
font-size: 36px;
|
||||
font-size: 30px;
|
||||
color: @COLOR_WHITE;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -78,35 +78,35 @@ export default function ThemeItemCard({
|
||||
const { originalPrice, discountedPrice, discountRate } =
|
||||
usePriceInfo(priceInfo) || {};
|
||||
|
||||
const mockEnergyLabel = [
|
||||
{
|
||||
"enrgLblExpsOrd": "0",
|
||||
"enrgLblTpCd": "EL_TYPE_05",
|
||||
"enrgLblCd": "MNLC",
|
||||
"enrgClasCd": "A",
|
||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_a.png",
|
||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241113229264&ORIGINAL_NAME_b1_a1=27U511SA EU (E).pdf&FILE_NAME=27U511SA EU (E)[20241113011401634].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241125231113&ORIGINAL_NAME_b1_a1=27U511SA-W.pdf&FILE_NAME=27U511SA-W[20241125231113].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||
},
|
||||
{
|
||||
"enrgLblExpsOrd": "0",
|
||||
"enrgLblTpCd": "EL_TYPE_05",
|
||||
"enrgLblCd": "MNLC",
|
||||
"enrgClasCd": "D",
|
||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||
},
|
||||
{
|
||||
"enrgLblExpsOrd": "0",
|
||||
"enrgLblTpCd": "EL_TYPE_05",
|
||||
"enrgLblCd": "MNLC",
|
||||
"enrgClasCd": "D",
|
||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||
}
|
||||
];
|
||||
// const mockEnergyLabel = [
|
||||
// {
|
||||
// "enrgLblExpsOrd": "0",
|
||||
// "enrgLblTpCd": "EL_TYPE_05",
|
||||
// "enrgLblCd": "MNLC",
|
||||
// "enrgClasCd": "A",
|
||||
// "enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_a.png",
|
||||
// "enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241113229264&ORIGINAL_NAME_b1_a1=27U511SA EU (E).pdf&FILE_NAME=27U511SA EU (E)[20241113011401634].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||
// "enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241125231113&ORIGINAL_NAME_b1_a1=27U511SA-W.pdf&FILE_NAME=27U511SA-W[20241125231113].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||
// },
|
||||
// {
|
||||
// "enrgLblExpsOrd": "0",
|
||||
// "enrgLblTpCd": "EL_TYPE_05",
|
||||
// "enrgLblCd": "MNLC",
|
||||
// "enrgClasCd": "D",
|
||||
// "enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
||||
// "enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||
// "enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||
// },
|
||||
// {
|
||||
// "enrgLblExpsOrd": "0",
|
||||
// "enrgLblTpCd": "EL_TYPE_05",
|
||||
// "enrgLblCd": "MNLC",
|
||||
// "enrgClasCd": "D",
|
||||
// "enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
||||
// "enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||
// "enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||
// }
|
||||
// ];
|
||||
|
||||
const setEnactFitZIndex = (zIndexValue) => {
|
||||
const target = document.getElementById("floatLayer");
|
||||
@@ -211,7 +211,7 @@ export default function ThemeItemCard({
|
||||
))}
|
||||
</div>
|
||||
)} */}
|
||||
{mockEnergyLabel && mockEnergyLabel.length > 0 && (
|
||||
{/* {mockEnergyLabel && mockEnergyLabel.length > 0 && (
|
||||
<div className={css.energyLabels}>
|
||||
{mockEnergyLabel.map((label, labelIndex) => (
|
||||
<SpottableTemp
|
||||
@@ -228,7 +228,7 @@ export default function ThemeItemCard({
|
||||
</SpottableTemp>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</SpottableDiv>
|
||||
{(() => {
|
||||
|
||||
@@ -93,9 +93,6 @@ export default function THeaderCustom({
|
||||
role="button"
|
||||
/>
|
||||
)}
|
||||
{type === "theme" && themeTitle && (
|
||||
<span className={css.themeTitle} dangerouslySetInnerHTML={{ __html: themeTitle }} />
|
||||
)}
|
||||
{kind ? (
|
||||
""
|
||||
) : (
|
||||
@@ -107,6 +104,9 @@ export default function THeaderCustom({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{type === "theme" && themeTitle && (
|
||||
<span className={css.themeTitle} dangerouslySetInnerHTML={{ __html: `[${themeTitle}]` }} />
|
||||
)}
|
||||
<Marquee
|
||||
marqueeOn="render"
|
||||
className={css.title}
|
||||
|
||||
@@ -61,4 +61,5 @@
|
||||
color: #eaeaea;
|
||||
width: max-content;
|
||||
margin-right: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
@@ -1,14 +1,26 @@
|
||||
import React, { useCallback, useEffect, useRef, useState, useMemo, forwardRef } from 'react';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { off, on } from '@enact/core/dispatcher';
|
||||
import {
|
||||
off,
|
||||
on,
|
||||
} from '@enact/core/dispatcher';
|
||||
import { Job } from '@enact/core/util';
|
||||
import Scroller from '@enact/sandstone/Scroller';
|
||||
import Spotlight from '@enact/spotlight';
|
||||
|
||||
import AutoScrollAreaDetail, { POSITION } from '../AutoScrollAreaDetail/AutoScrollAreaDetail';
|
||||
import AutoScrollAreaDetail, {
|
||||
POSITION,
|
||||
} from '../AutoScrollAreaDetail/AutoScrollAreaDetail';
|
||||
import css from './TScrollerDetail.module.less';
|
||||
|
||||
/**
|
||||
@@ -206,7 +218,8 @@ const TScrollerDetail = forwardRef(
|
||||
onScrollStop={_onScrollStop}
|
||||
onScroll={_onScroll}
|
||||
scrollMode={scrollMode || 'translate'}
|
||||
focusableScrollbar={focusableScrollbar}
|
||||
// focusableScrollbar={focusableScrollbar}
|
||||
focusableScrollbar={false}
|
||||
className={classNames(isMounted && css.tScroller, noScrollByWheel && css.preventScroll)}
|
||||
direction={direction}
|
||||
horizontalScrollbar={horizontalScrollbar}
|
||||
|
||||
@@ -5,8 +5,8 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { Job } from "@enact/core/util";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
|
||||
import { updatePanel } from "../../../../../actions/panelActions";
|
||||
import { startVideoPlayer } from "../../../../../actions/playActions";
|
||||
import { pushPanel, updatePanel } from "../../../../../actions/panelActions";
|
||||
// import { startVideoPlayer } from "../../../../../actions/playActions";
|
||||
import TItemCard, {
|
||||
removeDotAndColon,
|
||||
} from "../../../../../components/TItemCard/TItemCard";
|
||||
@@ -113,27 +113,36 @@ export default function RecommendedShowsProductList({
|
||||
);
|
||||
}
|
||||
|
||||
let y =
|
||||
index < 2
|
||||
? 0
|
||||
: index === 2
|
||||
? scaleH(208)
|
||||
: scaleH(index * 248 - 248 - 40);
|
||||
|
||||
// 🆕 DetailPanel로 이동 (ShopByShow 방식)
|
||||
dispatch(
|
||||
startVideoPlayer({
|
||||
modal: false,
|
||||
patnrId,
|
||||
prdtId,
|
||||
showId,
|
||||
shptmBanrTpNm: "VOD",
|
||||
thumbnail: videoThumbnail,
|
||||
targetId: "spotlightId-" + prdtId,
|
||||
y,
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: { patnrId, prdtId },
|
||||
})
|
||||
);
|
||||
|
||||
// 🔴 기존 PlayerPanel 로직 (주석처리)
|
||||
// let y =
|
||||
// index < 2
|
||||
// ? 0
|
||||
// : index === 2
|
||||
// ? scaleH(208)
|
||||
// : scaleH(index * 248 - 248 - 40);
|
||||
//
|
||||
// dispatch(
|
||||
// startVideoPlayer({
|
||||
// modal: false,
|
||||
// patnrId,
|
||||
// prdtId,
|
||||
// showId,
|
||||
// shptmBanrTpNm: "VOD",
|
||||
// thumbnail: videoThumbnail,
|
||||
// targetId: "spotlightId-" + prdtId,
|
||||
// y,
|
||||
// })
|
||||
// );
|
||||
},
|
||||
[catCd, dispatch, patnrId, showId, videoThumbnail]
|
||||
[catCd, dispatch, patnrId]
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
|
||||
@@ -6,7 +6,11 @@ import Spotlight from '@enact/spotlight';
|
||||
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import { pushPanel, updatePanel, navigateFromBestSeller } from '../../../actions/panelActions';
|
||||
import {
|
||||
navigateFromBestSeller,
|
||||
pushPanel,
|
||||
updatePanel,
|
||||
} from '../../../actions/panelActions';
|
||||
import { navigateToDetailFromHome } from '../../../actions/panelNavigationActions';
|
||||
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
|
||||
import Tag from '../../../components/TItemCard/Tag';
|
||||
@@ -15,13 +19,20 @@ import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
|
||||
import TScroller from '../../../components/TScroller/TScroller';
|
||||
import useScrollReset from '../../../hooks/useScrollReset';
|
||||
import useScrollTo from '../../../hooks/useScrollTo';
|
||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../utils/Config';
|
||||
import {
|
||||
LOG_CONTEXT_NAME,
|
||||
LOG_MESSAGE_ID,
|
||||
panel_names,
|
||||
} from '../../../utils/Config';
|
||||
import { $L, scaleW } from '../../../utils/helperMethods';
|
||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||
import css from './BestSeller.module.less';
|
||||
|
||||
const SpottableComponent = Spottable('div');
|
||||
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: 'last-focused' },
|
||||
'div'
|
||||
);
|
||||
|
||||
const BestSeller = ({
|
||||
order,
|
||||
@@ -33,15 +44,26 @@ const BestSeller = ({
|
||||
shelfTitle,
|
||||
}) => {
|
||||
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, true);
|
||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(
|
||||
scrollLeft,
|
||||
true
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { cursorVisible } = useSelector((state) => state.common.appStatus);
|
||||
|
||||
const bestSellerDatas = useSelector((state) => state.product.bestSellerData?.bestSeller);
|
||||
const bestSellerDatas = useSelector(
|
||||
(state) => state.product.bestSellerData?.bestSeller
|
||||
);
|
||||
|
||||
const bestSellerNewDatas = useSelector((state) => state.foryou?.recommendInfo?.recommendProduct);
|
||||
const bestSellerNewDatas = useSelector(
|
||||
(state) => state.foryou?.recommendInfo?.recommendProduct
|
||||
);
|
||||
|
||||
const { userNumber } = useSelector(
|
||||
(state) => state.common.appStatus.loginUserData
|
||||
);
|
||||
|
||||
const [drawChk, setDrawChk] = useState(false);
|
||||
const [firstChk, setFirstChk] = useState(0);
|
||||
@@ -51,11 +73,14 @@ const BestSeller = ({
|
||||
|
||||
useEffect(() => {
|
||||
setBestInfos(
|
||||
bestSellerNewDatas?.filter((item) => item.recommendTpCd === 'BESTSELLER') || [] // 기본값으로 빈 배열 설정
|
||||
bestSellerNewDatas?.filter(
|
||||
(item) => item.recommendTpCd === 'BESTSELLER'
|
||||
) || [] // 기본값으로 빈 배열 설정
|
||||
);
|
||||
}, [bestSellerNewDatas]);
|
||||
|
||||
useEffect(() => {
|
||||
if (userNumber) {
|
||||
if (!bestInfos || bestInfos.length === 0) {
|
||||
const baseData =
|
||||
bestSellerDatas?.map((item) => ({
|
||||
@@ -72,7 +97,9 @@ const BestSeller = ({
|
||||
foryou: true,
|
||||
})) || [];
|
||||
|
||||
const recommendedPrdtIds = new Set(recommendedData.map((item) => item.prdtId));
|
||||
const recommendedPrdtIds = new Set(
|
||||
recommendedData.map((item) => item.prdtId)
|
||||
);
|
||||
|
||||
const baseData =
|
||||
bestSellerDatas?.map((item) => ({
|
||||
@@ -81,7 +108,10 @@ const BestSeller = ({
|
||||
})) || [];
|
||||
|
||||
setBestItemNewData(baseData);
|
||||
}, [bestSellerDatas, bestInfos]);
|
||||
} else {
|
||||
setBestItemNewData(bestSellerDatas);
|
||||
}
|
||||
}, [bestSellerDatas, bestInfos, userNumber]);
|
||||
|
||||
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
||||
|
||||
@@ -144,7 +174,10 @@ const BestSeller = ({
|
||||
if (c) {
|
||||
let cAriaLabel = c.getAttribute('aria-label');
|
||||
if (cAriaLabel) {
|
||||
const newcAriaLabel = cAriaLabel.replace('Best Seller, Heading 1,', '');
|
||||
const newcAriaLabel = cAriaLabel.replace(
|
||||
'Best Seller, Heading 1,',
|
||||
''
|
||||
);
|
||||
c.setAttribute('aria-label', newcAriaLabel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ export default function HomeBanner({
|
||||
|
||||
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
const isDeepLinkEntry = useSelector(
|
||||
(state) => state.home.homeInfo?.panelInfo?.isDeepLinkEntry
|
||||
);
|
||||
// 🔽 useFocusHistory - 경량화된 범용 포커스 히스토리
|
||||
const focusHistory = useFocusHistory({
|
||||
enableLogging: true,
|
||||
@@ -164,10 +167,11 @@ export default function HomeBanner({
|
||||
videoData = targetBannerData.bannerDetailInfos?.[0];
|
||||
}
|
||||
|
||||
// DetailPanel이 떠 있는 동안에는 배너 자동 재생을 스킵 (PlayerPanel 모달 재설정 방지)
|
||||
// 🔽 [251221] DetailPanel이나 DeepLink PlayerPanel이 떠 있으면 배너 자동 재생 스킵
|
||||
const hasDetailPanel = panels.some((p) => p.name === panel_names.DETAIL_PANEL);
|
||||
const hasPlayerPanel = panels.some((p) => p.name === panel_names.PLAYER_PANEL);
|
||||
|
||||
if (!hasDetailPanel && videoData && (videoData.shptmBanrTpNm === 'LIVE' || videoData.shptmBanrTpNm === 'VOD')) {
|
||||
if (!hasDetailPanel && !hasPlayerPanel && !isDeepLinkEntry && videoData && (videoData.shptmBanrTpNm === 'LIVE' || videoData.shptmBanrTpNm === 'VOD')) {
|
||||
console.log('[HomeBanner] 초기 비디오 자동 재생:', defaultFocus);
|
||||
|
||||
dispatch(
|
||||
@@ -185,7 +189,7 @@ export default function HomeBanner({
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [bannerDataList, defaultFocus, dispatch, panels]);
|
||||
}, [bannerDataList, defaultFocus, dispatch, panels, isDeepLinkEntry]);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(index, isHorizontal) => {
|
||||
|
||||
@@ -7,10 +7,7 @@ import React, {
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { applyMiddleware } from 'redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
@@ -23,11 +20,11 @@ import {
|
||||
import hsn from '../../../assets/images/bg/hsn_new.png';
|
||||
import koreaKiosk from '../../../assets/images/bg/koreaKiosk_new.png';
|
||||
import lgelectronics from '../../../assets/images/bg/lgelectronics_new.png';
|
||||
import nbcu from '../../../assets/images/bg/nbcu_new.png';
|
||||
import ontv4u from '../../../assets/images/bg/ontv4u_new.png';
|
||||
import Pinkfong from '../../../assets/images/bg/Pinkfong_new.png';
|
||||
import qvc from '../../../assets/images/bg/qvc_new.png';
|
||||
import shoplc from '../../../assets/images/bg/shoplc_new.png';
|
||||
import nbcu from '../../../assets/images/bg/nbcu_new.png';
|
||||
import { types } from '../../actions/actionTypes';
|
||||
import {
|
||||
changeAppStatus,
|
||||
@@ -45,14 +42,8 @@ import {
|
||||
getHomeMainContents,
|
||||
updateHomeInfo,
|
||||
} from '../../actions/homeActions';
|
||||
import {
|
||||
sendLogGNB,
|
||||
sendLogTotalRecommend,
|
||||
} from '../../actions/logActions';
|
||||
import {
|
||||
getSubCategory,
|
||||
getTop20Show,
|
||||
} from '../../actions/mainActions';
|
||||
import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
|
||||
import { getSubCategory, getTop20Show } from '../../actions/mainActions';
|
||||
import { setMyPageTermsAgree } from '../../actions/myPageActions';
|
||||
import { getHomeOnSaleInfo } from '../../actions/onSaleActions';
|
||||
import { updatePanel } from '../../actions/panelActions';
|
||||
@@ -69,8 +60,7 @@ import TButton, { TYPES } from '../../components/TButton/TButton';
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
import TNewPopUp from '../../components/TPopUp/TNewPopUp';
|
||||
import TPopUp from '../../components/TPopUp/TPopUp';
|
||||
import TVerticalPagenator
|
||||
from '../../components/TVerticalPagenator/TVerticalPagenator';
|
||||
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
|
||||
import useDebugKey from '../../hooks/useDebugKey';
|
||||
import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
@@ -178,68 +168,113 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
// });
|
||||
const isGnbOpened = useSelector((state) => state.common.isGnbOpened);
|
||||
const homeLayoutInfo = useSelector((state) => state.home.layoutData);
|
||||
const panelInfo = useSelector((state) => state.home.homeInfo?.panelInfo ?? {});
|
||||
const panelInfo = useSelector(
|
||||
(state) => state.home.homeInfo?.panelInfo ?? {}
|
||||
);
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
const webOSVersion = useSelector((state) => state.common.appStatus?.webOSVersion);
|
||||
const webOSVersion = useSelector(
|
||||
(state) => state.common.appStatus?.webOSVersion
|
||||
);
|
||||
const enterThroughGNB = useSelector((state) => state.home.enterThroughGNB);
|
||||
const defaultFocus = useSelector((state) => state.home.defaultFocus);
|
||||
const bannerDataList = useSelector((state) => state.home.bannerData?.bannerInfos);
|
||||
const bannerDataList = useSelector(
|
||||
(state) => state.home.bannerData?.bannerInfos
|
||||
);
|
||||
|
||||
// ✅ PlayerPanel의 shouldShrinkTo1px 상태 추적
|
||||
const playerPanelShouldShrink = useSelector((state) => {
|
||||
const playerPanel = state.panels.panels.find((p) => p.name === panel_names.PLAYER_PANEL);
|
||||
const playerPanel = state.panels.panels.find(
|
||||
(p) => p.name === panel_names.PLAYER_PANEL
|
||||
);
|
||||
return playerPanel?.panelInfo?.shouldShrinkTo1px ?? false;
|
||||
});
|
||||
|
||||
// ✅ PlayerPanel의 modal 상태 추적 (false → true 감지용)
|
||||
const playerModalState = useSelector((state) => {
|
||||
const playerPanel = state.panels.panels.find((p) => p.name === panel_names.PLAYER_PANEL);
|
||||
const playerPanel = state.panels.panels.find(
|
||||
(p) => p.name === panel_names.PLAYER_PANEL
|
||||
);
|
||||
return playerPanel?.panelInfo?.modal ?? false;
|
||||
});
|
||||
const prevPlayerModalStateRef = useRef(false);
|
||||
|
||||
const categoryInfos = useSelector((state) => state.onSale.homeOnSaleData?.data?.categoryInfos);
|
||||
const categoryInfos = useSelector(
|
||||
(state) => state.onSale.homeOnSaleData?.data?.categoryInfos
|
||||
);
|
||||
|
||||
const categoryItemInfos = useSelector((state) => state.main.subCategoryData?.categoryItemInfos);
|
||||
const categoryItemInfos = useSelector(
|
||||
(state) => state.main.subCategoryData?.categoryItemInfos
|
||||
);
|
||||
|
||||
const { popupVisible, activePopup } = useSelector((state) => state.common.popup);
|
||||
const { popupVisible, activePopup } = useSelector(
|
||||
(state) => state.common.popup
|
||||
);
|
||||
|
||||
const eventPopInfosData = useSelector((state) => state.event.eventData.eventPopInfo);
|
||||
const eventPopInfosData = useSelector(
|
||||
(state) => state.event.eventData.eventPopInfo
|
||||
);
|
||||
const eventData = useSelector((state) => state.event.eventData);
|
||||
const eventClickSuccess = useSelector((state) => state.event.eventClickSuccess);
|
||||
const homeOnSaleInfos = useSelector((state) => state.onSale.homeOnSaleData?.data.homeOnSaleInfos);
|
||||
const bestSellerDatas = useSelector((state) => state.product.bestSellerData?.bestSeller);
|
||||
const eventClickSuccess = useSelector(
|
||||
(state) => state.event.eventClickSuccess
|
||||
);
|
||||
const homeOnSaleInfos = useSelector(
|
||||
(state) => state.onSale.homeOnSaleData?.data.homeOnSaleInfos
|
||||
);
|
||||
const bestSellerDatas = useSelector(
|
||||
(state) => state.product.bestSellerData?.bestSeller
|
||||
);
|
||||
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
|
||||
const isDeepLink = useSelector((state) => state.common.deepLinkInfo.isDeepLink);
|
||||
const isDeepLink = useSelector(
|
||||
(state) => state.common.deepLinkInfo.isDeepLink
|
||||
);
|
||||
|
||||
// 선택약관 관련 Redux 상태
|
||||
const termsData = useSelector((state) => state.home.termsData);
|
||||
const termsIdMap = useSelector((state) => state.home.termsIdMap);
|
||||
const optionalTermsAvailable = useSelector((state) => state.home.optionalTermsAvailable);
|
||||
const optionalTermsAvailable = useSelector(
|
||||
(state) => state.home.optionalTermsAvailable
|
||||
);
|
||||
const optionalTermsData = useSelector((state) => {
|
||||
if (state.home.termsData && state.home.termsData.data && state.home.termsData.data.terms) {
|
||||
return state.home.termsData.data.terms.find((term) => term.trmsTpCd === 'MST00405');
|
||||
if (
|
||||
state.home.termsData &&
|
||||
state.home.termsData.data &&
|
||||
state.home.termsData.data.terms
|
||||
) {
|
||||
return state.home.termsData.data.terms.find(
|
||||
(term) => term.trmsTpCd === 'MST00405'
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const termsLoading = useSelector((state) => state.common.termsLoading);
|
||||
const currentTermsFlag = useSelector((state) => state.common.termsFlag);
|
||||
const optionalTermsPopupFlow = useSelector((state) => state.common.optionalTermsPopupFlow);
|
||||
const optionalTermsPopupFlow = useSelector(
|
||||
(state) => state.common.optionalTermsPopupFlow
|
||||
);
|
||||
|
||||
const { userNumber } = useSelector(
|
||||
(state) => state.common.appStatus.loginUserData
|
||||
);
|
||||
|
||||
const [btnDisabled, setBtnDisabled] = useState(true);
|
||||
const [arrowBottom, setArrowBottom] = useState(true);
|
||||
const [firstSpot, setFirstSpot] = useState(false);
|
||||
const [eventPopOpen, setEventPopOpen] = useState(false);
|
||||
const [nowShelf, setNowShelf] = useState(panelInfo.nowShelf);
|
||||
const [firstLgCatCd, setFirstLgCatCd] = useState(panelInfo.currentCatCd ?? null);
|
||||
const [firstLgCatCd, setFirstLgCatCd] = useState(
|
||||
panelInfo.currentCatCd ?? null
|
||||
);
|
||||
const [cateCd, setCateCd] = useState(panelInfo.currentCatCd ?? null);
|
||||
const [cateNm, setCateNm] = useState(panelInfo.currentCateName ?? null);
|
||||
// 선택약관 팝업 상태
|
||||
const [isOptionalConfirmVisible, setIsOptionalConfirmVisible] = useState(false);
|
||||
const [isOptionalConfirmVisible, setIsOptionalConfirmVisible] =
|
||||
useState(false);
|
||||
const [isOptionalTermsVisible, setIsOptionalTermsVisible] = useState(false);
|
||||
const [optionalTermsAgreed, setOptionalTermsAgreed] = useState(false);
|
||||
const { entryMenu, nowMenu } = useSelector((state) => state.common.menu);
|
||||
const [focusedContainerId, setFocusedContainerId] = useState(panelInfo.focusedContainerId);
|
||||
const [focusedContainerId, setFocusedContainerId] = useState(
|
||||
panelInfo.focusedContainerId
|
||||
);
|
||||
// DetailPanel 진입 시 포커스 대상 저장
|
||||
const lastFocusedTargetRef = useRef(panelInfo.lastFocusedTargetId || null);
|
||||
|
||||
@@ -254,7 +289,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
useEffect(() => {
|
||||
if (prevIsOnTopRef.current && !isOnTop) {
|
||||
const current = Spotlight.getCurrent();
|
||||
const tBody = document.querySelector(`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`);
|
||||
const tBody = document.querySelector(
|
||||
`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`
|
||||
);
|
||||
|
||||
if (current && tBody && tBody.contains(current)) {
|
||||
const targetId = current.getAttribute('data-spotlight-id');
|
||||
@@ -286,7 +323,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
ImagePreloader.preloadAllImages(BACKGROUND_IMAGES)
|
||||
.then((results) => {
|
||||
const successCount = results.filter((r) => r !== null).length;
|
||||
dlog(`[HomePanel] Background images preloaded: ${successCount}/${results.length} images`);
|
||||
dlog(
|
||||
`[HomePanel] Background images preloaded: ${successCount}/${results.length} images`
|
||||
);
|
||||
|
||||
// 프리로딩 통계 정보 로깅 (디버깅용)
|
||||
const stats = ImagePreloader.getStats();
|
||||
@@ -312,7 +351,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
|
||||
const sortedHomeLayoutInfo = useMemo(() => {
|
||||
if (homeLayoutInfo && homeLayoutInfo.homeLayoutInfo) {
|
||||
const sorted = [...homeLayoutInfo.homeLayoutInfo].sort((x, y) => x.expsOrd - y.expsOrd);
|
||||
const sorted = [...homeLayoutInfo.homeLayoutInfo].sort(
|
||||
(x, y) => x.expsOrd - y.expsOrd
|
||||
);
|
||||
return sorted;
|
||||
}
|
||||
return [];
|
||||
@@ -337,7 +378,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
const expandAttemptRef = useRef(0); // 복구 시도 횟수
|
||||
|
||||
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
|
||||
const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked);
|
||||
const isVideoTransitionLocked = useSelector(
|
||||
(state) => state.home.videoTransitionLocked
|
||||
);
|
||||
|
||||
// 선택약관 동의 핸들러
|
||||
const handleOptionalAgree = useCallback(() => {
|
||||
@@ -417,7 +460,7 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
});
|
||||
setTimeout(() => {
|
||||
Spotlight.focus('home_tbody');
|
||||
},100)
|
||||
}, 100);
|
||||
}, [handleOptionalAgree, dispatch, currentTermsFlag]);
|
||||
|
||||
const handleOptionalDeclineClick = useCallback(() => {
|
||||
@@ -428,7 +471,7 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
setIsOptionalConfirmVisible(false);
|
||||
setTimeout(() => {
|
||||
Spotlight.focus('home_tbody');
|
||||
},100)
|
||||
}, 100);
|
||||
}, [dispatch]);
|
||||
|
||||
const handleTermsPopupClosed = useCallback(() => {
|
||||
@@ -499,7 +542,12 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [shouldShowOptionalTermsPopup, termsLoading, isOptionalConfirmVisible, dispatch]);
|
||||
}, [
|
||||
shouldShowOptionalTermsPopup,
|
||||
termsLoading,
|
||||
isOptionalConfirmVisible,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
const currentSpot = Spotlight.getCurrent();
|
||||
@@ -550,7 +598,8 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
const containerId = sortedHomeLayoutInfo[0].shptmApphmDspyOptCd;
|
||||
const navigableEls = getContainerNavigableElements(containerId);
|
||||
const navigableIds = navigableEls.filter((el) => typeof el === 'string');
|
||||
const target = containerId === TEMPLATE_CODE_CONF.TOP ? 'banner0' : containerId;
|
||||
const target =
|
||||
containerId === TEMPLATE_CODE_CONF.TOP ? 'banner0' : containerId;
|
||||
|
||||
if (navigableIds.length > 0) {
|
||||
setContainerLastFocusedElement(null, navigableIds);
|
||||
@@ -626,7 +675,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
<HomeBanner
|
||||
key={el.shptmApphmDspyOptCd}
|
||||
spotlightId={el.shptmApphmDspyOptCd}
|
||||
firstSpot={!panelInfo.focusedContainerId && !panelInfo.currentSpot}
|
||||
firstSpot={
|
||||
!panelInfo.focusedContainerId && !panelInfo.currentSpot
|
||||
}
|
||||
className={css.homeBannerWrap}
|
||||
handleShelfFocus={handleItemFocus(
|
||||
el.shptmApphmDspyOptCd,
|
||||
@@ -718,7 +769,7 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
} else break;
|
||||
}
|
||||
case TEMPLATE_CODE_CONF.PICK_FOR_YOU: {
|
||||
if (bestSellerDatas && bestSellerDatas.length > 0) {
|
||||
if (userNumber) {
|
||||
return (
|
||||
<PickedForYou
|
||||
key={el.shptmApphmDspyOptCd}
|
||||
@@ -737,7 +788,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
}
|
||||
}
|
||||
})}
|
||||
{loadingComplete && sortedHomeLayoutInfo && sortedHomeLayoutInfo.length > 0 && (
|
||||
{loadingComplete &&
|
||||
sortedHomeLayoutInfo &&
|
||||
sortedHomeLayoutInfo.length > 0 && (
|
||||
<TButton
|
||||
className={css.tButton}
|
||||
onClick={handleTopButtonClick}
|
||||
@@ -914,7 +967,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
// console.log('[HomePanel] playVideo 호출 완료');
|
||||
|
||||
if (isDeepLink || (!panels.length && !panelInfo.focusedContainerId)) {
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
||||
dispatch(
|
||||
changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } })
|
||||
);
|
||||
dispatch(getHomeMainContents());
|
||||
dispatch(getHomeLayout());
|
||||
dispatch(
|
||||
@@ -1071,6 +1126,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
const detailPanelClosedTime = useSelector(
|
||||
(state) => state.home.homeInfo?.panelInfo?.detailPanelClosedAt
|
||||
);
|
||||
const isDeepLinkEntry = useSelector(
|
||||
(state) => state.home.homeInfo?.panelInfo?.isDeepLinkEntry
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (detailPanelClosed && isOnTop) {
|
||||
@@ -1082,9 +1140,18 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
// console.log('[HomePanel] *** videoPlayIntentRef.current:', videoPlayIntentRef.current);
|
||||
// console.log('[HomePanel] *** lastPlayedBannerIdRef.current:', lastPlayedBannerIdRef.current);
|
||||
|
||||
// 🔽 [251221] DeepLink로 PlayerPanel이 진입한 경우 자동 재생 스킵
|
||||
// (플래그 리셋은 PlayerPanel cleanup에서 처리하므로 여기서는 스킵만)
|
||||
if (isDeepLinkEntry) {
|
||||
dlog('[HomePanel] *** [DeepLink] isDeepLinkEntry=true 감지 - 자동 재생 스킵');
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔽 videoPlayIntentRef가 null인 경우: 비디오 재생 가능한 첫 번째 배너 찾기
|
||||
if (!videoPlayIntentRef.current && bannerDataList) {
|
||||
dlog('[HomePanel] *** videoPlayIntentRef가 null - 첫 번째 비디오 배너 검색');
|
||||
dlog(
|
||||
'[HomePanel] *** videoPlayIntentRef가 null - 첫 번째 비디오 배너 검색'
|
||||
);
|
||||
|
||||
// HomeBanner.jsx의 defaultFocus 계산 로직과 동일
|
||||
let targetIndex = 0;
|
||||
@@ -1138,7 +1205,10 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
};
|
||||
|
||||
lastPlayedBannerIdRef.current = bannerId;
|
||||
dlog('[HomePanel] *** videoPlayIntentRef 설정 완료:', videoPlayIntentRef.current);
|
||||
dlog(
|
||||
'[HomePanel] *** videoPlayIntentRef 설정 완료:',
|
||||
videoPlayIntentRef.current
|
||||
);
|
||||
} else {
|
||||
dlog('[HomePanel] *** ⚠️ 비디오 재생 가능한 배너를 찾지 못함');
|
||||
}
|
||||
@@ -1181,7 +1251,8 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
|
||||
// 🔽 DetailPanel에서 돌아온 뒤 포커스를 마지막 포커스 대상에 복원
|
||||
dlog('[HomePanel] *** 🎯 Focus 복원 준비');
|
||||
const targetFocusId = panelInfo.lastFocusedTargetId || lastFocusedTargetRef.current;
|
||||
const targetFocusId =
|
||||
panelInfo.lastFocusedTargetId || lastFocusedTargetRef.current;
|
||||
dlog(
|
||||
'[HomePanel] *** 📍 targetFocusId:',
|
||||
targetFocusId,
|
||||
@@ -1219,7 +1290,7 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [detailPanelClosed, isOnTop, bannerDataList, dispatch]);
|
||||
}, [detailPanelClosed, isOnTop, bannerDataList, isDeepLinkEntry, dispatch]);
|
||||
// =======
|
||||
// const justCameBack = !prevIsOnTopRef.current && isOnTop;
|
||||
|
||||
@@ -1250,7 +1321,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
targetSpotlightCateNm = c.getAttribute('data-catcd-nm');
|
||||
}
|
||||
|
||||
const tBody = document.querySelector(`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`);
|
||||
const tBody = document.querySelector(
|
||||
`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`
|
||||
);
|
||||
const currentSpot = c && tBody.contains(c) ? targetSpotlightId : null;
|
||||
|
||||
dispatch(checkEnterThroughGNB(false));
|
||||
@@ -1263,7 +1336,8 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
currentCateName: targetSpotlightCateNm,
|
||||
// <<<<<<< HEAD
|
||||
focusedContainerId: focusedContainerIdRef.current,
|
||||
lastFocusedTargetId: lastFocusedTargetRef.current || panelInfo.lastFocusedTargetId,
|
||||
lastFocusedTargetId:
|
||||
lastFocusedTargetRef.current || panelInfo.lastFocusedTargetId,
|
||||
// =======
|
||||
// focusedContainerId: focusedContainerId,
|
||||
// >>>>>>> gitlab/develop
|
||||
@@ -1339,7 +1413,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
<>
|
||||
{/* HomePanel용 메모리 상주 그라데이션 배경 */}
|
||||
<div
|
||||
className={classNames(css.gradientBackground, { [css.visible]: showGradientBackground })}
|
||||
className={classNames(css.gradientBackground, {
|
||||
[css.visible]: showGradientBackground,
|
||||
})}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
@@ -1369,7 +1445,10 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
)}
|
||||
|
||||
{arrowBottom && (
|
||||
<p className={classNames(css.arrow, css.arrowBottom)} onClick={handleArrowClick} />
|
||||
<p
|
||||
className={classNames(css.arrow, css.arrowBottom)}
|
||||
onClick={handleArrowClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activePopup === ACTIVE_POPUP.exitPopup && (
|
||||
@@ -1386,9 +1465,8 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
||||
text={$L('Are you sure you want to exit Shop Time?')}
|
||||
/>
|
||||
)}
|
||||
{(activePopup === ACTIVE_POPUP.eventPopup || activePopup === ACTIVE_POPUP.smsPopup) && (
|
||||
<EventPopUpBanner />
|
||||
)}
|
||||
{(activePopup === ACTIVE_POPUP.eventPopup ||
|
||||
activePopup === ACTIVE_POPUP.smsPopup) && <EventPopUpBanner />}
|
||||
{/* 선택약관 동의 팝업 */}
|
||||
<OptionalConfirm
|
||||
open={isOptionalConfirmVisible}
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
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 { getContainerId } from '@enact/spotlight/src/container';
|
||||
|
||||
import { updateHomeInfo } from '../../../actions/homeActions';
|
||||
import { pushPanel, popPanel } from '../../../actions/panelActions';
|
||||
import { popPanel, pushPanel } from '../../../actions/panelActions';
|
||||
import { startVideoPlayer } from '../../../actions/playActions';
|
||||
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
|
||||
import Tag from '../../../components/TItemCard/Tag';
|
||||
@@ -35,18 +25,15 @@ import {
|
||||
LOG_MESSAGE_ID,
|
||||
panel_names,
|
||||
} from '../../../utils/Config';
|
||||
import {
|
||||
$L,
|
||||
scaleW,
|
||||
} from '../../../utils/helperMethods';
|
||||
import { $L, scaleW } from '../../../utils/helperMethods';
|
||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||
import { TEMPLATE_CODE_CONF } from '../HomePanel';
|
||||
import css from '../PopularShow/PopularShow.module.less';
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
const SpottableComponent = Spottable('div');
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
{ enterTo: 'last-focused' },
|
||||
'div'
|
||||
);
|
||||
|
||||
const PopularShow = ({
|
||||
@@ -72,9 +59,13 @@ const PopularShow = ({
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
|
||||
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
|
||||
const recommendInfo = useSelector((state) => state.foryou?.recommendInfo?.recommendShow);
|
||||
|
||||
const recommendInfo = useSelector(
|
||||
(state) => state.foryou?.recommendInfo?.recommendShow
|
||||
);
|
||||
|
||||
const { userNumber } = useSelector(
|
||||
(state) => state.common.appStatus.loginUserData
|
||||
);
|
||||
|
||||
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
||||
const [drawChk, setDrawChk] = useState(false);
|
||||
@@ -89,15 +80,16 @@ const PopularShow = ({
|
||||
|
||||
useEffect(() => {
|
||||
setShowInfos(
|
||||
recommendInfo?.filter(
|
||||
(item) => item.recommendTpCd === "POPULARSHOW"
|
||||
) || []
|
||||
)
|
||||
},[recommendInfo])
|
||||
recommendInfo?.filter((item) => item.recommendTpCd === 'POPULARSHOW') ||
|
||||
[]
|
||||
);
|
||||
}, [recommendInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (userNumber) {
|
||||
if (!showInfos || showInfos.length === 0) {
|
||||
const baseData = topInfos?.map((item) => ({
|
||||
const baseData =
|
||||
topInfos?.map((item) => ({
|
||||
...item,
|
||||
foryou: false,
|
||||
})) || [];
|
||||
@@ -105,23 +97,27 @@ const PopularShow = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const recommendedData = showInfos[0].showInfos?.map((item) => ({
|
||||
const recommendedData =
|
||||
showInfos[0].showInfos?.map((item) => ({
|
||||
...item,
|
||||
foryou: true,
|
||||
})) || [];
|
||||
|
||||
const recommendedPrdtIds = new Set(recommendedData?.map(item => item.showId));
|
||||
const recommendedPrdtIds = new Set(
|
||||
recommendedData?.map((item) => item.showId)
|
||||
);
|
||||
|
||||
const baseData = topInfos?.map((item) => ({
|
||||
const baseData =
|
||||
topInfos?.map((item) => ({
|
||||
...item,
|
||||
foryou: recommendedPrdtIds.has(item.showId),
|
||||
})) || [];
|
||||
|
||||
setShowNewInfos([...baseData]);
|
||||
|
||||
}, [topInfos, showInfos]);
|
||||
|
||||
|
||||
} else {
|
||||
setShowNewInfos(topInfos);
|
||||
}
|
||||
}, [topInfos, showInfos, userNumber]);
|
||||
|
||||
const handleCardClick = useCallback(
|
||||
(patnrId, showId, catCd, showUrl) => () => {
|
||||
@@ -135,7 +131,7 @@ const PopularShow = ({
|
||||
startVideoPlayer({
|
||||
showId,
|
||||
patnrId,
|
||||
shptmBanrTpNm: "VOD",
|
||||
shptmBanrTpNm: 'VOD',
|
||||
lgCatCd: catCd,
|
||||
modal: false,
|
||||
showUrl: showUrl,
|
||||
@@ -151,7 +147,7 @@ const PopularShow = ({
|
||||
{
|
||||
name: panel_names.TRENDING_NOW_PANEL,
|
||||
panelInfo: {
|
||||
pageName: "PS",
|
||||
pageName: 'PS',
|
||||
focusedContainerId: SpotlightIds.TRENDING_NOW_POPULAR_SHOW,
|
||||
},
|
||||
},
|
||||
@@ -179,23 +175,23 @@ const PopularShow = ({
|
||||
|
||||
if (firstChk === 0 && itemIndex === 0) {
|
||||
const c = Spotlight.getCurrent();
|
||||
const getAriaLabel = c.getAttribute("aria-label");
|
||||
const getAriaLabel = c.getAttribute('aria-label');
|
||||
if (c) {
|
||||
let cAriaLabel = c.getAttribute("aria-label");
|
||||
cAriaLabel = "POPULAR SHOW, Heading 1," + cAriaLabel;
|
||||
c.setAttribute("aria-label", cAriaLabel);
|
||||
let cAriaLabel = c.getAttribute('aria-label');
|
||||
cAriaLabel = 'POPULAR SHOW, Heading 1,' + cAriaLabel;
|
||||
c.setAttribute('aria-label', cAriaLabel);
|
||||
}
|
||||
setFirstChk(1);
|
||||
} else if (firstChk === 1 && itemIndex === 0) {
|
||||
const c = Spotlight.getCurrent();
|
||||
if (c) {
|
||||
let cAriaLabel = c.getAttribute("aria-label");
|
||||
let cAriaLabel = c.getAttribute('aria-label');
|
||||
if (cAriaLabel) {
|
||||
const newcAriaLabel = cAriaLabel.replace(
|
||||
"POPULAR SHOW, Heading 1,",
|
||||
""
|
||||
'POPULAR SHOW, Heading 1,',
|
||||
''
|
||||
);
|
||||
c.setAttribute("aria-label", newcAriaLabel);
|
||||
c.setAttribute('aria-label', newcAriaLabel);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -246,7 +242,7 @@ const PopularShow = ({
|
||||
>
|
||||
<SectionTitle
|
||||
className={css.subTitle}
|
||||
title={$L("POPULAR SHOW")}
|
||||
title={$L('POPULAR SHOW')}
|
||||
data-title-index="homePopularShow"
|
||||
label="POPULAR SHOW"
|
||||
/>
|
||||
@@ -293,8 +289,8 @@ const PopularShow = ({
|
||||
showId={showId}
|
||||
showTitle={showNm}
|
||||
imageSource={
|
||||
(thumbnailUrl && thumbnailUrl960) ?
|
||||
thumbnailUrl !== thumbnailUrl960
|
||||
thumbnailUrl && thumbnailUrl960
|
||||
? thumbnailUrl !== thumbnailUrl960
|
||||
? thumbnailUrl960
|
||||
: thumbnailUrl
|
||||
: thumbnailUrl
|
||||
@@ -304,7 +300,7 @@ const PopularShow = ({
|
||||
nonPosition={true}
|
||||
type={TYPES.videoShow}
|
||||
imgType={
|
||||
vtctpYn !== "Y"
|
||||
vtctpYn !== 'Y'
|
||||
? IMAGETYPES.imgHorizontal
|
||||
: IMAGETYPES.imgVertical
|
||||
}
|
||||
@@ -313,11 +309,11 @@ const PopularShow = ({
|
||||
onFocus={handleFocus(itemIndex)}
|
||||
onBlur={handleBlur(itemIndex)}
|
||||
onClick={handleCardClick(patnrId, showId, catCd, showUrl)}
|
||||
firstLabel={patncNm + " "}
|
||||
label={itemIndex * 1 + 1 + " of " + showNewInfos.length}
|
||||
firstLabel={patncNm + ' '}
|
||||
label={itemIndex * 1 + 1 + ' of ' + showNewInfos.length}
|
||||
lastLabel=" go to detail, button"
|
||||
>
|
||||
{foryou === true && <Tag text={"For You"} />}
|
||||
{foryou === true && <Tag text={'For You'} />}
|
||||
</TItemCardNew>
|
||||
);
|
||||
}
|
||||
@@ -329,7 +325,7 @@ const PopularShow = ({
|
||||
className={css.displayBox}
|
||||
onClick={handleMoreCardClick}
|
||||
onFocus={_handleItemFocus}
|
||||
spotlightId={"home-popularshow-more-btn"}
|
||||
spotlightId={'home-popularshow-more-btn'}
|
||||
></SpottableComponent>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -8,21 +8,32 @@ import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
|
||||
|
||||
import { sendLogCuration } from '../../../actions/logActions';
|
||||
import { getSubCategory } from '../../../actions/mainActions';
|
||||
import { pushPanel, navigateFromSubCategory } from '../../../actions/panelActions';
|
||||
import {
|
||||
navigateFromSubCategory,
|
||||
pushPanel,
|
||||
} from '../../../actions/panelActions';
|
||||
import Tag from '../../../components/TItemCard/Tag';
|
||||
import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
|
||||
import TScroller from '../../../components/TScroller/TScroller';
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
import useScrollReset from '../../../hooks/useScrollReset';
|
||||
import useScrollTo from '../../../hooks/useScrollTo';
|
||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, LOG_TP_NO, panel_names } from '../../../utils/Config';
|
||||
import {
|
||||
LOG_CONTEXT_NAME,
|
||||
LOG_MESSAGE_ID,
|
||||
LOG_TP_NO,
|
||||
panel_names,
|
||||
} from '../../../utils/Config';
|
||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||
import CategoryNav from '../../HomePanel/SubCategory/CategoryNav/CategoryNav';
|
||||
import css from '../../HomePanel/SubCategory/SubCategory.module.less';
|
||||
|
||||
const SpottableComponent = Spottable('div');
|
||||
const Container = SpotlightContainerDecorator({ enterTo: null }, 'div');
|
||||
const ContainerBasic = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
const ContainerBasic = SpotlightContainerDecorator(
|
||||
{ enterTo: 'last-focused' },
|
||||
'div'
|
||||
);
|
||||
|
||||
const getExpsOrdByLgCatCd = (array, value) => {
|
||||
const expsOrd = array.findIndex(({ lgCatCd }) => value === lgCatCd) + 1;
|
||||
@@ -40,12 +51,24 @@ export default memo(function SubCategory({
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, false);
|
||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(
|
||||
scrollLeft,
|
||||
false
|
||||
);
|
||||
|
||||
const categoryInfos = useSelector((state) => state.home.menuData?.data?.homeCategory);
|
||||
const categoryItemInfos = useSelector((state) => state.main.subCategoryData?.categoryItemInfos);
|
||||
const categoryInfos = useSelector(
|
||||
(state) => state.home.menuData?.data?.homeCategory
|
||||
);
|
||||
const categoryItemInfos = useSelector(
|
||||
(state) => state.main.subCategoryData?.categoryItemInfos
|
||||
);
|
||||
|
||||
const foruItemInfos = useSelector((state) => state.main.recommendProduct[0]?.productInfos);
|
||||
const foruItemInfos = useSelector(
|
||||
(state) => state.main.recommendProduct[0]?.productInfos
|
||||
);
|
||||
const { userNumber } = useSelector(
|
||||
(state) => state.common.appStatus.loginUserData
|
||||
);
|
||||
|
||||
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
||||
|
||||
@@ -207,6 +230,7 @@ export default memo(function SubCategory({
|
||||
}, [handleShelfFocus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (userNumber) {
|
||||
if (!foruItemInfos || foruItemInfos.length === 0) {
|
||||
const baseData =
|
||||
categoryItemInfos?.subCatItemList?.map((item) => ({
|
||||
@@ -223,7 +247,9 @@ export default memo(function SubCategory({
|
||||
foryou: true,
|
||||
})) || [];
|
||||
|
||||
const recommendedPrdtIds = new Set(recommendedData.map((item) => item.prdtId));
|
||||
const recommendedPrdtIds = new Set(
|
||||
recommendedData.map((item) => item.prdtId)
|
||||
);
|
||||
|
||||
const baseData =
|
||||
categoryItemInfos?.subCatItemList?.map((item) => ({
|
||||
@@ -232,10 +258,17 @@ export default memo(function SubCategory({
|
||||
})) || [];
|
||||
|
||||
setCategoryItemNewData([...baseData]);
|
||||
}, [categoryItemInfos?.subCatItemList, foruItemInfos]);
|
||||
} else {
|
||||
setCategoryItemNewData(categoryItemInfos?.subCatItemList);
|
||||
}
|
||||
}, [categoryItemInfos?.subCatItemList, foruItemInfos, userNumber]);
|
||||
|
||||
return (
|
||||
<Container spotlightId={spotlightId} data-wheel-point onFocus={_handleShelfFocus}>
|
||||
<Container
|
||||
spotlightId={spotlightId}
|
||||
data-wheel-point
|
||||
onFocus={_handleShelfFocus}
|
||||
>
|
||||
<CategoryNav
|
||||
categoryInfos={categoryInfos}
|
||||
currentCategoryCode={currentLgCatCd}
|
||||
@@ -293,7 +326,12 @@ export default memo(function SubCategory({
|
||||
offerInfo={offerInfo}
|
||||
data-catcd-num={currentLgCatCd}
|
||||
data-catcd-nm={currentLgCatNm}
|
||||
label={itemIndex * 1 + 1 + ' of ' + (categoryItemNewData?.length || 0)}
|
||||
label={
|
||||
itemIndex * 1 +
|
||||
1 +
|
||||
' of ' +
|
||||
(categoryItemNewData?.length || 0)
|
||||
}
|
||||
lastLabel=" go to detail, button"
|
||||
euEnrgLblInfos={euEnrgLblInfos}
|
||||
>
|
||||
|
||||
@@ -209,6 +209,9 @@ export default function MainView({ className, initService }) {
|
||||
const hasFeaturedBrandsPanel = panels.some(
|
||||
(panel) => panel?.name === Config.panel_names.FEATURED_BRANDS_PANEL
|
||||
);
|
||||
const hasTrendingNowPanel = panels.some(
|
||||
(panel) => panel?.name === Config.panel_names.TRENDING_NOW_PANEL
|
||||
);
|
||||
// 단독 패널 체크 - CheckOutPanel, CartPanel 등 단독으로 렌더링되어야 하는 패널들
|
||||
if (DEBUG_MODE) {
|
||||
console.log(`[PANEL_MainView] 🔍 Top panel name: ${topPanel?.name}`);
|
||||
@@ -250,7 +253,11 @@ export default function MainView({ className, initService }) {
|
||||
'[MainView] Rendering 3-layer structure: PlayerPanel + DetailPanel + MediaPanel'
|
||||
);
|
||||
}
|
||||
renderingPanels = hasFeaturedBrandsPanel ? panels.slice(-4) : panels.slice(-3);
|
||||
if (hasFeaturedBrandsPanel || hasTrendingNowPanel) {
|
||||
renderingPanels = panels.slice(-4);
|
||||
} else {
|
||||
renderingPanels = panels.slice(-3);
|
||||
}
|
||||
} else if (
|
||||
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL ||
|
||||
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL_NEW ||
|
||||
@@ -258,7 +265,11 @@ export default function MainView({ className, initService }) {
|
||||
panels[panels.length - 2]?.name === Config.panel_names.PLAYER_PANEL ||
|
||||
panels[panels.length - 2]?.name === Config.panel_names.MEDIA_PANEL
|
||||
) {
|
||||
renderingPanels = hasFeaturedBrandsPanel ? panels.slice(-3) : panels.slice(-2);
|
||||
if (hasFeaturedBrandsPanel || hasTrendingNowPanel) {
|
||||
renderingPanels = panels.slice(-3);
|
||||
} else {
|
||||
renderingPanels = panels.slice(-2);
|
||||
}
|
||||
} else {
|
||||
renderingPanels = panels.slice(-1);
|
||||
}
|
||||
|
||||
@@ -391,6 +391,7 @@ const MediaPanel = React.forwardRef(
|
||||
const onEnded = useCallback(
|
||||
(e) => {
|
||||
debugLog('[MediaPanel] Video ended');
|
||||
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] MediaPanel onEnded triggered - will pop after 1500ms');
|
||||
// continuousPlay는 MediaPlayer(VideoPlayer) 컴포넌트 내부에서 loop 속성으로 처리
|
||||
// onEnded가 호출되면 loop=false 인 경우이므로 패널을 닫음
|
||||
Spotlight.pause();
|
||||
@@ -402,6 +403,7 @@ const MediaPanel = React.forwardRef(
|
||||
|
||||
// ✅ 새로운 타이머 저장 (cleanup 시 정리용)
|
||||
onEndedTimerRef.current = setTimeout(() => {
|
||||
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] Executing popPanel(MEDIA_PANEL) after 1500ms');
|
||||
Spotlight.resume();
|
||||
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||
onEndedTimerRef.current = null;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
video {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain; /* 비율 유지하면서 컨테이너 안에 맞춤 */
|
||||
object-fit: contain; /* 높이 기준으로 맞추고 좌우는 잘림 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -912,7 +912,7 @@ const MediaPanel = React.forwardRef(
|
||||
}
|
||||
|
||||
if (!panelInfo.modal) {
|
||||
dispatch(PanelActions.popPanel());
|
||||
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||
dispatch(changeAppStatus({ cursorVisible: false }));
|
||||
document?.dispatchEvent?.(new CustomEvent('detailpanel-scroll-reset'));
|
||||
|
||||
@@ -937,7 +937,7 @@ const MediaPanel = React.forwardRef(
|
||||
// 패널이 2개 존재할때만 popPanel 진행
|
||||
// fullscreen 전환 중이면 popPanel하지 않음
|
||||
if (panelInfo.modal && !isOnTop && !isTransitioningToFullscreen.current) {
|
||||
dispatch(PanelActions.popPanel());
|
||||
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||
} else {
|
||||
Spotlight.focus('tbody');
|
||||
}
|
||||
@@ -1576,7 +1576,7 @@ const MediaPanel = React.forwardRef(
|
||||
) {
|
||||
// case: Featured Brands
|
||||
if (panelInfo?.sourcePanel === panel_names.FEATURED_BRANDS_PANEL) {
|
||||
dispatch(PanelActions.popPanel());
|
||||
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||
}
|
||||
}
|
||||
}, [
|
||||
@@ -2043,19 +2043,25 @@ const MediaPanel = React.forwardRef(
|
||||
(e) => {
|
||||
const currentInfo = panelInfoRef.current;
|
||||
|
||||
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] onEnded triggered - shptmBanrTpNm:', currentInfo?.shptmBanrTpNm);
|
||||
|
||||
// MEDIA: 기존 동작 유지 (배경 복원 없이 즉시 pop)
|
||||
if (currentInfo.shptmBanrTpNm === 'MEDIA') {
|
||||
console.log('[MediaPanel] 🚫 Skipping background restoration for ended media');
|
||||
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] MEDIA type - popPanel will be called');
|
||||
Spotlight.pause();
|
||||
setTimeout(() => {
|
||||
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] setTimeout fired - dispatching popPanel(MEDIA_PANEL)');
|
||||
Spotlight.resume();
|
||||
dispatch(PanelActions.popPanel());
|
||||
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||
}, VIDEO_END_ACTION_DELAY);
|
||||
e?.stopPropagation();
|
||||
e?.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// VOD: modal 여부에 따라 동작 분리
|
||||
if (currentInfo.shptmBanrTpNm === 'VOD') {
|
||||
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] VOD type - popPanel will be called');
|
||||
Spotlight.pause();
|
||||
setTimeout(() => {
|
||||
stopExternalPlayer();
|
||||
@@ -2074,6 +2080,8 @@ const MediaPanel = React.forwardRef(
|
||||
e?.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] Unknown shptmBanrTpNm - no action taken');
|
||||
},
|
||||
[dispatch, focusBackButtonOrFallback, stopExternalPlayer]
|
||||
);
|
||||
|
||||
@@ -390,7 +390,7 @@ function PlayerOverlayContents({
|
||||
e.preventDefault();
|
||||
// tabIndexV2가 2일 때만 LiveChannelNext로 포커스
|
||||
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||
Spotlight.focus('live-channel-next-button');
|
||||
Spotlight.focus('below-tab-shop-now-button');
|
||||
}
|
||||
}}
|
||||
onSpotlightDown={(e) => {
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
background-image: url("../../../../assets/images/btn/btn-video-cc-nor@3x.png");
|
||||
background-size: cover;
|
||||
position: absolute;
|
||||
right: 60px;
|
||||
top: 800px;
|
||||
right: 300px;
|
||||
top: 680px;
|
||||
z-index: 10;
|
||||
|
||||
&.videoVericalSubtitleButton {
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
getMainLiveShow,
|
||||
getMainLiveShowNowProduct,
|
||||
} from '../../actions/mainActions';
|
||||
import { updateHomeInfo } from '../../actions/homeActions';
|
||||
import * as PanelActions from '../../actions/panelActions';
|
||||
import { updatePanel } from '../../actions/panelActions';
|
||||
import {
|
||||
@@ -90,6 +91,51 @@ const findSelector = (selector, maxAttempts = 5, currentAttempts = 0) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 배너 위치 수집 함수 (top, left만 저장)
|
||||
const collectBannerPositions = () => {
|
||||
const positions = [];
|
||||
|
||||
// banner0, banner1 등의 배너 위치 수집
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const bannerId = `banner${i}`;
|
||||
const node = document.querySelector(`[data-spotlight-id="${bannerId}"]`);
|
||||
|
||||
if (node) {
|
||||
const { top, left } = node.getBoundingClientRect();
|
||||
positions.push({
|
||||
bannerId,
|
||||
position: { top: Math.round(top), left: Math.round(left) }
|
||||
});
|
||||
dlog(`[PlayerPanel] 배너 위치 수집: ${bannerId}`, { top: Math.round(top), left: Math.round(left) });
|
||||
}
|
||||
}
|
||||
|
||||
return positions;
|
||||
};
|
||||
|
||||
// 위치 검증 함수 (오차 범위: 1px)
|
||||
const isPositionMatching = (bannerPositions, bannerId, currentPosition) => {
|
||||
const validPosition = bannerPositions.find(p => p.bannerId === bannerId);
|
||||
|
||||
if (!validPosition) {
|
||||
dlog(`[PlayerPanel] 배너 위치 검증 실패: ${bannerId} 배너를 찾을 수 없음`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const tolerance = 1; // 1px 오차 범위
|
||||
const isMatching =
|
||||
Math.abs(currentPosition.top - validPosition.position.top) <= tolerance &&
|
||||
Math.abs(currentPosition.left - validPosition.position.left) <= tolerance;
|
||||
|
||||
dlog(`[PlayerPanel] 배너 위치 검증: ${bannerId}`, {
|
||||
expected: validPosition.position,
|
||||
current: currentPosition,
|
||||
matching: isMatching
|
||||
});
|
||||
|
||||
return isMatching;
|
||||
};
|
||||
|
||||
const getLogTpNo = (type, nowMenu) => {
|
||||
if (type === 'LIVE') {
|
||||
switch (nowMenu) {
|
||||
@@ -218,6 +264,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
const [tabIndexV2, setTabIndexV2] = USE_STATE('tabIndexV2', 1); // 0: ShopNow, 1: LiveChannel, 2: ShopNowButton
|
||||
const [tabContainerVersion, setTabContainerVersion] = USE_STATE('tabContainerVersion', 2); // 1: TabContainer (우측), 2: TabContainerV2 (하단)
|
||||
const [isModalClosed, setIsModalClosed] = USE_STATE('isModalClosed', true); // 모달이 false 상태인지 나타내는 플래그
|
||||
const [validBannerPositions, setValidBannerPositions] = USE_STATE('validBannerPositions', []); // 유효한 배너 위치 (top, left)
|
||||
|
||||
const panels = USE_SELECTOR('panels', (state) => state.panels.panels);
|
||||
const chatData = USE_SELECTOR('chatData', (state) => state.play.chatData);
|
||||
@@ -407,6 +454,10 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
lastFocusedTargetId: panelInfo.lastFocusedTargetId,
|
||||
});
|
||||
|
||||
// TabContainerV2의 tabIndex를 ShopNowContents(0)로 리셋
|
||||
dlog('[PlayerPanel] 📑 TabContainerV2 tabIndex를 ShopNowContents(0)로 리셋');
|
||||
setTabIndexV2(0);
|
||||
|
||||
// 포커스 복원 로직 추가 (1000ms 지연)
|
||||
if (panelInfo.lastFocusedTargetId) {
|
||||
dlog(
|
||||
@@ -431,6 +482,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
};
|
||||
}
|
||||
|
||||
console.log('[PANEL] PlayerPanel updatePanel - detailPanelClosed reset');
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.PLAYER_PANEL,
|
||||
@@ -503,10 +555,10 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
|
||||
// PanelInfo 상태 변화 모니터링 useEffect (isPaused가 실제로 변경될 때만)
|
||||
useEffect(() => {
|
||||
const isOnTop = panel_names.HOME_PANEL === topPanel?.name;
|
||||
const isPausedChanged = previousPanelInfo.current?.isPaused !== panelInfo?.isPaused;
|
||||
|
||||
if (isOnTop && panelInfo?.isPaused !== undefined && isPausedChanged) {
|
||||
// isOnTop 여부와 관계없이 isPaused 변경을 감지하여 비디오 제어
|
||||
if (panelInfo?.isPaused !== undefined && isPausedChanged) {
|
||||
// 상태 변경 시에만 디버깅 로그 출력
|
||||
dlog('🔍 [PlayerPanel] PanelInfo isPaused changed', {
|
||||
previousIsPaused: previousPanelInfo.current?.isPaused,
|
||||
@@ -539,7 +591,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
}
|
||||
|
||||
previousPanelInfo.current = panelInfo;
|
||||
}, [panelInfo?.isPaused, topPanel?.name, currentPlayingUrl]);
|
||||
}, [panelInfo?.isPaused, currentPlayingUrl]);
|
||||
|
||||
// VideoPlayer 인스턴스 및 소스 변경 모니터링 (중요 변화만)
|
||||
useEffect(() => {
|
||||
@@ -647,6 +699,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
useEffect(() => {
|
||||
if (currentLiveShowInfo && Object.keys(currentLiveShowInfo).length > 0) {
|
||||
if (currentLiveShowInfo.showId !== panelInfo?.showId) {
|
||||
console.log('[PANEL] PlayerPanel updatePanel - LIVE showId update');
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.PLAYER_PANEL,
|
||||
@@ -1146,6 +1199,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
videoPlayer.current?.hideControls();
|
||||
setSelectedIndex(backupInitialIndex);
|
||||
if (panelInfo.shptmBanrTpNm === 'MEDIA') {
|
||||
console.log('[PANEL] PlayerPanel updatePanel - DETAIL_PANEL launchedFromPlayer false');
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
@@ -1163,6 +1217,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
if (!panelInfo.modal) {
|
||||
console.log('[PlayerPanel] popPanel - closeButtonHandler');
|
||||
dispatch(PanelActions.popPanel());
|
||||
// 🔽 [251221] cleanup useEffect에서 isDeepLinkEntry 리셋 처리
|
||||
dispatch(changeAppStatus({ cursorVisible: false }));
|
||||
|
||||
//딮링크로 플레이어 진입 후 이전버튼 클릭시
|
||||
@@ -1199,6 +1254,17 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
topPanel: panels[panels.length - 1]?.name,
|
||||
stack: panels.map((p) => p.name),
|
||||
});
|
||||
|
||||
// 🔽 [251221] PlayerPanel unmount 시 DeepLink 플래그 리셋
|
||||
dispatch(
|
||||
updateHomeInfo({
|
||||
name: panel_names.HOME_PANEL,
|
||||
panelInfo: {
|
||||
isDeepLinkEntry: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const topPanelName = panels[panels.length - 1]?.name;
|
||||
if (
|
||||
panelInfo.modal &&
|
||||
@@ -1212,7 +1278,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
Spotlight.focus('tbody');
|
||||
}
|
||||
};
|
||||
}, [panelInfo?.modal, isOnTop, panels]);
|
||||
}, [panelInfo?.modal, isOnTop, panels, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showNowInfos && panelInfo.shptmBanrTpNm === 'LIVE') {
|
||||
@@ -1309,10 +1375,21 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
|
||||
// 최상단 패널이 DetailPanel이고 PlayerPanel에서 진입했는지 확인
|
||||
const isTopPanelDetailFromPlayer = useMemo(() => {
|
||||
const result =
|
||||
let result =
|
||||
topPanel?.name === panel_names.DETAIL_PANEL &&
|
||||
topPanel?.panelInfo?.launchedFromPlayer === true;
|
||||
|
||||
// MediaPanel이 최상단에 있고 그 아래가 DetailPanel인 경우도 체크
|
||||
if (!result && topPanel?.name === panel_names.MEDIA_PANEL) {
|
||||
const prevPanel = panels[panels.length - 2];
|
||||
if (
|
||||
prevPanel?.name === panel_names.DETAIL_PANEL &&
|
||||
prevPanel?.panelInfo?.launchedFromPlayer === true
|
||||
) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 🔍 DetailPanel 상태 변화 로깅
|
||||
if (result) {
|
||||
dlog('🎬 [PlayerPanel] DetailPanel is now on top (from Player)', {
|
||||
@@ -1858,6 +1935,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
dlog('[PlayerPanel] Condition 2.5: Removing skipFlag after DOM render');
|
||||
console.log('[PANEL] PlayerPanel updatePanel - skipModalStyleRecalculation remove');
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.PLAYER_PANEL,
|
||||
@@ -1898,12 +1976,37 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
scale = width / window.innerWidth;
|
||||
setModalScale(scale);
|
||||
}
|
||||
console.log('[PANEL] PlayerPanel updatePanel - modalStyle and scale update');
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.PLAYER_PANEL,
|
||||
panelInfo: { modalStyle: modalStyle, modalScale: scale },
|
||||
})
|
||||
);
|
||||
|
||||
// 🔽 배너 위치 수집 (초기 로드 시에만 실행)
|
||||
if (validBannerPositions.length === 0) {
|
||||
const positions = collectBannerPositions();
|
||||
if (positions.length > 0) {
|
||||
setValidBannerPositions(positions);
|
||||
dlog('[PlayerPanel] ✅ 배너 위치 초기 수집 완료:', positions);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔽 배너 위치 검증 (위치가 맞지 않으면 비디오 재생 중단)
|
||||
if (validBannerPositions.length > 0) {
|
||||
const currentPosition = { top: Math.round(top), left: Math.round(left) };
|
||||
const isValidPosition = isPositionMatching(validBannerPositions, panelInfo.modalContainerId, currentPosition);
|
||||
|
||||
if (!isValidPosition) {
|
||||
dlog('[PlayerPanel] ⚠️ 배너 위치 검증 실패 - 비디오 재생 중단', {
|
||||
bannerId: panelInfo.modalContainerId,
|
||||
currentPosition,
|
||||
validBannerPositions
|
||||
});
|
||||
return; // 비디오 재생 중단
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dlog('[PlayerPanel] Condition 1: Node not found, using saved modalStyle');
|
||||
setModalStyle(panelInfo.modalStyle);
|
||||
@@ -2223,6 +2326,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
);
|
||||
Spotlight.focus('playVideoShopNowBox');
|
||||
} else {
|
||||
console.log('[PANEL] PlayerPanel updatePanel - handleIndicatorDownClick');
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.PLAYER_PANEL,
|
||||
@@ -2270,6 +2374,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
);
|
||||
Spotlight.focus('playVideoShopNowBox');
|
||||
} else {
|
||||
console.log('[PANEL] PlayerPanel updatePanel - handleIndicatorUpClick');
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.PLAYER_PANEL,
|
||||
@@ -2389,6 +2494,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
const onEnded = useCallback(
|
||||
(e) => {
|
||||
if (panelInfoRef.current.shptmBanrTpNm === 'MEDIA') {
|
||||
console.log('[PANEL] PlayerPanel updatePanel - DETAIL_PANEL video ended');
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
|
||||
@@ -4,16 +4,22 @@ import { useDispatch } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
|
||||
import { sendLogTotalRecommend } from '../../../../actions/logActions';
|
||||
// <<<<<<< HEAD
|
||||
import { updatePanel } from '../../../../actions/panelActions';
|
||||
import TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList';
|
||||
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 { $L } from '../../../../utils/helperMethods';
|
||||
import PlayerItemCard, { TYPES } from '../../PlayerItemCard/PlayerItemCard';
|
||||
import ListEmptyContents from '../TabContents/ListEmptyContents/ListEmptyContents';
|
||||
import css from './LiveChannelContents.module.less';
|
||||
import cssV2 from './LiveChannelContents.v2.module.less';
|
||||
import { sendLogTotalRecommend } from '../../../../actions/logActions';
|
||||
|
||||
// =======
|
||||
// import { updatePanel } from "../../../../actions/panelActions";
|
||||
// import TVirtualGridList from "../../../../components/TVirtualGridList/TVirtualGridList";
|
||||
@@ -181,6 +187,7 @@ export default function LiveChannelContents({
|
||||
startDt={strtDt}
|
||||
endDt={endDt}
|
||||
currentTime={currentTime}
|
||||
currentVideoVisible={currentVideoShowId === liveInfos[index].showId}
|
||||
// <<<<<<< HEAD
|
||||
version={version}
|
||||
// =======
|
||||
@@ -189,7 +196,15 @@ export default function LiveChannelContents({
|
||||
/>
|
||||
);
|
||||
},
|
||||
[liveInfos, currentTime, currentVideoShowId, isClickBlocked, dispatch, handleFocus, version]
|
||||
[
|
||||
liveInfos,
|
||||
currentTime,
|
||||
currentVideoShowId,
|
||||
isClickBlocked,
|
||||
dispatch,
|
||||
handleFocus,
|
||||
version,
|
||||
]
|
||||
);
|
||||
|
||||
const containerClass = version === 2 ? cssV2.container : css.container;
|
||||
|
||||
@@ -18,11 +18,19 @@ export default function ShopNowButton({ onClick }) {
|
||||
};
|
||||
|
||||
const handleSpotlightDown = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
// tabIndexV2가 2일 때만 CC 버튼으로 내려가기
|
||||
Spotlight.focus('live-channel-next-button');
|
||||
};
|
||||
|
||||
const handleSpotlightLeft = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
// tabIndexV2가 2일 때만 CC 버튼으로 내려가기
|
||||
Spotlight.focus('player-subtitlebutton');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={css.container}>
|
||||
<SpottableDiv
|
||||
@@ -31,6 +39,7 @@ export default function ShopNowButton({ onClick }) {
|
||||
spotlightId="below-tab-shop-now-button"
|
||||
onSpotlightUp={handleSpotlightUp}
|
||||
onSpotlightDown={handleSpotlightDown}
|
||||
onSpotlightLeft={handleSpotlightLeft}
|
||||
>
|
||||
<span className={css.buttonText}>SHOP NOW</span>
|
||||
</SpottableDiv>
|
||||
|
||||
@@ -196,7 +196,7 @@ export default function TabContainerV2({
|
||||
// tabIndex = 2 (ShopNowButton)
|
||||
const timeoutId = setTimeout(() => {
|
||||
Spotlight.focus('below-tab-shop-now-button');
|
||||
}, 100);
|
||||
}, 10);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
// 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';
|
||||
@@ -33,7 +39,9 @@ import {
|
||||
// showWarningToast,
|
||||
// } from '../../actions/toastActions';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
import TItemCardNew, { removeDotAndColon } from '../../components/TItemCard/TItemCard.new';
|
||||
import TItemCardNew, {
|
||||
removeDotAndColon,
|
||||
} from '../../components/TItemCard/TItemCard.new';
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
|
||||
import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList';
|
||||
@@ -41,15 +49,22 @@ import usePanelHistory from '../../hooks/usePanelHistory/usePanelHistory';
|
||||
// import VirtualKeyboardContainer from "../../components/TToast/VirtualKeyboardContainer";
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import { useSearchHistory } from '../../hooks/useSearchHistory';
|
||||
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 { createDebugHelpers } from '../../utils/debug';
|
||||
import NoSearchResults from './NoSearchResults/NoSearchResults';
|
||||
// import NoSearchResults from './NoSearchResults/NoSearchResults';
|
||||
import SearchInputOverlay from './SearchInputOverlay';
|
||||
import css from './SearchPanel.new.module.less';
|
||||
import SearchResultsNew from './SearchResults.new.v2';
|
||||
import TInputSimple, { ICONS, KINDS } from './TInput/TInputSimple';
|
||||
import VoiceInputOverlay, { VOICE_MODES } from './VoiceInputOverlay/VoiceInputOverlay';
|
||||
import { createDebugHelpers } from '../../utils/debug';
|
||||
import VoiceInputOverlay, {
|
||||
VOICE_MODES,
|
||||
} from './VoiceInputOverlay/VoiceInputOverlay';
|
||||
|
||||
// 디버그 헬퍼 설정
|
||||
const DEBUG_MODE = false;
|
||||
@@ -75,13 +90,22 @@ export const SEARCH_PANEL_MODES = {
|
||||
VOICE_RESULT: 'voice_result', // 음성 검색 결과 표시
|
||||
};
|
||||
|
||||
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'
|
||||
);
|
||||
|
||||
// 메모리 누수 방지를 위한 안전한 이미지 컴포넌트 (컴포넌트 외부로 이동)
|
||||
const SafeImageComponent = ({ src, alt, className, ...props }) => {
|
||||
@@ -118,7 +142,9 @@ const SafeImageComponent = ({ src, alt, className, ...props }) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <img ref={imgRef} src={src} alt={alt} className={className} {...props} />;
|
||||
return (
|
||||
<img ref={imgRef} src={src} alt={alt} className={className} {...props} />
|
||||
);
|
||||
};
|
||||
|
||||
const ITEMS_PER_PAGE = 9;
|
||||
@@ -152,22 +178,36 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 0hun: 패널 전역 상태
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
// 0hun: 음성 검색 결과에 대한 전역 상태
|
||||
const shopperHouseData = useSelector((state) => state.search.shopperHouseData);
|
||||
const shopperHouseError = useSelector((state) => state.search.shopperHouseError);
|
||||
const shopperHouseData = useSelector(
|
||||
(state) => state.search.shopperHouseData
|
||||
);
|
||||
const shopperHouseError = useSelector(
|
||||
(state) => state.search.shopperHouseError
|
||||
);
|
||||
// 0hun: 음성 검색 searchId (Redux에서 별도 관리)
|
||||
const shopperHouseSearchId = useSelector((state) => state.search.shopperHouseSearchId);
|
||||
const shopperHouseSearchId = useSelector(
|
||||
(state) => state.search.shopperHouseSearchId
|
||||
);
|
||||
// 0hun: 음성 검색 relativeQueries (Redux에서 별도 관리)
|
||||
const shopperHouseRelativeQueries = useSelector(
|
||||
(state) => state.search.shopperHouseRelativeQueries
|
||||
);
|
||||
// 🔄 이전 shopperHouseData (sortingType 변경 시 사용)
|
||||
const preShopperHouseData = useSelector((state) => state.search.preShopperHouseData);
|
||||
const preShopperHouseData = useSelector(
|
||||
(state) => state.search.preShopperHouseData
|
||||
);
|
||||
// 0hun: 검색 메인, Hot Picks for you 영역에 대한 전역 상태 값
|
||||
const hotPicksForYou = useSelector((state) => state.search.searchMainData.hotPicksForYou);
|
||||
const hotPicksForYou = useSelector(
|
||||
(state) => state.search.searchMainData.hotPicksForYou
|
||||
);
|
||||
// 0hun: 검색 메인, Popular Brands 영역에 대한 전역 상태 값
|
||||
const popularBrands = useSelector((state) => state.search.searchMainData.popularBrands);
|
||||
const popularBrands = useSelector(
|
||||
(state) => state.search.searchMainData.popularBrands
|
||||
);
|
||||
// 0hun: 검색 메인, Top Searchs 영역에 대한 전역 상태 값
|
||||
const topSearchs = useSelector((state) => state.search.searchMainData.topSearchs);
|
||||
const topSearchs = useSelector(
|
||||
(state) => state.search.searchMainData.topSearchs
|
||||
);
|
||||
// jhun: 검색 메인, Today Deals 영역에 대한 전역 상태 값
|
||||
const tsvInfo = useSelector((state) => state.search.searchMainData.tsvInfo);
|
||||
|
||||
@@ -177,7 +217,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 0hun: 초기 포커스 유무를 나타내는 Boolean 상태
|
||||
const [firstSpot, setFirstSpot] = useState(false);
|
||||
// 0hun: 검색어 상태
|
||||
const [searchQuery, setSearchQuery] = useState(panelInfo.searchVal ? panelInfo.searchVal : null);
|
||||
const [searchQuery, setSearchQuery] = useState(
|
||||
panelInfo.searchVal ? panelInfo.searchVal : null
|
||||
);
|
||||
// 0hun: 검색 컨테이너 포커스 position 상태 값
|
||||
const [position, setPosition] = useState(null);
|
||||
// 0hun: 가상 키보드 Display 유무 Boolean 값 (주석: 현재 VirtualKeyboardContainer가 비활성화됨)
|
||||
@@ -191,14 +233,17 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// ✨ [Phase 3] TInput의 입력 모드 상태 제거 (더 이상 InputField가 없으므로 불필요)
|
||||
// const [isInputModeActive, setIsInputModeActive] = useState(false);
|
||||
// 0hun: 현재 포커스된 container의 spotlightId를 관리하는 상태 값
|
||||
const [focusedContainerId, setFocusedContainerId] = useState(panelInfo?.focusedContainerId);
|
||||
const [focusedContainerId, setFocusedContainerId] = useState(
|
||||
panelInfo?.focusedContainerId
|
||||
);
|
||||
|
||||
// ✨ [Phase 1] SearchPanel의 현재 모드 상태 (VoiceInputOverlay의 VOICE_MODES와 동일한 개념)
|
||||
const [currentMode, setCurrentMode] = useState(SEARCH_PANEL_MODES.INITIAL);
|
||||
const [isShopperHousePending, setIsShopperHousePending] = useState(false);
|
||||
const [voiceOverlayMode, setVoiceOverlayMode] = useState(VOICE_MODES.PROMPT);
|
||||
const [voiceOverlayResponseText, setVoiceOverlayResponseText] = useState('');
|
||||
const [isVoiceOverlayBubbleSearch, setIsVoiceOverlayBubbleSearch] = useState(false);
|
||||
const [isVoiceOverlayBubbleSearch, setIsVoiceOverlayBubbleSearch] =
|
||||
useState(false);
|
||||
const [shouldFocusVoiceResult, setShouldFocusVoiceResult] = useState(false);
|
||||
|
||||
// 🎯 HowAboutThese 포커스 관리 - 검색 입력 영역 포커스 감지용 상태
|
||||
@@ -285,11 +330,16 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
const unifiedFocusTimerRef = useRef(null);
|
||||
|
||||
// ShopperHouse 에러 팝업 상태 가져오기
|
||||
const shopperHouseErrorPopup = useSelector((state) => state.search.shopperHouseErrorPopup);
|
||||
const shopperHouseErrorPopup = useSelector(
|
||||
(state) => state.search.shopperHouseErrorPopup
|
||||
);
|
||||
|
||||
// API 실패 시 fallback reference 초기화
|
||||
useEffect(() => {
|
||||
if (shopperHouseErrorPopup?.visible && shopperHouseErrorPopup?.type === 'API_FAILURE') {
|
||||
if (
|
||||
shopperHouseErrorPopup?.visible &&
|
||||
shopperHouseErrorPopup?.type === 'API_FAILURE'
|
||||
) {
|
||||
dlog('[SearchPanel] 🧹 API 실패 감지 - fallbackShopperHouseData 초기화');
|
||||
shopperHouseDataRef.current = null;
|
||||
}
|
||||
@@ -307,8 +357,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
/**
|
||||
* useSearchHistory Hook 적용
|
||||
*/
|
||||
const { normalSearches, addNormalSearch, refreshHistory, executeSearchFromHistory } =
|
||||
useSearchHistory();
|
||||
const {
|
||||
normalSearches,
|
||||
addNormalSearch,
|
||||
refreshHistory,
|
||||
executeSearchFromHistory,
|
||||
} = useSearchHistory();
|
||||
|
||||
/**
|
||||
* 🎯 [DetailPanel 복귀 감지] usePanelHistory Hook 적용
|
||||
@@ -422,7 +476,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
// ✨ [Phase 4] Enter/OK 키 처리 - SearchInputOverlay 표시
|
||||
if (e.key === 'Enter' || e.keyCode === 13) {
|
||||
dlog('[DEBUG] [SearchPanel] TInputSimple에서 Enter/OK 키 감지 → SearchInputOverlay 오픈');
|
||||
dlog(
|
||||
'[DEBUG] [SearchPanel] TInputSimple에서 Enter/OK 키 감지 → SearchInputOverlay 오픈'
|
||||
);
|
||||
e.preventDefault();
|
||||
|
||||
// ✨ [Phase 6] SearchInputOverlay 오픈 후 자동으로 입력 준비 완료
|
||||
@@ -450,7 +506,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
];
|
||||
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;
|
||||
}
|
||||
@@ -461,7 +521,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// DOM 쿼리 최적화: 캐싱된 input element 사용
|
||||
const input =
|
||||
inputElementRef.current ||
|
||||
document.querySelector(`[data-spotlight-id="input-field-box"] > input`);
|
||||
document.querySelector(
|
||||
`[data-spotlight-id="input-field-box"] > input`
|
||||
);
|
||||
if (input) {
|
||||
inputElementRef.current = input; // 캐싱
|
||||
if (position === input.value.length) {
|
||||
@@ -658,7 +720,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
'[DEBUG]-VOICE_RESULT: Clearing ShopperHouse data (searchId will be preserved for 2nd search)'
|
||||
);
|
||||
dlog('[VoiceInput]-SearchPanel-onCancel-VOICE_RESULT');
|
||||
dlog('[VoiceInput] 🧹 VOICE_RESULT 모드에서 ESC 누름 - clearShopperHouseData 호출');
|
||||
dlog(
|
||||
'[VoiceInput] 🧹 VOICE_RESULT 모드에서 ESC 누름 - clearShopperHouseData 호출'
|
||||
);
|
||||
}
|
||||
// 🎯 [포커스 로직 통합] 포커스는 상태 변경에 의해 자동으로 처리됨
|
||||
setIsShopperHousePending(false);
|
||||
@@ -816,7 +880,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
if (isReturningFromDetailPanel) {
|
||||
const currentSpot = currentPanel?.panelInfo?.currentSpot;
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[Focus] usePanelHistory로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동');
|
||||
dlog(
|
||||
'[Focus] usePanelHistory로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동'
|
||||
);
|
||||
dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (usePanelHistory)', {
|
||||
currentSpot,
|
||||
mode: currentMode,
|
||||
@@ -860,7 +926,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
) {
|
||||
const usedHistoryOnTop = currentIsOnTop && isOnTopChange?.becameOnTop;
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[Focus] 개선된 방식으로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동');
|
||||
dlog(
|
||||
'[Focus] 개선된 방식으로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동'
|
||||
);
|
||||
dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (improved fallback)', {
|
||||
currentSpot: panelInfo.currentSpot,
|
||||
mode: currentMode,
|
||||
@@ -928,7 +996,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
prevMode: currentModeRef.current,
|
||||
nextMode: currentMode,
|
||||
isOnTopChanged: isOnTop !== isOnTopRef.current,
|
||||
modeChanged: currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT,
|
||||
modeChanged:
|
||||
currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT,
|
||||
dataChanged: shopperHouseDataRef.current !== shopperHouseData,
|
||||
});
|
||||
}
|
||||
@@ -946,7 +1015,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 이렇게 하면 VOICE_OVERLAY_CLOSED 시나리오에서 TInput으로 가는 것을 방지
|
||||
if (shopperHouseData && currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) {
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[FOCUS] 🔄 VOICE_OVERLAY_CLOSED + new data → NEW_SEARCH_LOADED 우선 처리');
|
||||
dlog(
|
||||
'[FOCUS] 🔄 VOICE_OVERLAY_CLOSED + new data → NEW_SEARCH_LOADED 우선 처리'
|
||||
);
|
||||
}
|
||||
return 'NEW_SEARCH_LOADED';
|
||||
}
|
||||
@@ -1009,7 +1080,10 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
let currentSpot = null;
|
||||
|
||||
// 1. usePanelHistory의 currentSpot 우선 사용
|
||||
if (isReturningFromDetailPanel && currentPanel?.panelInfo?.currentSpot) {
|
||||
if (
|
||||
isReturningFromDetailPanel &&
|
||||
currentPanel?.panelInfo?.currentSpot
|
||||
) {
|
||||
currentSpot = currentPanel.panelInfo.currentSpot;
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[FOCUS] 🎯 usePanelHistory currentSpot 사용:', currentSpot);
|
||||
@@ -1019,13 +1093,19 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
else if (panelInfo?.currentSpot) {
|
||||
currentSpot = panelInfo.currentSpot;
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[FOCUS] 🔄 fallback으로 panelInfo.currentSpot 사용:', currentSpot);
|
||||
dlog(
|
||||
'[FOCUS] 🔄 fallback으로 panelInfo.currentSpot 사용:',
|
||||
currentSpot
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSpot && currentSpot.startsWith('searchItemContents')) {
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[FOCUS] 🎯 DETAIL_PANEL_RETURN: 이전 상품으로 포커스 복원:', currentSpot);
|
||||
dlog(
|
||||
'[FOCUS] 🎯 DETAIL_PANEL_RETURN: 이전 상품으로 포커스 복원:',
|
||||
currentSpot
|
||||
);
|
||||
}
|
||||
return currentSpot;
|
||||
} else {
|
||||
@@ -1066,7 +1146,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// SearchInputOverlay에서 검색을 실행하면 isSearchOverlayVisible이 false로 설정되고
|
||||
// 동시에 검색 결과에 따라 모드가 변경되므로, 이 케이스는 검색어 선택 후 닫을 때만 발생
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED - TInputSimple으로 포커스');
|
||||
dlog(
|
||||
'[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED - TInputSimple으로 포커스'
|
||||
);
|
||||
}
|
||||
return SPOTLIGHT_IDS.SEARCH_INPUT_BOX;
|
||||
|
||||
@@ -1290,17 +1372,27 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
{...rest}
|
||||
>
|
||||
<div className={css.productImageWrapper}>
|
||||
<SafeImage src={bgImgPath} alt={curationNm} className={css.productImage} />
|
||||
<SafeImage
|
||||
src={bgImgPath}
|
||||
alt={curationNm}
|
||||
className={css.productImage}
|
||||
/>
|
||||
</div>
|
||||
<div className={css.productInfo}>
|
||||
{showBrandLogo && (
|
||||
<div className={css.productBrandWrapper}>
|
||||
<SafeImage src={patncLogoPath} alt={patncNm} className={css.brandLogo} />
|
||||
<SafeImage
|
||||
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>
|
||||
@@ -1323,7 +1415,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
const renderTsvItem = useCallback(
|
||||
({ index, ...rest }) => {
|
||||
const { offerInfo, prdtId, imgUrl, patnrId, prdtNm, priceInfo } = tsvInfo[index];
|
||||
const { offerInfo, prdtId, imgUrl, patnrId, prdtNm, priceInfo } =
|
||||
tsvInfo[index];
|
||||
|
||||
return (
|
||||
<TItemCardNew
|
||||
@@ -1336,7 +1429,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
priceInfo={priceInfo}
|
||||
productId={prdtId}
|
||||
productName={prdtNm}
|
||||
spotlightId={'searchMain-tsvInfo-spotlightId-' + removeDotAndColon(prdtId)}
|
||||
spotlightId={
|
||||
'searchMain-tsvInfo-spotlightId-' + removeDotAndColon(prdtId)
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
@@ -1603,10 +1698,13 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 우선순위 2: 음성 검색 결과가 있으면 VOICE_RESULT 모드
|
||||
else if (shopperHouseData || isShopperHousePending) {
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[DEBUG]-MODE: shopperHouseData EXISTS or pending → VOICE_RESULT', {
|
||||
dlog(
|
||||
'[DEBUG]-MODE: shopperHouseData EXISTS or pending → VOICE_RESULT',
|
||||
{
|
||||
hasData: !!shopperHouseData,
|
||||
isPending: isShopperHousePending,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
nextMode = SEARCH_PANEL_MODES.VOICE_RESULT;
|
||||
}
|
||||
@@ -1640,7 +1738,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 모드가 변경되었을 때만 업데이트
|
||||
if (nextMode !== currentMode) {
|
||||
if (DEBUG_MODE) {
|
||||
dlog(`[DEBUG]-VOICE_RESULT 🔀 Mode changed: ${currentMode} → ${nextMode}`, {
|
||||
dlog(
|
||||
`[DEBUG]-VOICE_RESULT 🔀 Mode changed: ${currentMode} → ${nextMode}`,
|
||||
{
|
||||
isVoiceOverlayVisible,
|
||||
shopperHouseData: !!shopperHouseData,
|
||||
isShopperHousePending,
|
||||
@@ -1653,7 +1753,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
),
|
||||
isSearchOverlayVisible,
|
||||
inputFocus,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
setCurrentMode(nextMode);
|
||||
} else {
|
||||
@@ -1779,10 +1880,14 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// NEW_SEARCH_LOADED: 음성 검색 결과 로드 시 VoiceInputOverlay와 충돌 방지
|
||||
// 다른 시나리오에서는 기존과 같은 지연 시간 (100ms)
|
||||
const focusDelay =
|
||||
scenario === 'DETAIL_PANEL_RETURN' || scenario === 'NEW_SEARCH_LOADED' ? 50 : 100;
|
||||
scenario === 'DETAIL_PANEL_RETURN' || scenario === 'NEW_SEARCH_LOADED'
|
||||
? 50
|
||||
: 100;
|
||||
|
||||
unifiedFocusTimerRef.current = setTimeout(() => {
|
||||
const targetElement = document.querySelector(`[data-spotlight-id="${targetId}"]`);
|
||||
const targetElement = document.querySelector(
|
||||
`[data-spotlight-id="${targetId}"]`
|
||||
);
|
||||
|
||||
if (targetElement || targetId === SPOTLIGHT_IDS.SEARCH_INPUT_BOX) {
|
||||
Spotlight.focus(targetId);
|
||||
@@ -1805,9 +1910,14 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
}
|
||||
|
||||
// 🎯 DETAIL_PANEL_RETURN에서 요소를 찾지 못하면 fallback으로 첫 번째 상품 시도
|
||||
if (scenario === 'DETAIL_PANEL_RETURN' && targetId.startsWith('searchItemContents')) {
|
||||
if (
|
||||
scenario === 'DETAIL_PANEL_RETURN' &&
|
||||
targetId.startsWith('searchItemContents')
|
||||
) {
|
||||
const fallbackTarget = 'searchItemContents0';
|
||||
const fallbackElement = document.querySelector(`[data-spotlight-id="${fallbackTarget}"]`);
|
||||
const fallbackElement = document.querySelector(
|
||||
`[data-spotlight-id="${fallbackTarget}"]`
|
||||
);
|
||||
if (fallbackElement) {
|
||||
if (DEBUG_MODE) {
|
||||
dlog(
|
||||
@@ -1825,10 +1935,15 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
// 🎯 [NEW_SEARCH_LOADED] 1초 후 다시 첫 번째 아이템으로 포커스 이동
|
||||
// TInputSimple과 Mic Icon의 포커스 충돌 해결을 위해
|
||||
if (scenario === 'NEW_SEARCH_LOADED' && targetId === 'searchItemContents0') {
|
||||
if (
|
||||
scenario === 'NEW_SEARCH_LOADED' &&
|
||||
targetId === 'searchItemContents0'
|
||||
) {
|
||||
setTimeout(() => {
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[FOCUS] 🔄 NEW_SEARCH_LOADED: 1초 후 첫 번째 상품으로 다시 포커스 이동');
|
||||
dlog(
|
||||
'[FOCUS] 🔄 NEW_SEARCH_LOADED: 1초 후 첫 번째 상품으로 다시 포커스 이동'
|
||||
);
|
||||
}
|
||||
Spotlight.focus('searchItemContents0');
|
||||
}, 500); // 0.5초 후
|
||||
@@ -1866,7 +1981,13 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
isVoiceOverlayVisibleRef.current = isVoiceOverlayVisible;
|
||||
isSearchOverlayVisibleRef.current = isSearchOverlayVisible;
|
||||
currentModeRef.current = currentMode;
|
||||
}, [shopperHouseData, searchDatas, isVoiceOverlayVisible, isSearchOverlayVisible, currentMode]);
|
||||
}, [
|
||||
shopperHouseData,
|
||||
searchDatas,
|
||||
isVoiceOverlayVisible,
|
||||
isSearchOverlayVisible,
|
||||
currentMode,
|
||||
]);
|
||||
|
||||
/**
|
||||
* 🎯 SearchInputOverlay 닫힘 후 포커스 관리
|
||||
@@ -1889,10 +2010,13 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
});
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[FOCUS] 🎯 SearchInputOverlay 닫힘 후 포커스 관리 useEffect 실행', {
|
||||
dlog(
|
||||
'[FOCUS] 🎯 SearchInputOverlay 닫힘 후 포커스 관리 useEffect 실행',
|
||||
{
|
||||
shouldFocusSearchInput,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 500ms 후 TInputSimple에 포커스 이동
|
||||
@@ -1903,10 +2027,13 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
});
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[FOCUS] ⏰ 500ms 타이머 콜백 실행 - TInputSimple으로 포커스 이동', {
|
||||
dlog(
|
||||
'[FOCUS] ⏰ 500ms 타이머 콜백 실행 - TInputSimple으로 포커스 이동',
|
||||
{
|
||||
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
dlog('[DEBUG] Spotlight.focus() 호출 직전', {
|
||||
@@ -1950,9 +2077,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
});
|
||||
if (focusTimer) {
|
||||
if (DEBUG_MODE) {
|
||||
dlog('[FOCUS] 🧹 SearchInputOverlay 포커스 관리 useEffect cleanup - 타이머 정리', {
|
||||
dlog(
|
||||
'[FOCUS] 🧹 SearchInputOverlay 포커스 관리 useEffect cleanup - 타이머 정리',
|
||||
{
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
clearTimeout(focusTimer);
|
||||
}
|
||||
@@ -2053,12 +2183,18 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
}, [currentMode, isOnTop, refreshHistory]);
|
||||
|
||||
return (
|
||||
<TPanel className={css.container} handleCancel={onCancel} spotlightId={spotlightId}>
|
||||
<TPanel
|
||||
className={css.container}
|
||||
handleCancel={onCancel}
|
||||
spotlightId={spotlightId}
|
||||
>
|
||||
{/* ✨ [Phase 2] spotlightDisabled를 currentMode로 제어 */}
|
||||
<TBody
|
||||
className={css.tBody}
|
||||
scrollable
|
||||
spotlightDisabled={!isOnTop || currentMode === SEARCH_PANEL_MODES.SEARCH_INPUT}
|
||||
spotlightDisabled={
|
||||
!isOnTop || currentMode === SEARCH_PANEL_MODES.SEARCH_INPUT
|
||||
}
|
||||
>
|
||||
<ContainerBasic>
|
||||
{isOnTop && (
|
||||
@@ -2078,7 +2214,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
className={classNames(
|
||||
css.inputContainer,
|
||||
inputFocus === true && css.inputFocus,
|
||||
searchDatas && css.searchValue /* 이건 결과값 있을때만. 조건 추가필요 */,
|
||||
searchDatas &&
|
||||
css.searchValue /* 이건 결과값 있을때만. 조건 추가필요 */,
|
||||
(currentMode === SEARCH_PANEL_MODES.VOICE_INPUT ||
|
||||
currentMode === SEARCH_PANEL_MODES.INPUT_FOCUSED) &&
|
||||
css.hidden
|
||||
@@ -2129,7 +2266,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
placeholder="Search products or brands"
|
||||
/>
|
||||
</div>
|
||||
<SpottableMicButton
|
||||
{/* <SpottableMicButton
|
||||
className={css.microphoneButton}
|
||||
onClick={onClickMic}
|
||||
onFocus={onFocusMic}
|
||||
@@ -2148,7 +2285,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
<div className={css.microphoneCircle}>
|
||||
<SafeImage src={micIcon} alt="Microphone" className={css.microphoneIcon} />
|
||||
</div>
|
||||
</SpottableMicButton>
|
||||
</SpottableMicButton> */}
|
||||
</div>
|
||||
</InputContainer>
|
||||
|
||||
|
||||
@@ -291,7 +291,7 @@ const TrendingNowPanel = ({ panelInfo, spotlightId, isOnTop, ...rest }) => {
|
||||
|
||||
return (
|
||||
<div className={css.trendingNowWrap}>
|
||||
{selectedIndex >= 1 && showButton && (
|
||||
{isOnTop && selectedIndex >= 1 && showButton && (
|
||||
<TButton
|
||||
className={classNames(css.button, css.prevBtn)}
|
||||
onClick={handleIndicatorClick("prev")}
|
||||
@@ -411,7 +411,7 @@ const TrendingNowPanel = ({ panelInfo, spotlightId, isOnTop, ...rest }) => {
|
||||
</TVerticalPagenator>
|
||||
</TBody>
|
||||
</TPanel>
|
||||
{topInfos &&
|
||||
{isOnTop && topInfos &&
|
||||
topInfos?.length > 0 &&
|
||||
selectedIndex !== topInfos?.length - 1 &&
|
||||
showButton && (
|
||||
|
||||
Reference in New Issue
Block a user