[251216] fix: TrendingNowPanel DetailPanel MediaPanel popPanel fix

🕐 커밋 시간: 2025. 12. 16. 15:21:13

📊 변경 통계:
  • 총 파일: 6개
  • 추가: +62줄
  • 삭제: -14줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/mediaActions.js
  ~ com.twin.app.shoptime/src/actions/panelActions.js
  ~ com.twin.app.shoptime/src/reducers/panelReducer.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 소규모 기능 개선
  • 모듈 구조 개선
This commit is contained in:
2025-12-16 15:21:13 +09:00
parent 4f4887ebdb
commit c9b2e5daf5
6 changed files with 62 additions and 14 deletions

View File

@@ -99,17 +99,25 @@ export const finishMediaPreview = () => (dispatch, getState) => {
export const finishModalMediaForce = () => (dispatch, getState) => { export const finishModalMediaForce = () => (dispatch, getState) => {
const panels = getState().panels.panels; 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( const hasProductVideoPanel = panels.some(
(panel) => (panel) =>
panel.name === panel_names.MEDIA_PANEL && panel.name === panel_names.MEDIA_PANEL &&
(panel.panelInfo?.modal || panel.panelInfo?.modalContainerId === 'product-video-player') (panel.panelInfo?.modal || panel.panelInfo?.modalContainerId === 'product-video-player')
); );
console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] hasProductVideoPanel:', hasProductVideoPanel);
if (hasProductVideoPanel) { if (hasProductVideoPanel) {
if (startMediaFocusTimer) { if (startMediaFocusTimer) {
clearTimeout(startMediaFocusTimer); clearTimeout(startMediaFocusTimer);
startMediaFocusTimer = null; startMediaFocusTimer = null;
} }
console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] Calling popPanel(panel_names.MEDIA_PANEL)');
dispatch(popPanel(panel_names.MEDIA_PANEL)); dispatch(popPanel(panel_names.MEDIA_PANEL));
} }
}; };

View File

