From ac5414a5fed48b462387e01f60815f8149821ca2 Mon Sep 17 00:00:00 2001 From: optrader Date: Mon, 15 Dec 2025 12:38:20 +0900 Subject: [PATCH] fix: DetailPanel above PlayerPanel , FeaturedBrandsPanel --- .../src/actions/panelActions.js | 34 ++++++++++++++++--- .../src/actions/playActions.js | 6 ++++ .../src/middleware/panelHistoryMiddleware.js | 8 +++++ .../src/views/DetailPanel/DetailPanel.jsx | 25 +++++++++----- .../src/views/MainView/MainView.jsx | 34 ++++++++----------- .../src/views/PlayerPanel/PlayerPanel.jsx | 19 +++++++++-- 6 files changed, 91 insertions(+), 35 deletions(-) diff --git a/com.twin.app.shoptime/src/actions/panelActions.js b/com.twin.app.shoptime/src/actions/panelActions.js index cfe19ad7..6706a6f8 100644 --- a/com.twin.app.shoptime/src/actions/panelActions.js +++ b/com.twin.app.shoptime/src/actions/panelActions.js @@ -6,7 +6,7 @@ import { updateHomeInfo } from './homeActions'; import { createDebugHelpers } from '../utils/debug'; // 디버그 헬퍼 설정 -const DEBUG_MODE = false; +const DEBUG_MODE = true; const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE); // 시작 메뉴 추적을 위한 상수 @@ -39,10 +39,19 @@ export const pushPanel = (panel, duplicatable = false) => ({ duplicatable: duplicatable, }); -export const popPanel = (panelName) => ({ - type: types.POP_PANEL, - payload: panelName, -}); +export const popPanel = (panelName) => { + if (DEBUG_MODE) { + console.log('[PANEL-TRACE] popPanel action creator', { + panelName, + caller: new Error().stack?.split('\n')[2]?.trim(), + }); + console.trace('[PANEL-TRACE] popPanel stack trace'); + } + return { + type: types.POP_PANEL, + payload: panelName, + }; +}; export const updatePanel = (panelInfo) => ({ type: types.UPDATE_PANEL, @@ -289,10 +298,25 @@ export const navigateToDetail = ({ case SOURCE_MENUS.PLAYER_MEDIA: { // PlayerPanel에서 온 경우 const { hidePlayerOverlays } = require('./videoPlayActions'); + const statePanels = panels || getState().panels.panels; + const playerPanelEntry = statePanels.find((p) => p.name === panel_names.PLAYER_PANEL); // DetailPanel push 전에 VideoPlayer 오버레이 숨김 dispatch(hidePlayerOverlays()); + // PlayerPanel이 modal=true라면 풀스크린 백그라운드로 전환 + if (playerPanelEntry?.panelInfo?.modal) { + dispatch( + updatePanel({ + name: panel_names.PLAYER_PANEL, + panelInfo: { + ...playerPanelEntry.panelInfo, + modal: false, + }, + }) + ); + } + // 현재 포커스된 요소 저장 if (Object.keys(focusSnapshot).length > 0) { panelInfo.lastFocusedTargetId = focusSnapshot.lastFocusedTargetId; diff --git a/com.twin.app.shoptime/src/actions/playActions.js b/com.twin.app.shoptime/src/actions/playActions.js index 6a7d005c..5a377615 100644 --- a/com.twin.app.shoptime/src/actions/playActions.js +++ b/com.twin.app.shoptime/src/actions/playActions.js @@ -325,6 +325,12 @@ 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', { + topPanelName: topPanel.name, + modal: topPanel.panelInfo.modal, + stack: panels.map((p) => p.name), + panelInfo: topPanel.panelInfo, + }); if (startVideoFocusTimer) { clearTimeout(startVideoFocusTimer); startVideoFocusTimer = null; diff --git a/com.twin.app.shoptime/src/middleware/panelHistoryMiddleware.js b/com.twin.app.shoptime/src/middleware/panelHistoryMiddleware.js index d7c05ea8..29c1ff65 100644 --- a/com.twin.app.shoptime/src/middleware/panelHistoryMiddleware.js +++ b/com.twin.app.shoptime/src/middleware/panelHistoryMiddleware.js @@ -125,6 +125,14 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => { // POP 후 top panel을 기록 (이전 패널로 돌아감) if (panels.length > 0) { const topPanel = panels[panels.length - 1]; + if (DEBUG_MODE) { + console.log('[PANEL-TRACE] POP_PANEL middleware stack', { + stack: panels.map((p) => p.name), + topPanel: topPanel?.name, + payload: action.payload, + caller: new Error().stack?.split('\n')[2]?.trim(), + }); + } if (topPanel && topPanel.name) { const isGNB = isGNBCall(); const isOnTop = calculateIsOnTop(topPanel.name); // 🎯 isOnTop 계산 diff --git a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx index 7923c89f..78034238 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx @@ -303,6 +303,12 @@ 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); // DetailPanel이 unmount되는 시점 @@ -1051,11 +1057,11 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { } else { console.log('[Detail-BG] ⏭️ DetailPanel - Skipping pause (no playerPanel or no productVideo)', { hasPlayerPanel, - hasProductVideo, - reason: !hasPlayerPanel ? 'no playerPanel' : 'no productVideo', - timestamp: Date.now(), - }); - } + hasProductVideo, + reason: !hasPlayerPanel ? 'no playerPanel' : 'no productVideo', + timestamp: Date.now(), + }); + } return () => { // DetailPanel 언마운트 시: 비디오가 있었고 멈췄던 경우만 재생 재개 @@ -1104,12 +1110,15 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const hasPlayerPanel = panels.some( (panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal === true ); + const launchedFromPlayer = panelInfo?.fromPlayer || panelInfo?.sourcePanel === panel_names.PLAYER_PANEL; - if (hasPlayerPanel) { - console.log('[DetailPanel] PlayerPanel modal=true detected - stopping video preview'); + if (hasPlayerPanel && !launchedFromPlayer) { + console.log('[DetailPanel] PlayerPanel modal=true detected - stopping video preview (non-player source)'); dispatch(finishVideoPreview()); + } else if (hasPlayerPanel && launchedFromPlayer) { + console.log('[DetailPanel] PlayerPanel modal=true detected - launched from Player, skip finishVideoPreview'); } - }, [panels, dispatch]); + }, [panels, dispatch, panelInfo?.fromPlayer, panelInfo?.sourcePanel]); return (
diff --git a/com.twin.app.shoptime/src/views/MainView/MainView.jsx b/com.twin.app.shoptime/src/views/MainView/MainView.jsx index c417444b..44a00a4e 100644 --- a/com.twin.app.shoptime/src/views/MainView/MainView.jsx +++ b/com.twin.app.shoptime/src/views/MainView/MainView.jsx @@ -206,7 +206,9 @@ export default function MainView({ className, initService }) { let renderingPanels = []; const topPanel = panels[panels.length - 1]; - + const hasFeaturedBrandsPanel = panels.some( + (panel) => panel?.name === Config.panel_names.FEATURED_BRANDS_PANEL + ); // 단독 패널 체크 - CheckOutPanel, CartPanel 등 단독으로 렌더링되어야 하는 패널들 if (DEBUG_MODE) { console.log(`[PANEL_MainView] 🔍 Top panel name: ${topPanel?.name}`); @@ -248,7 +250,7 @@ export default function MainView({ className, initService }) { '[MainView] Rendering 3-layer structure: PlayerPanel + DetailPanel + MediaPanel' ); } - renderingPanels = panels.slice(-3); + renderingPanels = hasFeaturedBrandsPanel ? panels.slice(-4) : 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 || @@ -256,18 +258,17 @@ 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 = panels.slice(-2); - // const hasFeaturedBrandsPanel = panels.some(p => p.name === Config.panel_names.FEATURED_BRANDS_PANEL); - // if (hasFeaturedBrandsPanel) { - // renderingPanels = panels.slice(-3); // 3개 다 렌더링 - // } else { - // renderingPanels = panels.slice(-2); // 2개만 - // } + renderingPanels = hasFeaturedBrandsPanel ? panels.slice(-3) : panels.slice(-2); } else { renderingPanels = panels.slice(-1); } } + // DetailPanel 위치 확인 (있으면 항상 onTop 처리) + const detailPanelIndex = renderingPanels.findIndex( + (panel) => panel.name === Config.panel_names.DETAIL_PANEL + ); + return ( <> {(isHomeOnTop || @@ -293,17 +294,12 @@ export default function MainView({ className, initService }) { console.log(`[MainView] Standalone panel ${panel.name} is always onTop`); } } - // 3-layer 케이스: 중간 패널(DetailPanel)이 onTop - else if (renderingPanels.length === 3) { - if (index === 1) { - // DetailPanel (중간) - isPanelOnTop = true; - if (DEBUG_MODE) { - console.log('[MainView] 3-layer: DetailPanel is onTop'); - } + // DetailPanel이 포함되어 있으면 항상 onTop + else if (detailPanelIndex >= 0) { + isPanelOnTop = index === detailPanelIndex; + if (DEBUG_MODE && isPanelOnTop) { + console.log('[MainView] DetailPanel set to onTop'); } - // PlayerPanel (index 0): isOnTop = false (백그라운드) - // MediaPanel (index 2): isOnTop = false (modal overlay) } // 2-layer 케이스: modal이면 첫 번째가 onTop else if ( diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx index bf1b9540..8523b768 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx @@ -1192,14 +1192,27 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props //todo if(modal) return () => { // 패널이 2개 존재할때만 popPanel 진행 - if (panelInfo.modal && !isOnTop) { - console.log('[PlayerPanel] popPanel - useEffect cleanup'); + // 현재 스택의 top이 PlayerPanel일 때만 pop 수행 (다른 패널이 올라온 상태에서 오작동 방지) + console.log('[PP-TRACE] cleanup start', { + modal: panelInfo.modal, + isOnTop, + topPanel: panels[panels.length - 1]?.name, + stack: panels.map((p) => p.name), + }); + const topPanelName = panels[panels.length - 1]?.name; + if ( + panelInfo.modal && + !isOnTop && + topPanelName === panel_names.PLAYER_PANEL && + panels.length === 1 // 다른 패널 존재 시 pop 금지 (DetailPanel 제거 방지) + ) { + console.log('[PP-TRACE] popPanel - useEffect cleanup (top is PlayerPanel)'); dispatch(PanelActions.popPanel()); } else { Spotlight.focus('tbody'); } }; - }, [panelInfo?.modal, isOnTop]); + }, [panelInfo?.modal, isOnTop, panels]); useEffect(() => { if (showNowInfos && panelInfo.shptmBanrTpNm === 'LIVE') {