@@ -40,12 +40,20 @@ export const pushPanel = (panel, duplicatable = false) => ({
}); });
export const popPanel = (panelName) => { 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) { if (DEBUG_MODE) {
console.log('[PANEL-TRACE] popPanel action creator', { console.log('[💜UNIQUE_PANEL_STACK💜] popPanel action creator stack:', {
panelName, panelName,
caller: new Error().stack?.split('\n')[2]?.trim(), caller: stackLines[2]?.trim(),
}); });
console.trace('[PANEL-TRACE] popPanel stack trace');
} }
return { return {
type: types.POP_PANEL, type: types.POP_PANEL,

View File

@@ -94,6 +94,12 @@ export const panelsReducer = (state = initialState, action) => {
} }
case types.POP_PANEL: { 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', { dlog('[panelReducer] 🔴 POP_PANEL START', {
targetPanel: action.payload || 'last_panel', targetPanel: action.payload || 'last_panel',
currentPanels: state.panels.map((p) => p.name), 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); 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', { dlog('[panelReducer] 🔴 POP_PANEL END', {
resultPanels: resultPanels.map((p) => p.name), resultPanels: resultPanels.map((p) => p.name),
lastAction, lastAction,

View File

@@ -280,6 +280,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
useEffect(() => { useEffect(() => {
return () => { return () => {
console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] DetailPanel cleanup - calling finishModalMediaForce');
dispatch(finishModalMediaForce()); dispatch(finishModalMediaForce());
}; };
}, [dispatch]); }, [dispatch]);
@@ -303,16 +304,15 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const sourcePanel = panelInfo?.sourcePanel; const sourcePanel = panelInfo?.sourcePanel;
const sourceMenu = panelInfo?.sourceMenu; const sourceMenu = panelInfo?.sourceMenu;
console.log('[DP-TRACE] Detail unmount start', { console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel cleanup/unmount triggered', {
sourcePanel, sourcePanel,
sourceMenu, sourceMenu,
panelsSnapshot: panels.map((p) => p.name), panelsSnapshot: panels.map((p) => p.name),
timestamp: Date.now(),
}); });
console.log('[Detail-BG] 306-line sourcePanel:', sourcePanel, 'sourceMenu:', sourceMenu);
// DetailPanel이 unmount되는 시점 // DetailPanel이 unmount되는 시점
console.log('[DetailPanel] unmount:', { console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel unmount details:', {
sourcePanel, sourcePanel,
sourceMenu, sourceMenu,
timestamp: Date.now(), timestamp: Date.now(),
@@ -385,6 +385,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const sourcePanel = panelInfo?.sourcePanel; const sourcePanel = panelInfo?.sourcePanel;
const sourceMenu = panelInfo?.sourceMenu; 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( fp.pipe(
() => { () => {
dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거 dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거
@@ -393,7 +401,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
switch (sourcePanel) { switch (sourcePanel) {
case panel_names.PLAYER_PANEL: case panel_names.PLAYER_PANEL:
// PlayerPanel에서 온 경우: 플레이어 비디오는 그대로 두고 모달만 정리 // PlayerPanel에서 온 경우: 플레이어 비디오는 그대로 두고 모달만 정리
console.log('[DetailPanel] onBackClick - PlayerPanel 출신: 모달 정리만 수행'); console.log('[🟠UNIQUE_DETAIL_BACK🟠] PlayerPanel 출신: 모달 정리만 수행');
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료 dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
dispatch(finishVideoPreview()); dispatch(finishVideoPreview());
break; break;
@@ -403,7 +411,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
default: default:
// HomePanel, SearchPanel 등에서 온 경우: 백그라운드 비디오 일시 중지 // HomePanel, SearchPanel 등에서 온 경우: 백그라운드 비디오 일시 중지
console.log( console.log(
'[DetailPanel] onBackClick - source panel:', '[🟠UNIQUE_DETAIL_BACK🟠] source panel:',
sourcePanel, sourcePanel,
'백그라운드 비디오 일시 중지' '백그라운드 비디오 일시 중지'
); );
@@ -413,6 +421,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
break; break;
} }
console.log('[🟠UNIQUE_DETAIL_BACK🟠] Calling popPanel(DETAIL_PANEL)');
dispatch(popPanel(panel_names.DETAIL_PANEL)); dispatch(popPanel(panel_names.DETAIL_PANEL));
}, },
() => { () => {

View File

@@ -391,6 +391,7 @@ const MediaPanel = React.forwardRef(
const onEnded = useCallback( const onEnded = useCallback(
(e) => { (e) => {
debugLog('[MediaPanel] Video ended'); debugLog('[MediaPanel] Video ended');
console.log('[🔥UNIQUE_MEDIA_ENDED🔥] MediaPanel onEnded triggered - will pop after 1500ms');
// continuousPlay는 MediaPlayer(VideoPlayer) 컴포넌트 내부에서 loop 속성으로 처리 // continuousPlay는 MediaPlayer(VideoPlayer) 컴포넌트 내부에서 loop 속성으로 처리
// onEnded가 호출되면 loop=false 인 경우이므로 패널을 닫음 // onEnded가 호출되면 loop=false 인 경우이므로 패널을 닫음
Spotlight.pause(); Spotlight.pause();
@@ -402,6 +403,7 @@ const MediaPanel = React.forwardRef(
// ✅ 새로운 타이머 저장 (cleanup 시 정리용) // ✅ 새로운 타이머 저장 (cleanup 시 정리용)
onEndedTimerRef.current = setTimeout(() => { onEndedTimerRef.current = setTimeout(() => {
console.log('[🔥UNIQUE_MEDIA_ENDED🔥] Executing popPanel(MEDIA_PANEL) after 1500ms');
Spotlight.resume(); Spotlight.resume();
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL)); dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
onEndedTimerRef.current = null; onEndedTimerRef.current = null;

View File

@@ -912,7 +912,7 @@ const MediaPanel = React.forwardRef(
} }
if (!panelInfo.modal) { if (!panelInfo.modal) {
dispatch(PanelActions.popPanel()); dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
dispatch(changeAppStatus({ cursorVisible: false })); dispatch(changeAppStatus({ cursorVisible: false }));
document?.dispatchEvent?.(new CustomEvent('detailpanel-scroll-reset')); document?.dispatchEvent?.(new CustomEvent('detailpanel-scroll-reset'));
@@ -937,7 +937,7 @@ const MediaPanel = React.forwardRef(
// 패널이 2개 존재할때만 popPanel 진행 // 패널이 2개 존재할때만 popPanel 진행
// fullscreen 전환 중이면 popPanel하지 않음 // fullscreen 전환 중이면 popPanel하지 않음
if (panelInfo.modal && !isOnTop && !isTransitioningToFullscreen.current) { if (panelInfo.modal && !isOnTop && !isTransitioningToFullscreen.current) {
dispatch(PanelActions.popPanel()); dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
} else { } else {
Spotlight.focus('tbody'); Spotlight.focus('tbody');
} }
@@ -1576,7 +1576,7 @@ const MediaPanel = React.forwardRef(
) { ) {
// case: Featured Brands // case: Featured Brands
if (panelInfo?.sourcePanel === panel_names.FEATURED_BRANDS_PANEL) { 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) => { (e) => {
const currentInfo = panelInfoRef.current; const currentInfo = panelInfoRef.current;
console.log('[🔥UNIQUE_MEDIA_ENDED🔥] onEnded triggered - shptmBanrTpNm:', currentInfo?.shptmBanrTpNm);
// MEDIA: 기존 동작 유지 (배경 복원 없이 즉시 pop) // MEDIA: 기존 동작 유지 (배경 복원 없이 즉시 pop)
if (currentInfo.shptmBanrTpNm === 'MEDIA') { 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(); Spotlight.pause();
setTimeout(() => { setTimeout(() => {
console.log('[🔥UNIQUE_MEDIA_ENDED🔥] setTimeout fired - dispatching popPanel(MEDIA_PANEL)');
Spotlight.resume(); Spotlight.resume();
dispatch(PanelActions.popPanel()); dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
}, VIDEO_END_ACTION_DELAY); }, VIDEO_END_ACTION_DELAY);
e?.stopPropagation();
e?.preventDefault();
return; return;
} }
// VOD: modal 여부에 따라 동작 분리 // VOD: modal 여부에 따라 동작 분리
if (currentInfo.shptmBanrTpNm === 'VOD') { if (currentInfo.shptmBanrTpNm === 'VOD') {
console.log('[🔥UNIQUE_MEDIA_ENDED🔥] VOD type - popPanel will be called');
Spotlight.pause(); Spotlight.pause();
setTimeout(() => { setTimeout(() => {
stopExternalPlayer(); stopExternalPlayer();
@@ -2074,6 +2080,8 @@ const MediaPanel = React.forwardRef(
e?.preventDefault(); e?.preventDefault();
return; return;
} }
console.log('[🔥UNIQUE_MEDIA_ENDED🔥] Unknown shptmBanrTpNm - no action taken');
}, },
[dispatch, focusBackButtonOrFallback, stopExternalPlayer] [dispatch, focusBackButtonOrFallback, stopExternalPlayer]
); );