[251115] fix: DetailPanel FullScreen Focus Move

🕐 커밋 시간: 2025. 11. 15. 22:03:44

📊 변경 통계:
  • 총 파일: 17개
  • 추가: +573줄
  • 삭제: -87줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/MediaOverlayContents.jsx

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/panelActions.js
  ~ com.twin.app.shoptime/src/components/MediaPlayer/MediaControls.js
  ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.module.less
  ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx
  ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.js
  ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.module.less
  ~ com.twin.app.shoptime/src/utils/SpotlightIds.js
  ~ com.twin.app.shoptime/src/views/HomePanel/BestSeller/BestSeller.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/EventPopUpBanner/EventPopUpBanner.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/PickedForYou/PickedForYou.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/SubCategory/SubCategory.jsx
  ~ com.twin.app.shoptime/src/views/MainView/MainView.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx
  ~ com.twin.app.shoptime/src/views/SearchPanel/SearchResultsNew/ItemCard.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/actions/panelActions.js (javascript):
     Added: updatePanel()
  📄 com.twin.app.shoptime/src/components/MediaPlayer/MediaControls.js (javascript):
     Added: onSpotlightRight(), onSpotlightUp(), MediaControlsDecoratorHOC(), handleCancel()
     Deleted: onSpotlightRight(), onSpotlightUp(), MediaControlsDecoratorHOC(), handleCancel()
  📄 com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx (javascript):
    🔄 Modified: SpotlightContainerDecorator()
  📄 com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.module.less (unknown):
     Added: position()
     Deleted: position()
  📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx (javascript):
    🔄 Modified: SpotlightContainerDecorator()
  📄 com.twin.app.shoptime/src/views/HomePanel/SubCategory/SubCategory.jsx (javascript):
    🔄 Modified: getExpsOrdByLgCatCd()
  📄 com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx (javascript):
    🔄 Modified: normalizeModalStyle()
  📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx (javascript):
    🔄 Modified: SpotlightContainerDecorator(), PlayerOverlayContents()
  📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/MediaOverlayContents.jsx (javascript):
     Added: MediaOverlayContents()

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • UI 컴포넌트 아키텍처 개선
  • 공통 유틸리티 함수 최적화
This commit is contained in:
2025-11-15 22:03:44 +09:00
parent 70381438ac
commit a503bf923a
17 changed files with 1878 additions and 1492 deletions

View File

@@ -11,6 +11,8 @@ export const SOURCE_MENUS = {
HOME_ROLLING_UNIT: 'home_rolling_unit', HOME_ROLLING_UNIT: 'home_rolling_unit',
HOME_EVENT_POPUP: 'home_event_popup', HOME_EVENT_POPUP: 'home_event_popup',
HOME_TODAYS_DEAL: 'home_todays_deal', HOME_TODAYS_DEAL: 'home_todays_deal',
SEARCH_RESULT: 'search_result',
HOME_GENERAL: 'home_general',
THEMED_PRODUCT: 'themed_product', THEMED_PRODUCT: 'themed_product',
GENERAL_PRODUCT: 'general_product', GENERAL_PRODUCT: 'general_product',
}; };
@@ -43,6 +45,417 @@ export const resetPanels = (panels) => ({
payload: panels, payload: panels,
}); });
/**
* DetailPanel로 이동하는 공통 액션 함수
* @param {Object} params - 이동 파라미터
* @param {string} params.patnrId - 파트너 ID
* @param {string} params.prdtId - 상품 ID
* @param {string} [params.curationId] - 큐레이션 ID (테마 상품인 경우)
* @param {string} [params.nowShelf] - 현재 셸프 ID
* @param {string} [params.type] - 상품 타입 ('theme' 등)
* @param {string} [params.sourceMenu] - 시작 메뉴 (SOURCE_MENUS 상수 사용)
* @param {Object} [params.additionalInfo] - 추가 정보
* @returns {Function} Redux thunk 함수
*/
export const navigateToDetail = ({
patnrId,
prdtId,
curationId,
nowShelf,
type,
sourceMenu,
additionalInfo = {},
}) => {
return (dispatch, getState) => {
const panelInfo = {
patnrId,
prdtId,
...additionalInfo,
};
// 선택적 파라미터들 추가
if (curationId) panelInfo.curationId = curationId;
if (nowShelf) panelInfo.nowShelf = nowShelf;
if (type) panelInfo.type = type;
if (sourceMenu) panelInfo.sourceMenu = sourceMenu;
// 로깅
console.log(`[navigateToDetail] ${sourceMenu || 'unknown'} → DetailPanel`, {
patnrId,
prdtId,
curationId,
nowShelf,
type,
sourceMenu,
timestamp: Date.now(),
});
// sourceMenu에 따른 사전 처리
switch (sourceMenu) {
case SOURCE_MENUS.HOME_BEST_SELLER:
case SOURCE_MENUS.HOME_PICKED_FOR_YOU:
case SOURCE_MENUS.HOME_SUB_CATEGORY:
case SOURCE_MENUS.HOME_EVENT_POPUP:
case SOURCE_MENUS.HOME_TODAYS_DEAL:
case SOURCE_MENUS.HOME_RANDOM_UNIT:
case SOURCE_MENUS.HOME_ROLLING_UNIT:
case SOURCE_MENUS.HOME_GENERAL: {
// 🔽 모든 HomePanel에서 DetailPanel로 이동 시 HomeBanner modal 비디오 정지
const state = getState();
const playerPanelInfo = state.panels.panels.find(
(p) => p.name === panel_names.PLAYER_PANEL
);
// playerPanel이 없는 경우 비디오 정지 로직 건너뛰기
if (!playerPanelInfo) {
// 비디오가 없어도 HomePanel 상태 저장
dispatch(
updatePanel({
name: panel_names.HOME_PANEL,
panelInfo: {
lastSelectedProduct: { patnrId, prdtId },
lastActionSource: sourceMenu,
...additionalInfo,
},
})
);
panelInfo.fromHome = true;
break;
}
const isCurrentBannerVideoPlaying = playerPanelInfo.panelInfo?.modal !== false;
// HomeBanner의 modal=true 비디오가 재생 중이면 정지
if (isCurrentBannerVideoPlaying) {
// 🔽 비디오 상태 저장 후 정지
const { finishVideoPreview } = require('./playActions');
// 비디오 복원을 위한 상태 저장
const videoStateToRestore = {
...playerPanelInfo.panelInfo,
wasPlaying: true,
restoreOnBack: true,
sourceMenu,
timestamp: Date.now(),
};
// HomePanel에 비디오 복원 상태 저장
dispatch(
updatePanel({
name: panel_names.HOME_PANEL,
panelInfo: {
videoStateToRestore,
lastSelectedProduct: { patnrId, prdtId },
lastActionSource: sourceMenu,
...additionalInfo,
},
})
);
// 비디오 상태 저장 후 정지 (로그는 개발 시 필요 시 주석 해제)
dispatch(finishVideoPreview());
} else {
// 비디오가 재생 중이 아니어도 HomePanel 상태 저장
dispatch(
updatePanel({
name: panel_names.HOME_PANEL,
panelInfo: {
lastSelectedProduct: { patnrId, prdtId },
lastActionSource: sourceMenu,
...additionalInfo,
},
})
);
}
// HomePanel 내부 컴포넌트들: 기본 HomePanel 상태 저장
dispatch(
updatePanel({
name: panel_names.HOME_PANEL,
panelInfo: {
lastSelectedProduct: { patnrId, prdtId },
lastActionSource: sourceMenu,
...additionalInfo,
},
})
);
panelInfo.fromHome = true;
break;
}
case SOURCE_MENUS.SEARCH_RESULT:
// Search: 현재 패널 상태 저장 (updatePanel)
if (additionalInfo.searchVal && additionalInfo.currentSpot) {
dispatch(
updatePanel({
name: panel_names.SEARCH_PANEL,
panelInfo: {
searchVal: additionalInfo.searchVal,
currentSpot: additionalInfo.currentSpot,
tab: additionalInfo.tab || 0,
},
})
);
}
panelInfo.fromSearch = true;
panelInfo.searchQuery = additionalInfo.searchVal;
break;
case SOURCE_MENUS.THEMED_PRODUCT:
// 테마 상품: 별도 처리 필요할 경우
break;
case SOURCE_MENUS.GENERAL_PRODUCT:
default:
// 일반 상품: 기본 처리
break;
}
// DetailPanel push
dispatch(
pushPanel({
name: panel_names.DETAIL_PANEL,
panelInfo,
})
);
};
};
/**
* 테마 상품을 위한 DetailPanel 이동 헬퍼 함수
* @param {Object} params - 이동 파라미터
* @returns {Function} Redux thunk
*/
export const navigateToThemeDetail = ({
patnrId,
prdtId,
curationId,
sourceMenu = SOURCE_MENUS.THEMED_PRODUCT,
...additionalInfo
}) => {
return navigateToDetail({
patnrId,
prdtId,
curationId,
type: 'theme',
sourceMenu,
...additionalInfo,
});
};
/**
* 홈패널 BestSeller에서 DetailPanel로 이동
* @param {Object} params - 상품 정보
* @returns {Function} Redux thunk
*/
export const navigateFromBestSeller = ({ patnrId, prdtId, spotlightId }) => {
return navigateToDetail({
patnrId,
prdtId,
nowShelf: spotlightId,
sourceMenu: SOURCE_MENUS.HOME_BEST_SELLER,
});
};
/**
* 홈패널 PickedForYou에서 DetailPanel로 이동
* @param {Object} params - 상품 정보
* @returns {Function} Redux thunk
*/
export const navigateFromPickedForYou = ({ patnrId, prdtId, spotlightId }) => {
return navigateToDetail({
patnrId,
prdtId,
nowShelf: spotlightId,
sourceMenu: SOURCE_MENUS.HOME_PICKED_FOR_YOU,
});
};
/**
* 홈패널 SubCategory에서 DetailPanel로 이동
* @param {Object} params - 상품 정보
* @returns {Function} Redux thunk
*/
export const navigateFromSubCategory = ({ patnrId, prdtId, spotlightId }) => {
return navigateToDetail({
patnrId,
prdtId,
nowShelf: spotlightId,
sourceMenu: SOURCE_MENUS.HOME_SUB_CATEGORY,
});
};
/**
* 홈패널 RandomUnit 배너에서 DetailPanel로 이동
* @param {Object} params - 상품 정보
* @returns {Function} Redux thunk
*/
export const navigateFromRandomUnit = ({ patnrId, prdtId, curationId, type = 'product' }) => {
return navigateToDetail({
patnrId,
prdtId,
curationId,
type: type === 'theme' ? 'theme' : undefined,
sourceMenu: SOURCE_MENUS.HOME_RANDOM_UNIT,
});
};
/**
* 홈패널 RollingUnit 배너에서 DetailPanel로 이동
* @param {Object} params - 상품 정보
* @returns {Function} Redux thunk
*/
export const navigateFromRollingUnit = ({ patnrId, prdtId, curationId, additionalInfo = {} }) => {
return navigateToDetail({
patnrId,
prdtId,
curationId,
sourceMenu: SOURCE_MENUS.HOME_ROLLING_UNIT,
...additionalInfo,
});
};
/**
* 홈패널 EventPopUpBanner에서 DetailPanel로 이동
* @param {Object} params - 상품 정보
* @returns {Function} Redux thunk
*/
export const navigateFromEventPopup = ({ patnrId, prdtId }) => {
return navigateToDetail({
patnrId,
prdtId,
sourceMenu: SOURCE_MENUS.HOME_EVENT_POPUP,
});
};
/**
* SearchPanel에서 DetailPanel로 이동
* @param {Object} params - 검색 및 상품 정보
* @returns {Function} Redux thunk
*/
export const navigateFromSearch = ({
patnrId,
prdtId,
searchQuery,
currentSpot,
additionalInfo = {},
}) => {
return navigateToDetail({
patnrId,
prdtId,
sourceMenu: SOURCE_MENUS.SEARCH_RESULT,
additionalInfo: {
searchVal: searchQuery,
currentSpot,
tab: 0,
...additionalInfo,
},
});
};
/**
* HomePanel 일반 클릭에서 DetailPanel로 이동
* @param {Object} params - 상품 정보
* @returns {Function} Redux thunk
*/
export const navigateFromHomeGeneral = ({ patnrId, prdtId, additionalInfo = {} }) => {
return navigateToDetail({
patnrId,
prdtId,
sourceMenu: SOURCE_MENUS.HOME_GENERAL,
additionalInfo,
});
};
/**
* DetailPanel에서 돌아올 때 비디오 복원 함수
* HomePanel에 저장된 비디오 상태를 확인하고 복원
* @returns {Function} Redux thunk
*/
export const restoreVideoOnBack = () => {
return (dispatch, getState) => {
const state = getState();
const panels = state.panels.panels;
// HomePanel 찾기
const homePanel = panels.find((p) => p.name === panel_names.HOME_PANEL);
const videoStateToRestore = homePanel?.panelInfo?.videoStateToRestore;
if (!videoStateToRestore || !videoStateToRestore.restoreOnBack) {
return;
}
// 비디오 복원 시작 (로그는 개발 시 필요 시 주석 해제)
// 비디오 상태 복원
const { startVideoPlayerNew } = require('./playActions');
// 복원할 비디오 정보 추출
const restoreInfo = {
bannerId: videoStateToRestore.bannerId || videoStateToRestore.playerState?.currentBannerId,
patnrId: videoStateToRestore.patnrId,
showId: videoStateToRestore.showId,
shptmBanrTpNm: videoStateToRestore.shptmBanrTpNm,
lgCatCd: videoStateToRestore.lgCatCd,
modal: true, // HomeBanner는 항상 modal
modalContainerId: videoStateToRestore.modalContainerId,
modalClassName: videoStateToRestore.modalClassName,
chanId: videoStateToRestore.chanId,
};
// 비디오 재생 시작
dispatch(
startVideoPlayerNew({
...restoreInfo,
spotlightDisable: false,
})
);
// 복원 상태 정리
dispatch(
updatePanel({
name: panel_names.HOME_PANEL,
panelInfo: {
...homePanel.panelInfo,
videoStateToRestore: {
...videoStateToRestore,
restoreOnBack: false, // 복원 완료 후 플래그 초기화
},
},
})
);
};
};
/**
* DetailPanel 닫기 시 비디오 복원 확인 함수
* DetailPanel 패널이 제거될 때 자동으로 비디오 복원 시도
* @returns {Function} Redux thunk
*/
export const handleDetailPanelCloseWithVideoRestore = () => {
return (dispatch, getState) => {
const state = getState();
const panels = state.panels.panels;
// 현재 최상단 패널이 DetailPanel인지 확인
const topPanel = panels[panels.length - 1];
if (topPanel?.name === panel_names.DETAIL_PANEL) {
// 기존 DetailPanel 닫기 로직 수행
dispatch({
type: 'POP_PANEL_WITH_VIDEO_RESTORE',
payload: panel_names.DETAIL_PANEL,
});
// 비디오 복원 시도 (약간의 지연 후)
setTimeout(() => {
dispatch(restoreVideoOnBack());
}, 100);
}
};
};
/** /**
* [251114] 명시적 포커스 이동 * [251114] 명시적 포커스 이동
* Panel의 비동기 작업(useEffect, 타이머 등)이 포커스를 탈취하는 것을 방지 * Panel의 비동기 작업(useEffect, 타이머 등)이 포커스를 탈취하는 것을 방지

View File

@@ -588,6 +588,8 @@
.overlay { .overlay {
.position(@position: absolute, @top: 0, @right: 0, @bottom: 0, @left: 0); .position(@position: absolute, @top: 0, @right: 0, @bottom: 0, @left: 0);
pointer-events: auto;
z-index: 10;
} }
@keyframes spin { @keyframes spin {
0% { 0% {
@@ -714,6 +716,7 @@
.controlsHandleAbove { .controlsHandleAbove {
pointer-events: none; pointer-events: none;
z-index: -1;
.position(@position: absolute, @top: 0, @right: 0, @bottom: auto, @left: 0); .position(@position: absolute, @top: 0, @right: 0, @bottom: auto, @left: 0);
} }

View File

@@ -9,7 +9,15 @@
* - 메모리 효율성 우선 * - 메모리 효율성 우선
*/ */
import React, { useState, useRef, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react'; import React, {
useState,
useRef,
useEffect,
useCallback,
useMemo,
forwardRef,
useImperativeHandle,
} from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import DurationFmt from 'ilib/lib/DurationFmt'; import DurationFmt from 'ilib/lib/DurationFmt';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -33,7 +41,7 @@ import {
setMediaControlToggle, setMediaControlToggle,
startMediaAutoClose, startMediaAutoClose,
stopMediaAutoClose, stopMediaAutoClose,
resetMediaAutoClose resetMediaAutoClose,
} from '../../actions/mediaOverlayActions'; } from '../../actions/mediaOverlayActions';
import css from './MediaPlayer.module.less'; import css from './MediaPlayer.module.less';
@@ -49,11 +57,12 @@ const RootContainer = SpotlightContainerDecorator(
// DurationFmt memoization // DurationFmt memoization
const memoGetDurFmt = memoize( const memoGetDurFmt = memoize(
() => new DurationFmt({ () =>
length: 'medium', new DurationFmt({
style: 'clock', length: 'medium',
useNative: false, style: 'clock',
}) useNative: false,
})
); );
const getDurFmt = () => { const getDurFmt = () => {
@@ -68,7 +77,7 @@ const getDurFmt = () => {
format: (time) => { format: (time) => {
if (!time || !time.millisecond) return '00:00'; if (!time || !time.millisecond) return '00:00';
return secondsToTime(Math.floor(time.millisecond / 1000)); return secondsToTime(Math.floor(time.millisecond / 1000));
} },
}; };
} }
}; };
@@ -159,7 +168,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
try { try {
// URL 파싱 시도 // URL 파싱 시도
const url = new URL(src); const url = new URL(src);
return ['youtube.com', 'youtu.be', 'm.youtube.com'].some(domain => return ['youtube.com', 'youtu.be', 'm.youtube.com'].some((domain) =>
url.hostname.includes(domain) url.hostname.includes(domain)
); );
} catch { } catch {
@@ -231,65 +240,75 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
} }
}, [ActualVideoComponent]); }, [ActualVideoComponent]);
const handleUpdate = useCallback((ev) => { const handleUpdate = useCallback(
const el = videoRef.current; (ev) => {
if (!el) return; const el = videoRef.current;
if (!el) return;
const newCurrentTime = el.currentTime || 0; const newCurrentTime = el.currentTime || 0;
const newDuration = el.duration || 0; const newDuration = el.duration || 0;
// 상태 업데이트 // 상태 업데이트
setCurrentTime(newCurrentTime); setCurrentTime(newCurrentTime);
setDuration(newDuration); setDuration(newDuration);
setPaused(el.paused); setPaused(el.paused);
setLoading(el.loading || false); setLoading(el.loading || false);
setError(el.error || null); setError(el.error || null);
// 함수형 업데이트로 stale closure 방지 // 함수형 업데이트로 stale closure 방지
setSourceUnavailable((prevUnavailable) => setSourceUnavailable((prevUnavailable) => (el.loading && prevUnavailable) || el.error);
(el.loading && prevUnavailable) || el.error
);
// Proportion 계산 // Proportion 계산
updateProportionLoaded(); // 플랫폼별 계산 함수 호출 updateProportionLoaded(); // 플랫폼별 계산 함수 호출
setProportionPlayed(newDuration > 0 ? newCurrentTime / newDuration : 0); setProportionPlayed(newDuration > 0 ? newCurrentTime / newDuration : 0);
// 콜백 호출 // 콜백 호출
if (ev.type === 'timeupdate' && onTimeUpdate) { if (ev.type === 'timeupdate' && onTimeUpdate) {
onTimeUpdate(ev); onTimeUpdate(ev);
} }
if (ev.type === 'loadeddata' && onLoadedData) { if (ev.type === 'loadeddata' && onLoadedData) {
onLoadedData(ev); onLoadedData(ev);
} }
if (ev.type === 'loadedmetadata' && onLoadedMetadata) { if (ev.type === 'loadedmetadata' && onLoadedMetadata) {
onLoadedMetadata(ev); onLoadedMetadata(ev);
} }
if (ev.type === 'durationchange' && onDurationChange) { if (ev.type === 'durationchange' && onDurationChange) {
onDurationChange(ev); onDurationChange(ev);
} }
}, [onTimeUpdate, onLoadedData, onLoadedMetadata, onDurationChange, updateProportionLoaded]); },
[onTimeUpdate, onLoadedData, onLoadedMetadata, onDurationChange, updateProportionLoaded]
);
const handleEnded = useCallback((e) => { const handleEnded = useCallback(
if (onEnded) { (e) => {
onEnded(e); if (onEnded) {
} onEnded(e);
}, [onEnded]); }
},
[onEnded]
);
const handleErrorEvent = useCallback((e) => { const handleErrorEvent = useCallback(
setError(e); (e) => {
if (onError) { setError(e);
onError(e); if (onError) {
} onError(e);
}, [onError]); }
},
[onError]
);
// ========== Controls Management ========== // ========== Controls Management ==========
const showControls = useCallback((timeout = 3000) => { const showControls = useCallback(
if (disabled || isModal) return; (timeout = 3000) => {
if (disabled || isModal) return;
console.log('🎬 [MediaPlayer.v2] showControls called, dispatching setMediaControlShow'); console.log('🎬 [MediaPlayer.v2] showControls called, dispatching setMediaControlShow');
dispatch(setMediaControlShow()); dispatch(setMediaControlShow());
dispatch(startMediaAutoClose(timeout)); dispatch(startMediaAutoClose(timeout));
}, [disabled, isModal, dispatch]); },
[disabled, isModal, dispatch]
);
const hideControls = useCallback(() => { const hideControls = useCallback(() => {
console.log('🎬 [MediaPlayer.v2] hideControls called, dispatching setMediaControlHide'); console.log('🎬 [MediaPlayer.v2] hideControls called, dispatching setMediaControlHide');
@@ -351,10 +370,13 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
}, [currentTime, duration, paused, loading, error, proportionLoaded]); }, [currentTime, duration, paused, loading, error, proportionLoaded]);
// ========== Slider Event Handlers ========== // ========== Slider Event Handlers ==========
const handleSliderChange = useCallback(({ value }) => { const handleSliderChange = useCallback(
const time = value * duration; ({ value }) => {
seek(time); const time = value * duration;
}, [duration, seek]); seek(time);
},
[duration, seek]
);
const handleKnobMove = useCallback((ev) => { const handleKnobMove = useCallback((ev) => {
if (!videoRef.current) return; if (!videoRef.current) return;
@@ -459,17 +481,21 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
}, [dispatch]); }, [dispatch]);
// ========== Imperative Handle (API) ========== // ========== Imperative Handle (API) ==========
useImperativeHandle(ref, () => ({ useImperativeHandle(
play, ref,
pause, () => ({
seek, play,
getMediaState, pause,
showControls, seek,
hideControls, getMediaState,
toggleControls, showControls,
areControlsVisible: () => controlsVisible, hideControls,
getVideoNode: () => videoRef.current, toggleControls,
}), [play, pause, seek, getMediaState, showControls, hideControls, toggleControls, controlsVisible]); areControlsVisible: () => controlsVisible,
getVideoNode: () => videoRef.current,
}),
[play, pause, seek, getMediaState, showControls, hideControls, toggleControls, controlsVisible]
);
// ========== setApiProvider 호출 ========== // ========== setApiProvider 호출 ==========
useEffect(() => { useEffect(() => {
@@ -486,7 +512,16 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
getVideoNode: () => videoRef.current, getVideoNode: () => videoRef.current,
}); });
} }
}, [setApiProvider, play, pause, seek, getMediaState, showControls, hideControls, toggleControls]); }, [
setApiProvider,
play,
pause,
seek,
getMediaState,
showControls,
hideControls,
toggleControls,
]);
// ========== Video Props ========== // ========== Video Props ==========
const videoProps = useMemo(() => { const videoProps = useMemo(() => {
@@ -535,7 +570,19 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
...baseProps, ...baseProps,
ref: assignVideoNode, ref: assignVideoNode,
}; };
}, [ActualVideoComponent, assignVideoNode, src, paused, loop, muted, handleLoadStart, handleUpdate, handleEnded, handleErrorEvent, reactPlayerConfig]); }, [
ActualVideoComponent,
assignVideoNode,
src,
paused,
loop,
muted,
handleLoadStart,
handleUpdate,
handleEnded,
handleErrorEvent,
reactPlayerConfig,
]);
// ========== Spotlight Handler ========== // ========== Spotlight Handler ==========
const handleSpotlightFocus = useCallback(() => { const handleSpotlightFocus = useCallback(() => {
@@ -564,18 +611,13 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
> >
{/* Video Element */} {/* Video Element */}
{ActualVideoComponent === Media ? ( {ActualVideoComponent === Media ? (
<ActualVideoComponent {...videoProps}> <ActualVideoComponent {...videoProps}>{children}</ActualVideoComponent>
{children}
</ActualVideoComponent>
) : ( ) : (
<ActualVideoComponent {...videoProps} /> <ActualVideoComponent {...videoProps} />
)} )}
{/* Overlay */} {/* Overlay */}
<Overlay <Overlay bottomControlsVisible={controlsVisible} onClick={handleVideoClick}>
bottomControlsVisible={controlsVisible}
onClick={handleVideoClick}
>
{/* Loading + Thumbnail */} {/* Loading + Thumbnail */}
{loading && thumbnailUrl && ( {loading && thumbnailUrl && (
<> <>
@@ -594,12 +636,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
{/* Slider Section */} {/* Slider Section */}
<div className={css.sliderContainer}> <div className={css.sliderContainer}>
{/* Times - Total */} {/* Times - Total */}
<Times <Times className={css.times} noCurrentTime total={duration} formatter={getDurFmt()} />
className={css.times}
noCurrentTime
total={duration}
formatter={getDurFmt()}
/>
{/* Times - Current */} {/* Times - Current */}
<Times <Times
@@ -663,7 +700,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
onSpotlightRight={handleSpotlightFocus} onSpotlightRight={handleSpotlightFocus}
onSpotlightLeft={handleSpotlightFocus} onSpotlightLeft={handleSpotlightFocus}
onClick={handleSpotlightFocus} onClick={handleSpotlightFocus}
spotlightDisabled={controlsVisible || shouldDisableSpotlight} spotlightDisabled={controlsVisible || shouldDisableSpotlight || !isModal}
/> />
</RootContainer> </RootContainer>
); );

View File

@@ -724,6 +724,7 @@ const VideoPlayerBase = class extends React.Component {
belowContentsVisible: PropTypes.bool, belowContentsVisible: PropTypes.bool,
tabContainerVersion: PropTypes.number, tabContainerVersion: PropTypes.number,
tabIndexV2: PropTypes.number, tabIndexV2: PropTypes.number,
overlayContentsComponent: PropTypes.elementType,
dispatch: PropTypes.func, dispatch: PropTypes.func,
}; };
@@ -1121,6 +1122,8 @@ const VideoPlayerBase = class extends React.Component {
return; return;
} }
Spotlight.setPointerMode(false);
// this.startDelayedFeedbackHide(); // this.startDelayedFeedbackHide();
// this.startDelayedTitleHide(); // this.startDelayedTitleHide();
@@ -1426,6 +1429,15 @@ const VideoPlayerBase = class extends React.Component {
this.showControlsFromPointer this.showControlsFromPointer
); );
onVideoClickCapture = (ev) => {
if (!this.state.mediaControlsVisible && !this.props.panelInfo?.modal) {
this.activityDetected();
this.onVideoClick();
ev.stopPropagation();
ev.preventDefault();
}
};
handleControlsHandleAboveHoldPulse = () => { handleControlsHandleAboveHoldPulse = () => {
if (shouldJump(this.props, this.state)) { if (shouldJump(this.props, this.state)) {
this.handleJump({ this.handleJump({
@@ -1997,6 +2009,11 @@ const VideoPlayerBase = class extends React.Component {
// Player Interaction events // Player Interaction events
// //
onVideoClick = () => { onVideoClick = () => {
console.log('[VideoPlayer] onVideoClick', {
controlsVisible: this.state.mediaControlsVisible,
panelInfoModal: this.props.panelInfo?.modal,
pointerMode: Spotlight.getPointerMode(),
});
// tabContainerVersion === 2일 때 belowContentsVisible도 함께 토글 // tabContainerVersion === 2일 때 belowContentsVisible도 함께 토글
if (this.props.tabContainerVersion === 2 && this.props.setBelowContentsVisible) { if (this.props.tabContainerVersion === 2 && this.props.setBelowContentsVisible) {
const willShowControls = !this.state.mediaControlsVisible; const willShowControls = !this.state.mediaControlsVisible;
@@ -2012,7 +2029,18 @@ const VideoPlayerBase = class extends React.Component {
return; return;
} }
const willShowControls = !this.state.mediaControlsVisible;
this.toggleControls(); this.toggleControls();
if (willShowControls && !this.props.panelInfo?.modal) {
this.restoreOverlayFocus();
}
};
restoreOverlayFocus = () => {
setTimeout(() => {
Spotlight.setPointerMode(false);
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
});
}; };
onSliderChange = ({ value }) => { onSliderChange = ({ value }) => {
@@ -2216,6 +2244,7 @@ const VideoPlayerBase = class extends React.Component {
setSideContentsVisible, setSideContentsVisible,
belowContentsVisible, belowContentsVisible,
tabContainerVersion, tabContainerVersion,
overlayContentsComponent,
disclaimer, disclaimer,
liveTotalTime, liveTotalTime,
currentLiveTimeSeconds, currentLiveTimeSeconds,
@@ -2264,6 +2293,7 @@ const VideoPlayerBase = class extends React.Component {
delete mediaProps.belowContentsVisible; delete mediaProps.belowContentsVisible;
delete mediaProps.tabContainerVersion; delete mediaProps.tabContainerVersion;
delete mediaProps.tabIndexV2; delete mediaProps.tabIndexV2;
delete mediaProps.overlayContentsComponent;
mediaProps.autoPlay = !noAutoPlay; mediaProps.autoPlay = !noAutoPlay;
mediaProps.className = type !== 'MEDIA' ? css.video : css.media; mediaProps.className = type !== 'MEDIA' ? css.video : css.media;
@@ -2272,6 +2302,9 @@ const VideoPlayerBase = class extends React.Component {
mediaProps.onLoadStart = this.handleLoadStart; mediaProps.onLoadStart = this.handleLoadStart;
mediaProps.onUpdate = this.handleEvent; mediaProps.onUpdate = this.handleEvent;
mediaProps.ref = this.setVideoRef; mediaProps.ref = this.setVideoRef;
if (!panelInfo.modal) {
mediaProps.tabIndex = -1;
}
//yhcho ReactPlayer //yhcho ReactPlayer
if ((typeof window === 'object' && !window.PalmSystem) || isYoutube) { if ((typeof window === 'object' && !window.PalmSystem) || isYoutube) {
@@ -2287,6 +2320,7 @@ const VideoPlayerBase = class extends React.Component {
} }
const controlsAriaProps = this.getControlsAriaProps(); const controlsAriaProps = this.getControlsAriaProps();
const OverlayContents = overlayContentsComponent || PlayerOverlayContents;
let proportionSelection = selection; let proportionSelection = selection;
if (proportionSelection != null && this.state.duration) { if (proportionSelection != null && this.state.duration) {
@@ -2356,6 +2390,7 @@ const VideoPlayerBase = class extends React.Component {
modalClassName modalClassName
)} )}
onClick={this.activityDetected} onClick={this.activityDetected}
onClickCapture={this.onVideoClickCapture}
ref={this.setPlayerRef} ref={this.setPlayerRef}
spotlightDisabled={spotlightDisabled} spotlightDisabled={spotlightDisabled}
spotlightId={spotlightId} spotlightId={spotlightId}
@@ -2439,7 +2474,7 @@ const VideoPlayerBase = class extends React.Component {
css.controlFeedbackBtnLayer + (this.state.infoVisible ? ' ' + css.lift : '') css.controlFeedbackBtnLayer + (this.state.infoVisible ? ' ' + css.lift : '')
} }
> >
<PlayerOverlayContents <OverlayContents
playListInfo={playListInfo && playListInfo} playListInfo={playListInfo && playListInfo}
selectedIndex={selectedIndex} selectedIndex={selectedIndex}
onClick={onBackButton} onClick={onBackButton}
@@ -2571,7 +2606,7 @@ const VideoPlayerBase = class extends React.Component {
onClick={this.onSpotlightFocus} onClick={this.onSpotlightFocus}
selectionKeys={controlsHandleAboveSelectionKeys} selectionKeys={controlsHandleAboveSelectionKeys}
spotlightDisabled={ spotlightDisabled={
this.state.mediaControlsVisible || spotlightDisabled || panelInfo.modal this.state.mediaControlsVisible || spotlightDisabled || !panelInfo.modal
} }
/> />
<Announce ref={this.setAnnounceRef} /> <Announce ref={this.setAnnounceRef} />

View File

@@ -594,9 +594,11 @@
} }
} }
.overlay { .overlay {
.position(@position: absolute, @top: 0, @right: 0, @bottom: 0, @left: 0); .position(@position: absolute, @top: 0, @right: 0, @bottom: 0, @left: 0);
} pointer-events: auto;
z-index: 10;
}
@keyframes spin { @keyframes spin {
0% { 0% {
transform: rotate(0.25turn); transform: rotate(0.25turn);
@@ -720,10 +722,11 @@
} }
} }
.controlsHandleAbove { .controlsHandleAbove {
pointer-events: none; pointer-events: none;
.position(@position: absolute, @top: 0, @right: 0, @bottom: auto, @left: 0); z-index: -1;
} .position(@position: absolute, @top: 0, @right: 0, @bottom: auto, @left: 0);
}
// Skin colors // Skin colors
.applySkins({ .applySkins({

View File

@@ -42,6 +42,7 @@ export const SpotlightIds = {
PLAYER_TAB_BUTTON: 'playerTabArrow', PLAYER_TAB_BUTTON: 'playerTabArrow',
PLAYER_BACK_BUTTON: 'player-back-button', PLAYER_BACK_BUTTON: 'player-back-button',
PLAYER_SUBTITLE_BUTTON: 'player-subtitlebutton', PLAYER_SUBTITLE_BUTTON: 'player-subtitlebutton',
PLAYER_PLAY_BUTTON: 'player-play-button',
// searchPanel // searchPanel
SEARCH_THEME: 'search_theme', SEARCH_THEME: 'search_theme',

View File

@@ -1,28 +1,13 @@
import React, { import React, { useCallback, useEffect, useMemo, useState } from 'react';
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { import { useDispatch, useSelector } from 'react-redux';
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight'; import Spotlight from '@enact/spotlight';
import { import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
SpotlightContainerDecorator,
} from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable'; import Spottable from '@enact/spotlight/Spottable';
import { import { pushPanel, updatePanel, navigateFromBestSeller } from '../../../actions/panelActions';
pushPanel, import { navigateToDetailFromHome } from '../../../actions/panelNavigationActions';
updatePanel,
} from '../../../actions/panelActions';
import {
navigateToDetailFromHome,
} from '../../../actions/panelNavigationActions';
import SectionTitle from '../../../components/SectionTitle/SectionTitle'; import SectionTitle from '../../../components/SectionTitle/SectionTitle';
import Tag from '../../../components/TItemCard/Tag'; import Tag from '../../../components/TItemCard/Tag';
import TItemCard from '../../../components/TItemCard/TItemCard'; import TItemCard from '../../../components/TItemCard/TItemCard';
@@ -30,23 +15,13 @@ import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
import TScroller from '../../../components/TScroller/TScroller'; import TScroller from '../../../components/TScroller/TScroller';
import useScrollReset from '../../../hooks/useScrollReset'; import useScrollReset from '../../../hooks/useScrollReset';
import useScrollTo from '../../../hooks/useScrollTo'; import useScrollTo from '../../../hooks/useScrollTo';
import { import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../utils/Config';
LOG_CONTEXT_NAME, import { $L, scaleW } from '../../../utils/helperMethods';
LOG_MESSAGE_ID,
panel_names,
} from '../../../utils/Config';
import {
$L,
scaleW,
} from '../../../utils/helperMethods';
import { SpotlightIds } from '../../../utils/SpotlightIds'; import { SpotlightIds } from '../../../utils/SpotlightIds';
import css from './BestSeller.module.less'; import css from './BestSeller.module.less';
const SpottableComponent = Spottable("div"); const SpottableComponent = Spottable('div');
const Container = SpotlightContainerDecorator( const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
{ enterTo: "last-focused" },
"div"
);
const BestSeller = ({ const BestSeller = ({
order, order,
@@ -58,25 +33,15 @@ const BestSeller = ({
shelfTitle, shelfTitle,
}) => { }) => {
const { getScrollTo, scrollLeft } = useScrollTo(); const { getScrollTo, scrollLeft } = useScrollTo();
const { handleScrollReset, handleStopScrolling } = useScrollReset( const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, true);
scrollLeft,
true
);
const dispatch = useDispatch(); const dispatch = useDispatch();
const { cursorVisible } = useSelector((state) => state.common.appStatus); const { cursorVisible } = useSelector((state) => state.common.appStatus);
const bestSellerDatas = useSelector( const bestSellerDatas = useSelector((state) => state.product.bestSellerData?.bestSeller);
(state) => state.product.bestSellerData?.bestSeller
);
const bestSellerNewDatas = useSelector(
(state) =>
state.foryou?.recommendInfo?.recommendProduct
);
const bestSellerNewDatas = useSelector((state) => state.foryou?.recommendInfo?.recommendProduct);
const [drawChk, setDrawChk] = useState(false); const [drawChk, setDrawChk] = useState(false);
const [firstChk, setFirstChk] = useState(0); const [firstChk, setFirstChk] = useState(0);
@@ -84,59 +49,57 @@ const BestSeller = ({
const [bestInfos, setBestInfos] = useState(null); const [bestInfos, setBestInfos] = useState(null);
const [bestItemNewData, setBestItemNewData] = useState([]); const [bestItemNewData, setBestItemNewData] = useState([]);
useEffect(()=>{ useEffect(() => {
setBestInfos( setBestInfos(
bestSellerNewDatas?.filter( bestSellerNewDatas?.filter((item) => item.recommendTpCd === 'BESTSELLER') || [] // 기본값으로 빈 배열 설정
(item) => item.recommendTpCd === "BESTSELLER" );
) || [] // 기본값으로 빈 배열 설정 }, [bestSellerNewDatas]);
)
},[bestSellerNewDatas])
useEffect(() => { useEffect(() => {
if (!bestInfos || bestInfos.length === 0) { if (!bestInfos || bestInfos.length === 0) {
const baseData = bestSellerDatas?.map((item) => ({ const baseData =
...item, bestSellerDatas?.map((item) => ({
foryou: false, ...item,
})) || []; foryou: false,
})) || [];
setBestItemNewData(baseData); setBestItemNewData(baseData);
return; return;
} }
const recommendedData = bestInfos[0].productInfos?.map((item) => ({ const recommendedData =
...item, bestInfos[0].productInfos?.map((item) => ({
foryou: true, ...item,
})) || []; 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) => ({
...item, const baseData =
foryou: recommendedPrdtIds.has(item.prdtId), bestSellerDatas?.map((item) => ({
})) || []; ...item,
foryou: recommendedPrdtIds.has(item.prdtId),
})) || [];
setBestItemNewData(baseData); setBestItemNewData(baseData);
}, [bestSellerDatas, bestInfos]); }, [bestSellerDatas, bestInfos]);
const orderStyle = useMemo(() => ({ order: order }), [order]); const orderStyle = useMemo(() => ({ order: order }), [order]);
useEffect(() => { useEffect(() => {
setDrawChk(true); setDrawChk(true);
}, [bestSellerNewDatas]); }, [bestSellerNewDatas]);
const handleCardClick = useCallback( const handleCardClick = useCallback(
(patnrId, prdtId) => () => { (patnrId, prdtId) => () => {
dispatch( dispatch(
pushPanel({ navigateFromBestSeller({
name: panel_names.DETAIL_PANEL, patnrId,
panelInfo: { prdtId,
patnrId: patnrId, spotlightId,
prdtId: prdtId,
nowShelf: spotlightId,
},
}) })
); );
}, },
[dispatch] [dispatch, spotlightId]
); );
const handleMoreCardClick = useCallback(() => { const handleMoreCardClick = useCallback(() => {
@@ -144,7 +107,7 @@ const BestSeller = ({
pushPanel({ pushPanel({
name: panel_names.TRENDING_NOW_PANEL, name: panel_names.TRENDING_NOW_PANEL,
panelInfo: { panelInfo: {
pageName: "BS", pageName: 'BS',
focusedContainerId: SpotlightIds.TRENDING_NOW_BEST_SELLER, focusedContainerId: SpotlightIds.TRENDING_NOW_BEST_SELLER,
}, },
}) })
@@ -171,21 +134,18 @@ const BestSeller = ({
if (firstChk === 0 && itemIndex === 0) { if (firstChk === 0 && itemIndex === 0) {
const c = Spotlight.getCurrent(); const c = Spotlight.getCurrent();
if (c) { if (c) {
let cAriaLabel = c.getAttribute("aria-label"); let cAriaLabel = c.getAttribute('aria-label');
cAriaLabel = "Best Seller, Heading 1," + cAriaLabel; cAriaLabel = 'Best Seller, Heading 1,' + cAriaLabel;
c.setAttribute("aria-label", cAriaLabel); c.setAttribute('aria-label', cAriaLabel);
} }
setFirstChk(1); setFirstChk(1);
} else if (firstChk === 1 && itemIndex === 0) { } else if (firstChk === 1 && itemIndex === 0) {
const c = Spotlight.getCurrent(); const c = Spotlight.getCurrent();
if (c) { if (c) {
let cAriaLabel = c.getAttribute("aria-label"); let cAriaLabel = c.getAttribute('aria-label');
if (cAriaLabel) { if (cAriaLabel) {
const newcAriaLabel = cAriaLabel.replace( const newcAriaLabel = cAriaLabel.replace('Best Seller, Heading 1,', '');
"Best Seller, Heading 1,", c.setAttribute('aria-label', newcAriaLabel);
""
);
c.setAttribute("aria-label", newcAriaLabel);
} }
} }
} else { } else {
@@ -219,7 +179,6 @@ const BestSeller = ({
handleShelfFocus(); handleShelfFocus();
} }
}, [handleShelfFocus]); }, [handleShelfFocus]);
return ( return (
<Container <Container
@@ -230,7 +189,7 @@ const BestSeller = ({
onFocus={_handleShelfFocus} onFocus={_handleShelfFocus}
> >
<SectionTitle <SectionTitle
title={$L("BEST SELLER")} title={$L('BEST SELLER')}
data-title-index="homeBestSellerTitle" data-title-index="homeBestSellerTitle"
label="BEST SELLER" label="BEST SELLER"
/> />
@@ -262,15 +221,15 @@ const BestSeller = ({
) => { ) => {
const rankText = const rankText =
rankOrd === 1 rankOrd === 1
? rankOrd + "st" ? rankOrd + 'st'
: rankOrd === 2 : rankOrd === 2
? rankOrd + "nd" ? rankOrd + 'nd'
: rankOrd === 3 : rankOrd === 3
? rankOrd + "rd" ? rankOrd + 'rd'
: rankOrd + "th"; : rankOrd + 'th';
return ( return (
<TItemCardNew <TItemCardNew
key={"subItem" + itemIndex} key={'subItem' + itemIndex}
contextName={LOG_CONTEXT_NAME.HOME} contextName={LOG_CONTEXT_NAME.HOME}
messageId={LOG_MESSAGE_ID.SHELF_CLICK} messageId={LOG_MESSAGE_ID.SHELF_CLICK}
order={itemIndex + 1} order={itemIndex + 1}
@@ -291,13 +250,13 @@ const BestSeller = ({
onBlur={handleBlur(itemIndex)} onBlur={handleBlur(itemIndex)}
onClick={handleCardClick(patnrId, prdtId)} onClick={handleCardClick(patnrId, prdtId)}
offerInfo={offerInfo} offerInfo={offerInfo}
spotlightId={"bestsellerItem" + itemIndex} spotlightId={'bestsellerItem' + itemIndex}
firstLabel={rankText} firstLabel={rankText}
label={itemIndex * 1 + 1 + " of " + bestItemNewData.length} label={itemIndex * 1 + 1 + ' of ' + bestItemNewData.length}
lastLabel=" go to detail, button" lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos} euEnrgLblInfos={euEnrgLblInfos}
> >
{foryou === true && <Tag text={"For You"} />} {foryou === true && <Tag text={'For You'} />}
</TItemCardNew> </TItemCardNew>
); );
} }
@@ -308,7 +267,7 @@ const BestSeller = ({
<SpottableComponent <SpottableComponent
className={css.displayBox} className={css.displayBox}
onClick={handleMoreCardClick} onClick={handleMoreCardClick}
spotlightId={"bestseller-item-more-btn"} spotlightId={'bestseller-item-more-btn'}
aria-label="See more button" aria-label="See more button"
></SpottableComponent> ></SpottableComponent>
</div> </div>
@@ -318,4 +277,4 @@ const BestSeller = ({
); );
}; };
export default BestSeller; export default BestSeller;

View File

@@ -1,57 +1,38 @@
import React, { useCallback, useEffect, useRef, useState } from "react"; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from 'react-redux';
import Spotlight from "@enact/spotlight"; import Spotlight from '@enact/spotlight';
import { SpotlightContainerDecorator } from "@enact/spotlight/SpotlightContainerDecorator"; import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
import { clearSMS } from "../../../actions/appDataActions"; import { clearSMS } from '../../../actions/appDataActions';
import { import { alertToast, setHidePopup, setShowPopup } from '../../../actions/commonActions';
alertToast,
setHidePopup,
setShowPopup,
} from "../../../actions/commonActions";
import { import {
clearGetProductCouponDownload, clearGetProductCouponDownload,
getProductCouponDownload, getProductCouponDownload,
} from "../../../actions/couponActions"; } from '../../../actions/couponActions';
import { setEventPopClickInfo } from "../../../actions/eventActions"; import { setEventPopClickInfo } from '../../../actions/eventActions';
import { sendLogGNB, sendLogShopByMobile } from "../../../actions/logActions"; import { sendLogGNB, sendLogShopByMobile } from '../../../actions/logActions';
import { pushPanel } from "../../../actions/panelActions"; import { pushPanel, navigateFromEventPopup } from '../../../actions/panelActions';
import { startVideoPlayer } from "../../../actions/playActions"; import { startVideoPlayer } from '../../../actions/playActions';
import MobileSendPopUp from "../../../components/MobileSend/MobileSendPopUp"; import MobileSendPopUp from '../../../components/MobileSend/MobileSendPopUp';
import TPopUp from "../../../components/TPopUp/TPopUp"; import TPopUp from '../../../components/TPopUp/TPopUp';
import { launchMembershipApp } from "../../../lunaSend"; import { launchMembershipApp } from '../../../lunaSend';
import { import { ACTIVE_POPUP, LOG_MENU, LOG_TP_NO, MYINFO_TABS, panel_names } from '../../../utils/Config';
ACTIVE_POPUP, import { $L, formatLocalDateTime } from '../../../utils/helperMethods';
LOG_MENU, import css from '../EventPopUpBanner/EventPopUpBanner.module.less';
LOG_TP_NO,
MYINFO_TABS,
panel_names,
} from "../../../utils/Config";
import { $L, formatLocalDateTime } from "../../../utils/helperMethods";
import css from "../EventPopUpBanner/EventPopUpBanner.module.less";
const Container = SpotlightContainerDecorator( const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
{ enterTo: "last-focused" },
"div"
);
export default function EventPopUpBanner() { export default function EventPopUpBanner() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const eventPopInfosData = useSelector( const eventPopInfosData = useSelector((state) => state.event.eventData.eventPopInfo);
(state) => state.event.eventData.eventPopInfo
);
const popupVisible = useSelector((state) => state.common.popup.popupVisible); const popupVisible = useSelector((state) => state.common.popup.popupVisible);
const activePopup = useSelector((state) => state.common.popup.activePopup); const activePopup = useSelector((state) => state.common.popup.activePopup);
const userNumber = useSelector( const userNumber = useSelector((state) => state.common?.appStatus.loginUserData.userNumber);
(state) => state.common?.appStatus.loginUserData.userNumber
);
const smsSuccess = useSelector((state) => state.appData.sendSms.retCode); const smsSuccess = useSelector((state) => state.appData.sendSms.retCode);
const couponDownloadSuccess = useSelector( const couponDownloadSuccess = useSelector((state) => state.coupon?.couponDownloadSuccess);
(state) => state.coupon?.couponDownloadSuccess
);
const timerRef = useRef(); const timerRef = useRef();
const shopByMobileLogRef = useRef(null); const shopByMobileLogRef = useRef(null);
@@ -61,7 +42,7 @@ export default function EventPopUpBanner() {
const evntTpCd = eventPopInfosData?.evntTpCd; const evntTpCd = eventPopInfosData?.evntTpCd;
const evntNm = eventPopInfosData?.evntNm; const evntNm = eventPopInfosData?.evntNm;
const [smsTpCd, setSmsTpCd] = useState(""); const [smsTpCd, setSmsTpCd] = useState('');
const [eventPopData, setEventPopData] = useState({}); const [eventPopData, setEventPopData] = useState({});
useEffect(() => { useEffect(() => {
@@ -77,12 +58,12 @@ export default function EventPopUpBanner() {
const handleApply = useCallback(() => { const handleApply = useCallback(() => {
if (eventPopData) { if (eventPopData) {
dispatch(setEventPopClickInfo({ evntApplcnFlag: "Y", evntId })); dispatch(setEventPopClickInfo({ evntApplcnFlag: 'Y', evntId }));
switch (evntTpCd) { switch (evntTpCd) {
case "EVT00102": // curation case 'EVT00102': // curation
case "EVT00104": // curation + prize case 'EVT00104': // curation + prize
case "EVT00107": { case 'EVT00107': {
// curation + billing coupon // curation + billing coupon
dispatch(setHidePopup()); dispatch(setHidePopup());
dispatch( dispatch(
@@ -96,43 +77,43 @@ export default function EventPopUpBanner() {
); );
break; break;
} }
case "EVT00105": { case 'EVT00105': {
// coupon Only // coupon Only
setSmsTpCd("APP00212"); setSmsTpCd('APP00212');
dispatch(setShowPopup(ACTIVE_POPUP.smsPopup)); dispatch(setShowPopup(ACTIVE_POPUP.smsPopup));
const params = { const params = {
befPrice: "", befPrice: '',
curationId: eventPopData?.curationId ?? "", curationId: eventPopData?.curationId ?? '',
curationNm: eventPopData?.curationNm ?? "", curationNm: eventPopData?.curationNm ?? '',
evntId: eventPopData?.evntId ?? "", evntId: eventPopData?.evntId ?? '',
evntNm: eventPopData?.evntNm ?? "", evntNm: eventPopData?.evntNm ?? '',
lastPrice: "", lastPrice: '',
lgCatCd: "", lgCatCd: '',
lgCatNm: "", lgCatNm: '',
liveFlag: "N", liveFlag: 'N',
locDt: formatLocalDateTime(new Date()), locDt: formatLocalDateTime(new Date()),
logTpNo: LOG_TP_NO.SHOP_BY_MOBILE.SHOP_BY_MOBILE, logTpNo: LOG_TP_NO.SHOP_BY_MOBILE.SHOP_BY_MOBILE,
mbphNoFlag: "N", mbphNoFlag: 'N',
patncNm: eventPopData?.patncNm, patncNm: eventPopData?.patncNm,
patnrId: eventPopData?.patnrId, patnrId: eventPopData?.patnrId,
prdtId: "", prdtId: '',
prdtNm: "", prdtNm: '',
revwGrd: "", revwGrd: '',
rewdAplyFlag: "N", rewdAplyFlag: 'N',
shopByMobileFlag: "Y", shopByMobileFlag: 'Y',
shopTpNm: "popevent", shopTpNm: 'popevent',
showId: "", showId: '',
showNm: "", showNm: '',
trmsAgrFlag: "N", trmsAgrFlag: 'N',
tsvFlag: "N", tsvFlag: 'N',
}; };
dispatch(sendLogShopByMobile(params)); dispatch(sendLogShopByMobile(params));
shopByMobileLogRef.current = params; shopByMobileLogRef.current = params;
break; break;
} }
case "EVT00108": { case 'EVT00108': {
// Direct+Billng+Coupon // Direct+Billng+Coupon
if (!userNumber) { if (!userNumber) {
dispatch(setShowPopup(ACTIVE_POPUP.loginPopup)); dispatch(setShowPopup(ACTIVE_POPUP.loginPopup));
@@ -152,7 +133,7 @@ export default function EventPopUpBanner() {
useEffect(() => { useEffect(() => {
if (couponDownloadSuccess === 0) { if (couponDownloadSuccess === 0) {
dispatch(alertToast($L("Downloading coupon complete."))); dispatch(alertToast($L('Downloading coupon complete.')));
if (eventPopData && eventPopData?.shptmLnkInfo?.shptmLnkTpCd) { if (eventPopData && eventPopData?.shptmLnkInfo?.shptmLnkTpCd) {
dispatch(setHidePopup()); dispatch(setHidePopup());
navigateToLinkByTypeCode(); navigateToLinkByTypeCode();
@@ -174,7 +155,7 @@ export default function EventPopUpBanner() {
const handleSkip = useCallback(() => { const handleSkip = useCallback(() => {
if (eventPopInfosData) { if (eventPopInfosData) {
dispatch(setEventPopClickInfo({ evntApplcnFlag: "N", evntId })); dispatch(setEventPopClickInfo({ evntApplcnFlag: 'N', evntId }));
dispatch(setHidePopup()); dispatch(setHidePopup());
} }
@@ -185,7 +166,7 @@ export default function EventPopUpBanner() {
useEffect(() => { useEffect(() => {
timerRef.current = setTimeout(() => { timerRef.current = setTimeout(() => {
Spotlight.focus("tPopupBtn1"); Spotlight.focus('tPopupBtn1');
}, 100); }, 100);
return () => clearTimeout(timerRef.current); return () => clearTimeout(timerRef.current);
}, []); }, []);
@@ -200,23 +181,23 @@ export default function EventPopUpBanner() {
if (eventPopData && eventPopData.shptmLnkInfo) { if (eventPopData && eventPopData.shptmLnkInfo) {
switch (eventPopData.shptmLnkInfo.shptmLnkTpCd) { switch (eventPopData.shptmLnkInfo.shptmLnkTpCd) {
case "EVT00201": case 'EVT00201':
break; break;
case "EVT00202": case 'EVT00202':
dispatch( dispatch(
pushPanel({ pushPanel({
name: panel_names.FEATURED_BRANDS_PANEL, name: panel_names.FEATURED_BRANDS_PANEL,
panelInfo: { panelInfo: {
from: "gnb", from: 'gnb',
patnrId: eventPopData.shptmLnkInfo?.lnkPatnrId, patnrId: eventPopData.shptmLnkInfo?.lnkPatnrId,
}, },
}) })
); );
break; break;
case "EVT00203": case 'EVT00203':
dispatch(pushPanel({ name: panel_names.TRENDING_NOW_PANEL })); dispatch(pushPanel({ name: panel_names.TRENDING_NOW_PANEL }));
break; break;
case "EVT00204": case 'EVT00204':
pushPanel({ pushPanel({
name: panel_names.HOT_PICKS_PANEL, name: panel_names.HOT_PICKS_PANEL,
panelInfo: { panelInfo: {
@@ -225,7 +206,7 @@ export default function EventPopUpBanner() {
}, },
}); });
break; break;
case "EVT00205": case 'EVT00205':
dispatch( dispatch(
pushPanel({ pushPanel({
name: panel_names.ON_SALE_PANEL, name: panel_names.ON_SALE_PANEL,
@@ -233,7 +214,7 @@ export default function EventPopUpBanner() {
}) })
); );
break; break;
case "EVT00206": case 'EVT00206':
dispatch( dispatch(
pushPanel({ pushPanel({
name: panel_names.CATEGORY_PANEL, name: panel_names.CATEGORY_PANEL,
@@ -243,28 +224,25 @@ export default function EventPopUpBanner() {
}) })
); );
break; break;
case "EVT00207": case 'EVT00207':
dispatch( dispatch(
pushPanel({ navigateFromEventPopup({
name: panel_names.DETAIL_PANEL, patnrId: eventPopData.shptmLnkInfo?.lnkPatnrId,
panelInfo: { prdtId: eventPopData.shptmLnkInfo?.lnkPrdtId,
patnrId: eventPopData.shptmLnkInfo?.lnkPatnrId,
prdtId: eventPopData.shptmLnkInfo?.lnkPrdtId,
},
}) })
); );
break; break;
case "EVT00208": case 'EVT00208':
dispatch( dispatch(
startVideoPlayer({ startVideoPlayer({
modal: false, modal: false,
patnrId: eventPopData.shptmLnkInfo?.lnkPatnrId, patnrId: eventPopData.shptmLnkInfo?.lnkPatnrId,
showId: eventPopData.shptmLnkInfo?.lnkBrdcId, showId: eventPopData.shptmLnkInfo?.lnkBrdcId,
shptmBanrTpNm: "VOD", shptmBanrTpNm: 'VOD',
}) })
); );
break; break;
case "EVT00209": case 'EVT00209':
dispatch( dispatch(
pushPanel({ pushPanel({
name: panel_names.THEME_CURATION_PANEL, name: panel_names.THEME_CURATION_PANEL,
@@ -274,12 +252,12 @@ export default function EventPopUpBanner() {
}) })
); );
break; break;
case "EVT00210": case 'EVT00210':
dispatch( dispatch(
pushPanel({ pushPanel({
name: panel_names.MY_PAGE_PANEL, name: panel_names.MY_PAGE_PANEL,
panelInfo: { panelInfo: {
menuNm: "My Info", menuNm: 'My Info',
menuOrd: 3, menuOrd: 3,
tabForced: MYINFO_TABS.COUPON, tabForced: MYINFO_TABS.COUPON,
}, },
@@ -307,8 +285,8 @@ export default function EventPopUpBanner() {
onClose={handleSkip} onClose={handleSkip}
onClick={handleApply} onClick={handleApply}
hasButton hasButton
button1Text={$L("Apply Now")} button1Text={$L('Apply Now')}
button2Text={$L("Skip")} button2Text={$L('Skip')}
className={css.eventPopup} className={css.eventPopup}
> >
{eventPopInfosData && ( {eventPopInfosData && (
@@ -322,10 +300,8 @@ export default function EventPopUpBanner() {
<MobileSendPopUp <MobileSendPopUp
open={popupVisible} open={popupVisible}
onClose={onClose} onClose={onClose}
title={$L("Send To Mobile")} title={$L('Send To Mobile')}
subTitle={$L( subTitle={$L('Enter your mobile number to receive detailed information via SMS')}
"Enter your mobile number to receive detailed information via SMS"
)}
smsTpCd={smsTpCd} smsTpCd={smsTpCd}
evntId={eventPopData.evntId} evntId={eventPopData.evntId}
patnrId={eventPopData.patnrId} patnrId={eventPopData.patnrId}
@@ -337,7 +313,7 @@ export default function EventPopUpBanner() {
kind="textPopup" kind="textPopup"
open={popupVisible} open={popupVisible}
hasText hasText
text={$L("Downloading coupon complete.")} text={$L('Downloading coupon complete.')}
/> />
)} )}
{activePopup === ACTIVE_POPUP.loginPopup && ( {activePopup === ACTIVE_POPUP.loginPopup && (
@@ -345,10 +321,10 @@ export default function EventPopUpBanner() {
kind="textPopup" kind="textPopup"
hasText hasText
open={popupVisible} open={popupVisible}
text={$L("Would you like to sign in?")} text={$L('Would you like to sign in?')}
hasButton hasButton
button1Text={$L("OK")} button1Text={$L('OK')}
button2Text={$L("CANCEL")} button2Text={$L('CANCEL')}
onClick={handleLogin} onClick={handleLogin}
onClose={onClose} onClose={onClose}
></TPopUp> ></TPopUp>

View File

@@ -17,7 +17,7 @@ import liveShow from '../../../../assets/images/tag-liveshow.png';
import { changeAppStatus } from '../../../actions/commonActions'; import { changeAppStatus } from '../../../actions/commonActions';
import { updateHomeInfo, setVideoTransitionLock } from '../../../actions/homeActions'; import { updateHomeInfo, setVideoTransitionLock } from '../../../actions/homeActions';
import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions'; import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions';
import { pushPanel } from '../../../actions/panelActions'; import { pushPanel, navigateFromRandomUnit } from '../../../actions/panelActions';
import { import {
finishVideoPreview, finishVideoPreview,
startVideoPlayer, startVideoPlayer,
@@ -462,17 +462,11 @@ export default function RandomUnit({
// 투데이즈딜 클릭 // 투데이즈딜 클릭
const todayDealClick = useCallback(() => { const todayDealClick = useCallback(() => {
if (playerPanelInfo?.modal !== false) {
finishAndUnlock();
}
dispatch( dispatch(
pushPanel({ navigateFromRandomUnit({
name: panel_names.DETAIL_PANEL, patnrId: randomData.patnrId,
panelInfo: { prdtId: randomData.prdtId,
patnrId: randomData.patnrId, type: 'product',
prdtId: randomData.prdtId,
},
}) })
); );

View File

@@ -1,41 +1,24 @@
import React, { import React, { useCallback, useEffect, useMemo, useState } from 'react';
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { import { useDispatch, useSelector } from 'react-redux';
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight'; import Spotlight from '@enact/spotlight';
import { import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
SpotlightContainerDecorator,
} from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable'; import Spottable from '@enact/spotlight/Spottable';
import { pushPanel } from '../../../actions/panelActions'; import { pushPanel, navigateFromPickedForYou } from '../../../actions/panelActions';
import SectionTitle from '../../../components/SectionTitle/SectionTitle'; import SectionTitle from '../../../components/SectionTitle/SectionTitle';
import TItemCardNew from '../../../components/TItemCard/TItemCard.new'; import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
import TScroller from '../../../components/TScroller/TScroller'; import TScroller from '../../../components/TScroller/TScroller';
import useScrollReset from '../../../hooks/useScrollReset'; import useScrollReset from '../../../hooks/useScrollReset';
import useScrollTo from '../../../hooks/useScrollTo'; import useScrollTo from '../../../hooks/useScrollTo';
import { import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../utils/Config';
LOG_CONTEXT_NAME,
LOG_MESSAGE_ID,
panel_names,
} from '../../../utils/Config';
import { $L } from '../../../utils/helperMethods'; import { $L } from '../../../utils/helperMethods';
import { SpotlightIds } from '../../../utils/SpotlightIds'; import { SpotlightIds } from '../../../utils/SpotlightIds';
import css from './PickedForYou.module.less'; import css from './PickedForYou.module.less';
const SpottableComponent = Spottable("div"); const SpottableComponent = Spottable('div');
const Container = SpotlightContainerDecorator( const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
{ enterTo: "last-focused" },
"div"
);
const PickedForYou = ({ const PickedForYou = ({
order, order,
@@ -46,36 +29,25 @@ const PickedForYou = ({
shelfLocation, shelfLocation,
shelfTitle, shelfTitle,
}) => { }) => {
const { getScrollTo, scrollLeft } = useScrollTo(); const { getScrollTo, scrollLeft } = useScrollTo();
const { handleScrollReset, handleStopScrolling } = useScrollReset( const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, true);
scrollLeft,
true
);
const dispatch = useDispatch(); const dispatch = useDispatch();
const { cursorVisible } = useSelector((state) => state.common.appStatus); const { cursorVisible } = useSelector((state) => state.common.appStatus);
const justForYouDatasNew = useSelector( const justForYouDatasNew = useSelector((state) => state.foryou?.recommendInfo?.recommendProduct);
(state) => state.foryou?.recommendInfo?.recommendProduct
)
const [drawChk, setDrawChk] = useState(false); const [drawChk, setDrawChk] = useState(false);
const [firstChk, setFirstChk] = useState(0); const [firstChk, setFirstChk] = useState(0);
const [pickedForYou, setPickedForYou] = useState(null); const [pickedForYou, setPickedForYou] = useState(null);
const orderStyle = useMemo(() => ({ order: order }), [order]);
useEffect(()=>{
setPickedForYou(
justForYouDatasNew?.filter(
(item) => item.recommendTpCd === "PICKEDFORYOU"
)
)
},[justForYouDatasNew])
const orderStyle = useMemo(() => ({ order: order }), [order]);
useEffect(() => {
setPickedForYou(justForYouDatasNew?.filter((item) => item.recommendTpCd === 'PICKEDFORYOU'));
}, [justForYouDatasNew]);
useEffect(() => { useEffect(() => {
setDrawChk(true); setDrawChk(true);
}, [justForYouDatasNew]); }, [justForYouDatasNew]);
@@ -83,17 +55,14 @@ const PickedForYou = ({
const handleCardClick = useCallback( const handleCardClick = useCallback(
(patnrId, prdtId) => () => { (patnrId, prdtId) => () => {
dispatch( dispatch(
pushPanel({ navigateFromPickedForYou({
name: panel_names.DETAIL_PANEL, patnrId,
panelInfo: { prdtId,
patnrId: patnrId, spotlightId,
prdtId: prdtId,
nowShelf: spotlightId,
},
}) })
); );
}, },
[dispatch] [dispatch, spotlightId]
); );
const handleMoreCardClick = useCallback(() => { const handleMoreCardClick = useCallback(() => {
@@ -101,7 +70,7 @@ const PickedForYou = ({
pushPanel({ pushPanel({
name: panel_names.TRENDING_NOW_PANEL, name: panel_names.TRENDING_NOW_PANEL,
panelInfo: { panelInfo: {
pageName: "BS", pageName: 'BS',
focusedContainerId: SpotlightIds.TRENDING_NOW_BEST_SELLER, focusedContainerId: SpotlightIds.TRENDING_NOW_BEST_SELLER,
}, },
}) })
@@ -128,21 +97,18 @@ const PickedForYou = ({
if (firstChk === 0 && itemIndex === 0) { if (firstChk === 0 && itemIndex === 0) {
const c = Spotlight.getCurrent(); const c = Spotlight.getCurrent();
if (c) { if (c) {
let cAriaLabel = c.getAttribute("aria-label"); let cAriaLabel = c.getAttribute('aria-label');
cAriaLabel = "Best Seller, Heading 1," + cAriaLabel; cAriaLabel = 'Best Seller, Heading 1,' + cAriaLabel;
c.setAttribute("aria-label", cAriaLabel); c.setAttribute('aria-label', cAriaLabel);
} }
setFirstChk(1); setFirstChk(1);
} else if (firstChk === 1 && itemIndex === 0) { } else if (firstChk === 1 && itemIndex === 0) {
const c = Spotlight.getCurrent(); const c = Spotlight.getCurrent();
if (c) { if (c) {
let cAriaLabel = c.getAttribute("aria-label"); let cAriaLabel = c.getAttribute('aria-label');
if (cAriaLabel) { if (cAriaLabel) {
const newcAriaLabel = cAriaLabel.replace( const newcAriaLabel = cAriaLabel.replace('Best Seller, Heading 1,', '');
"Best Seller, Heading 1,", c.setAttribute('aria-label', newcAriaLabel);
""
);
c.setAttribute("aria-label", newcAriaLabel);
} }
} }
} else { } else {
@@ -186,7 +152,7 @@ const PickedForYou = ({
onFocus={_handleShelfFocus} onFocus={_handleShelfFocus}
> >
<SectionTitle <SectionTitle
title={$L("PICKED FOR YOU")} title={$L('PICKED FOR YOU')}
data-title-index="homeBestSellerTitle" data-title-index="homeBestSellerTitle"
label="PICKED FOR YOU" label="PICKED FOR YOU"
/> />
@@ -198,26 +164,26 @@ const PickedForYou = ({
noScrollByWheel noScrollByWheel
> >
{pickedForYou && {pickedForYou &&
pickedForYou?.[0] && pickedForYou?.[0] &&
pickedForYou?.[0].productInfos.map( pickedForYou?.[0].productInfos.map(
( (
{ {
prdtId, prdtId,
imgUrl, imgUrl,
priceInfo, priceInfo,
prdtNm, prdtNm,
patnrId, patnrId,
offerInfo, offerInfo,
brndNm, brndNm,
patncNm, patncNm,
catNm, catNm,
euEnrgLblInfos euEnrgLblInfos,
}, },
itemIndex itemIndex
) => { ) => {
return ( return (
<TItemCardNew <TItemCardNew
key={"subItem" + itemIndex} key={'subItem' + itemIndex}
contextName={LOG_CONTEXT_NAME.HOME} contextName={LOG_CONTEXT_NAME.HOME}
messageId={LOG_MESSAGE_ID.SHELF_CLICK} messageId={LOG_MESSAGE_ID.SHELF_CLICK}
order={itemIndex + 1} order={itemIndex + 1}
@@ -230,14 +196,14 @@ const PickedForYou = ({
imageAlt={prdtId} imageAlt={prdtId}
imageSource={imgUrl} imageSource={imgUrl}
priceInfo={priceInfo} priceInfo={priceInfo}
productName={prdtNm} productName={prdtNm}
productId={prdtId} productId={prdtId}
onFocus={handleFocus(itemIndex)} onFocus={handleFocus(itemIndex)}
onBlur={handleBlur(itemIndex)} onBlur={handleBlur(itemIndex)}
onClick={handleCardClick(patnrId, prdtId)} onClick={handleCardClick(patnrId, prdtId)}
offerInfo={offerInfo} offerInfo={offerInfo}
spotlightId={"bestsellerItem" + itemIndex} spotlightId={'bestsellerItem' + itemIndex}
label={itemIndex * 1 + 1 + " of " + pickedForYou?.[0].productInfos.length} label={itemIndex * 1 + 1 + ' of ' + pickedForYou?.[0].productInfos.length}
lastLabel=" go to detail, button" lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos} euEnrgLblInfos={euEnrgLblInfos}
/> />
@@ -250,7 +216,7 @@ const PickedForYou = ({
<SpottableComponent <SpottableComponent
className={css.displayBox} className={css.displayBox}
onClick={handleMoreCardClick} onClick={handleMoreCardClick}
spotlightId={"bestseller-item-more-btn"} spotlightId={'bestseller-item-more-btn'}
aria-label="See more button" aria-label="See more button"
></SpottableComponent> ></SpottableComponent>
</div> </div>

View File

@@ -1,46 +1,28 @@
import React, { import React, { memo, useCallback, useEffect, useState } from 'react';
memo,
useCallback,
useEffect,
useState,
} from 'react';
import { import { useDispatch, useSelector } from 'react-redux';
useDispatch,
useSelector,
} from 'react-redux';
import { import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
SpotlightContainerDecorator,
} from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable'; import Spottable from '@enact/spotlight/Spottable';
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container'; import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
import { sendLogCuration } from '../../../actions/logActions'; import { sendLogCuration } from '../../../actions/logActions';
import { getSubCategory } from '../../../actions/mainActions'; import { getSubCategory } from '../../../actions/mainActions';
import { pushPanel } from '../../../actions/panelActions'; import { pushPanel, navigateFromSubCategory } from '../../../actions/panelActions';
import Tag from '../../../components/TItemCard/Tag'; import Tag from '../../../components/TItemCard/Tag';
import TItemCardNew from '../../../components/TItemCard/TItemCard.new'; import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
import TScroller from '../../../components/TScroller/TScroller'; import TScroller from '../../../components/TScroller/TScroller';
import usePrevious from '../../../hooks/usePrevious'; import usePrevious from '../../../hooks/usePrevious';
import useScrollReset from '../../../hooks/useScrollReset'; import useScrollReset from '../../../hooks/useScrollReset';
import useScrollTo from '../../../hooks/useScrollTo'; import useScrollTo from '../../../hooks/useScrollTo';
import { import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, LOG_TP_NO, panel_names } from '../../../utils/Config';
LOG_CONTEXT_NAME,
LOG_MESSAGE_ID,
LOG_TP_NO,
panel_names,
} from '../../../utils/Config';
import { SpotlightIds } from '../../../utils/SpotlightIds'; import { SpotlightIds } from '../../../utils/SpotlightIds';
import CategoryNav from '../../HomePanel/SubCategory/CategoryNav/CategoryNav'; import CategoryNav from '../../HomePanel/SubCategory/CategoryNav/CategoryNav';
import css from '../../HomePanel/SubCategory/SubCategory.module.less'; import css from '../../HomePanel/SubCategory/SubCategory.module.less';
const SpottableComponent = Spottable("div"); const SpottableComponent = Spottable('div');
const Container = SpotlightContainerDecorator({ enterTo: null }, "div"); const Container = SpotlightContainerDecorator({ enterTo: null }, 'div');
const ContainerBasic = SpotlightContainerDecorator( const ContainerBasic = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
{ enterTo: "last-focused" },
"div"
);
const getExpsOrdByLgCatCd = (array, value) => { const getExpsOrdByLgCatCd = (array, value) => {
const expsOrd = array.findIndex(({ lgCatCd }) => value === lgCatCd) + 1; const expsOrd = array.findIndex(({ lgCatCd }) => value === lgCatCd) + 1;
@@ -58,21 +40,12 @@ export default memo(function SubCategory({
}) { }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { getScrollTo, scrollLeft } = useScrollTo(); const { getScrollTo, scrollLeft } = useScrollTo();
const { handleScrollReset, handleStopScrolling } = useScrollReset( const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, false);
scrollLeft,
false
);
const categoryInfos = useSelector( const categoryInfos = useSelector((state) => state.home.menuData?.data?.homeCategory);
(state) => state.home.menuData?.data?.homeCategory const categoryItemInfos = useSelector((state) => state.main.subCategoryData?.categoryItemInfos);
);
const categoryItemInfos = useSelector(
(state) => state.main.subCategoryData?.categoryItemInfos
);
const foruItemInfos = useSelector( const foruItemInfos = useSelector((state) => state.main.recommendProduct[0]?.productInfos);
(state) => state.main.recommendProduct[0]?.productInfos
);
const nowMenu = useSelector((state) => state.common.menu.nowMenu); const nowMenu = useSelector((state) => state.common.menu.nowMenu);
@@ -110,9 +83,9 @@ export default memo(function SubCategory({
getSubCategory( getSubCategory(
{ {
lgCatCd: currentLgCatCd, lgCatCd: currentLgCatCd,
pageSize: "10", pageSize: '10',
tabType: "CAT00102", tabType: 'CAT00102',
filterType: "CAT00202", filterType: 'CAT00202',
recommendIncFlag: 'Y', recommendIncFlag: 'Y',
}, },
1 1
@@ -124,7 +97,7 @@ export default memo(function SubCategory({
}, [currentLgCatCd, dispatch, firstChk]); }, [currentLgCatCd, dispatch, firstChk]);
useEffect(() => { useEffect(() => {
if (!nowMenuRef.current || !nowMenuRef.current.startsWith("Home")) { if (!nowMenuRef.current || !nowMenuRef.current.startsWith('Home')) {
return; return;
} }
@@ -179,40 +152,47 @@ export default memo(function SubCategory({
const handleCardClick = useCallback( const handleCardClick = useCallback(
(patnrId, prdtId) => () => { (patnrId, prdtId) => () => {
dispatch( dispatch(
pushPanel({ navigateFromSubCategory({
name: panel_names.DETAIL_PANEL, patnrId,
panelInfo: { patnrId, prdtId, nowShelf: spotlightId }, prdtId,
spotlightId,
}) })
); );
}, },
[dispatch, spotlightId] [dispatch, spotlightId]
); );
const handleScrollRight = useCallback((e) => { const handleScrollRight = useCallback(
const container = e.currentTarget?.parentNode; (e) => {
const x = container.scrollWidth - container.clientWidth; const container = e.currentTarget?.parentNode;
const x = container.scrollWidth - container.clientWidth;
setTimeout(() => scrollLeft({ x, animate: true })); setTimeout(() => scrollLeft({ x, animate: true }));
}, [scrollLeft]); },
[scrollLeft]
);
const handleMoreCardClick = useCallback((e) => { const handleMoreCardClick = useCallback(
const lgCatCd = e.currentTarget?.getAttribute("data-catcd-num"); (e) => {
const lgCatNm = e.currentTarget?.getAttribute("data-catcd-nm"); const lgCatCd = e.currentTarget?.getAttribute('data-catcd-num');
const tab = 0; const lgCatNm = e.currentTarget?.getAttribute('data-catcd-nm');
const tab = 0;
if (lgCatCd && lgCatNm) { if (lgCatCd && lgCatNm) {
dispatch( dispatch(
pushPanel({ pushPanel({
name: panel_names.CATEGORY_PANEL, name: panel_names.CATEGORY_PANEL,
panelInfo: { panelInfo: {
lgCatCd, lgCatCd,
lgCatNm, lgCatNm,
tab, tab,
}, },
}) })
); );
} }
}, [dispatch]); },
[dispatch]
);
const _handleItemFocus = useCallback(() => { const _handleItemFocus = useCallback(() => {
if (handleItemFocus) { if (handleItemFocus) {
@@ -228,35 +208,34 @@ export default memo(function SubCategory({
useEffect(() => { useEffect(() => {
if (!foruItemInfos || foruItemInfos.length === 0) { if (!foruItemInfos || foruItemInfos.length === 0) {
const baseData = categoryItemInfos?.subCatItemList?.map((item) => ({ const baseData =
...item, categoryItemInfos?.subCatItemList?.map((item) => ({
foryou: false, ...item,
})) || []; foryou: false,
})) || [];
setCategoryItemNewData(baseData); setCategoryItemNewData(baseData);
return; return;
} }
const recommendedData = foruItemInfos?.map((item) => ({ const recommendedData =
...item, foruItemInfos?.map((item) => ({
foryou: true, ...item,
})) || []; 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) => ({
...item, const baseData =
foryou: recommendedPrdtIds.has(item.prdtId), categoryItemInfos?.subCatItemList?.map((item) => ({
})) || []; ...item,
foryou: recommendedPrdtIds.has(item.prdtId),
})) || [];
setCategoryItemNewData([...baseData]); setCategoryItemNewData([...baseData]);
}, [categoryItemInfos?.subCatItemList, foruItemInfos]); }, [categoryItemInfos?.subCatItemList, foruItemInfos]);
return ( return (
<Container <Container spotlightId={spotlightId} data-wheel-point onFocus={_handleShelfFocus}>
spotlightId={spotlightId}
data-wheel-point
onFocus={_handleShelfFocus}
>
<CategoryNav <CategoryNav
categoryInfos={categoryInfos} categoryInfos={categoryInfos}
currentCategoryCode={currentLgCatCd} currentCategoryCode={currentLgCatCd}
@@ -293,7 +272,7 @@ export default memo(function SubCategory({
) => { ) => {
return ( return (
<TItemCardNew <TItemCardNew
key={"subItem" + itemIndex} key={'subItem' + itemIndex}
contextName={LOG_CONTEXT_NAME.HOME} contextName={LOG_CONTEXT_NAME.HOME}
messageId={LOG_MESSAGE_ID.SHELF_CLICK} messageId={LOG_MESSAGE_ID.SHELF_CLICK}
catNm={categoryItemInfos?.catNm} catNm={categoryItemInfos?.catNm}
@@ -314,16 +293,11 @@ export default memo(function SubCategory({
offerInfo={offerInfo} offerInfo={offerInfo}
data-catcd-num={currentLgCatCd} data-catcd-num={currentLgCatCd}
data-catcd-nm={currentLgCatNm} data-catcd-nm={currentLgCatNm}
label={ label={itemIndex * 1 + 1 + ' of ' + (categoryItemNewData?.length || 0)}
itemIndex * 1 +
1 +
" of " +
(categoryItemNewData?.length || 0)
}
lastLabel=" go to detail, button" lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos} euEnrgLblInfos={euEnrgLblInfos}
> >
{foryou === true && <Tag text={"For You"} />} {foryou === true && <Tag text={'For You'} />}
</TItemCardNew> </TItemCardNew>
); );
} }
@@ -333,7 +307,7 @@ export default memo(function SubCategory({
<SpottableComponent <SpottableComponent
className={css.displayBox} className={css.displayBox}
onClick={handleMoreCardClick} onClick={handleMoreCardClick}
spotlightId={"home-cate-more-btn-" + currentLgCatCd} spotlightId={'home-cate-more-btn-' + currentLgCatCd}
onFocus={_handleItemFocus} onFocus={_handleItemFocus}
data-catcd-num={currentLgCatCd} data-catcd-num={currentLgCatCd}
data-catcd-nm={currentLgCatNm} data-catcd-nm={currentLgCatNm}
@@ -345,4 +319,4 @@ export default memo(function SubCategory({
</ContainerBasic> </ContainerBasic>
</Container> </Container>
); );
}); });

View File

@@ -186,6 +186,10 @@ export default function MainView({ className, initService }) {
const isHomeOnTop = useMemo(() => { const isHomeOnTop = useMemo(() => {
return !mainIndex && (panels.length <= 0 || (panels.length === 1 && panels[0].panelInfo.modal)); return !mainIndex && (panels.length <= 0 || (panels.length === 1 && panels[0].panelInfo.modal));
}, [mainIndex, panels]); }, [mainIndex, panels]);
const hasDetailPanel = useMemo(
() => panels.some((panel) => panel?.name === Config.panel_names.DETAIL_PANEL),
[panels]
);
const onPreImageLoadComplete = useCallback(() => { const onPreImageLoadComplete = useCallback(() => {
// console.log('MainView onPreImageLoadComplete'); // console.log('MainView onPreImageLoadComplete');
@@ -257,6 +261,7 @@ export default function MainView({ className, initService }) {
return ( return (
<> <>
{(isHomeOnTop || {(isHomeOnTop ||
hasDetailPanel ||
(panels.length === 1 && (panels.length === 1 &&
(panels[0]?.name === Config.panel_names.PLAYER_PANEL || (panels[0]?.name === Config.panel_names.PLAYER_PANEL ||
panels[0]?.name === Config.panel_names.PLAYER_PANEL_NEW || panels[0]?.name === Config.panel_names.PLAYER_PANEL_NEW ||

View File

@@ -61,6 +61,7 @@ import TPopUp from '../../components/TPopUp/TPopUp';
import Media from '../../components/VideoPlayer/Media'; import Media from '../../components/VideoPlayer/Media';
import TReactPlayer from '../../components/VideoPlayer/TReactPlayer'; import TReactPlayer from '../../components/VideoPlayer/TReactPlayer';
import { VideoPlayer } from '../../components/VideoPlayer/VideoPlayer.v3'; import { VideoPlayer } from '../../components/VideoPlayer/VideoPlayer.v3';
import MediaOverlayContents from '../PlayerPanel/PlayerOverlay/MediaOverlayContents';
import usePrevious from '../../hooks/usePrevious'; import usePrevious from '../../hooks/usePrevious';
import useWhyDidYouUpdate from '../../hooks/useWhyDidYouUpdate'; import useWhyDidYouUpdate from '../../hooks/useWhyDidYouUpdate';
import * as Config from '../../utils/Config'; import * as Config from '../../utils/Config';
@@ -2244,6 +2245,7 @@ const MediaPanel = React.forwardRef(
spotlightId={ spotlightId={
panelInfo.modal ? 'modal-video-player' : panelInfo.modalContainerId || spotlightId panelInfo.modal ? 'modal-video-player' : panelInfo.modalContainerId || spotlightId
} }
overlayContentsComponent={MediaOverlayContents}
handleIndicatorDownClick={handleIndicatorDownClick} handleIndicatorDownClick={handleIndicatorDownClick}
handleIndicatorUpClick={handleIndicatorUpClick} handleIndicatorUpClick={handleIndicatorUpClick}
onError={mediainfoHandler} onError={mediainfoHandler}

View File

@@ -0,0 +1,9 @@
import React from 'react';
import PlayerOverlayContents from './PlayerOverlayContents';
function MediaOverlayContents(props) {
return <PlayerOverlayContents {...props} forceShowMediaOverlay />;
}
export default MediaOverlayContents;

View File

@@ -37,11 +37,11 @@ function PlayerOverlayContents({
handleIndicatorDownClick, handleIndicatorDownClick,
tabContainerVersion, tabContainerVersion,
tabIndexV2, tabIndexV2,
forceShowMediaOverlay = false,
}) { }) {
const cntry_cd = useSelector((state) => state.common.httpHeader?.cntry_cd); const cntry_cd = useSelector((state) => state.common.httpHeader?.cntry_cd);
const dispatch = useDispatch(); const dispatch = useDispatch();
const onClickBack = (ev) => { const onClickBack = (ev) => {
// TabContainerV2가 표시된 상태에서 백버튼 클릭 시 이벤트 버블링 방지 // TabContainerV2가 표시된 상태에서 백버튼 클릭 시 이벤트 버블링 방지
// (Overlay의 onClick으로 전파되어 toggleControls()가 호출되는 것을 막음) // (Overlay의 onClick으로 전파되어 toggleControls()가 호출되는 것을 막음)
if (tabContainerVersion === 2 && belowContentsVisible) { if (tabContainerVersion === 2 && belowContentsVisible) {
@@ -107,11 +107,15 @@ function PlayerOverlayContents({
if (type === 'LIVE') { if (type === 'LIVE') {
return Spotlight.focus('videoIndicator-down-button'); return Spotlight.focus('videoIndicator-down-button');
} }
Spotlight.focus('videoPlayer_mediaControls'); return Spotlight.focus(SpotlightIds.PLAYER_PLAY_BUTTON);
}, },
[type] [type]
); );
const onSpotlightMoveSubtitleButton = useCallback(() => {
return Spotlight.focus('player-subtitlebutton');
}, []);
const onSpotlightMoveSlider = useCallback( const onSpotlightMoveSlider = useCallback(
(e) => { (e) => {
if (type === 'VOD') { if (type === 'VOD') {
@@ -154,9 +158,41 @@ function PlayerOverlayContents({
return Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON); return Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
}, []); }, []);
const handleOverlayKeyDownCapture = useCallback(
(ev) => {
const currentId = Spotlight.getCurrent()?.getAttribute('data-spotlight-id');
if (ev.keyCode === 40 && currentId === SpotlightIds.PLAYER_BACK_BUTTON) {
ev.preventDefault();
ev.stopPropagation();
return onSpotlightMoveMediaButton(ev);
}
if (ev.keyCode === 39 && currentId === SpotlightIds.PLAYER_BACK_BUTTON) {
ev.preventDefault();
ev.stopPropagation();
return onSpotlightMoveSubtitleButton(ev);
}
if (ev.keyCode === 37 && currentId === SpotlightIds.PLAYER_SUBTITLE_BUTTON) {
ev.preventDefault();
ev.stopPropagation();
return onSpotlightMoveMediaButton(ev);
}
if (ev.keyCode === 38 && currentId === SpotlightIds.PLAYER_PLAY_BUTTON) {
ev.preventDefault();
ev.stopPropagation();
return onSpotlightMoveBackButton();
}
},
[onSpotlightMoveBackButton, onSpotlightMoveMediaButton, onSpotlightMoveSubtitleButton]
);
const shouldShowExtendedControls = useMemo(
() => forceShowMediaOverlay || type !== 'MEDIA',
[forceShowMediaOverlay, type]
);
const currentSideButtonStatus = useMemo(() => { const currentSideButtonStatus = useMemo(() => {
if ( if (
type !== 'MEDIA' && shouldShowExtendedControls &&
!panelInfo?.modal && !panelInfo?.modal &&
!sideContentsVisible && !sideContentsVisible &&
tabContainerVersion === 1 tabContainerVersion === 1
@@ -164,7 +200,7 @@ function PlayerOverlayContents({
return true; return true;
} }
return false; return false;
}, [type, panelInfo, sideContentsVisible, tabContainerVersion]); }, [shouldShowExtendedControls, panelInfo, sideContentsVisible, tabContainerVersion]);
const noLiveContentsVisible = useMemo(() => { const noLiveContentsVisible = useMemo(() => {
if (!Array.isArray(playListInfo) || playListInfo.length === 0) { if (!Array.isArray(playListInfo) || playListInfo.length === 0) {
@@ -181,8 +217,8 @@ function PlayerOverlayContents({
return ( return (
<> <>
<Container className={css.overlayContainer}> <Container className={css.overlayContainer} onKeyDownCapture={handleOverlayKeyDownCapture}>
{type !== 'MEDIA' && playListInfo.length > 1 && noLiveContentsVisible && ( {shouldShowExtendedControls && playListInfo.length > 1 && noLiveContentsVisible && (
<> <>
<div className={css.indicatorUpButton}> <div className={css.indicatorUpButton}>
<SpottableBtn <SpottableBtn
@@ -232,7 +268,7 @@ function PlayerOverlayContents({
setSideContentsVisible={setSideContentsVisible} setSideContentsVisible={setSideContentsVisible}
sideContentsVisible={sideContentsVisible} sideContentsVisible={sideContentsVisible}
onSpotlightLeft={ onSpotlightLeft={
type !== 'MEDIA' && playListInfo.length < 2 && onSpotlightMoveBackButton shouldShowExtendedControls && playListInfo.length < 2 && onSpotlightMoveBackButton
} }
videoType={type} videoType={type}
/> />
@@ -247,8 +283,10 @@ function PlayerOverlayContents({
)} )}
onClick={handleSubtitleOnClick} onClick={handleSubtitleOnClick}
spotlightId="player-subtitlebutton" spotlightId="player-subtitlebutton"
onSpotlightUp={onSpotlightMoveTabButton} onSpotlightUp={onSpotlightMoveBackButton}
onSpotlightLeft={onSpotlightMoveMediaButton} onSpotlightLeft={onSpotlightMoveBackButton}
onSpotlightRight={onSpotlightMoveMediaButton}
onSpotlightDown={onSpotlightMoveMediaButton}
aria-label="Caption" aria-label="Caption"
/> />
</div> </div>
@@ -263,6 +301,8 @@ function PlayerOverlayContents({
? onSpotlightMoveBelowTab ? onSpotlightMoveBelowTab
: onSpotlightMoveMediaButton : onSpotlightMoveMediaButton
} }
onSpotlightRight={onSpotlightMoveSubtitleButton}
onSpotlightUp={onSpotlightMoveSubtitleButton}
aria-label="Video Player Close" aria-label="Video Player Close"
ref={backBtnRef} ref={backBtnRef}
/> />
@@ -312,7 +352,8 @@ const propsAreEqual = (prev, next) => {
prev.sideContentsVisible === next.sideContentsVisible && prev.sideContentsVisible === next.sideContentsVisible &&
prev.belowContentsVisible === next.belowContentsVisible && prev.belowContentsVisible === next.belowContentsVisible &&
prev.tabContainerVersion === next.tabContainerVersion && prev.tabContainerVersion === next.tabContainerVersion &&
prev.tabIndexV2 === next.tabIndexV2 prev.tabIndexV2 === next.tabIndexV2 &&
prev.forceShowMediaOverlay === next.forceShowMediaOverlay
); );
}; };

View File

@@ -4,11 +4,7 @@ import { useDispatch } from 'react-redux';
import Spotlight from '@enact/spotlight'; import Spotlight from '@enact/spotlight';
import { import { pushPanel, updatePanel, navigateFromSearch } from '../../../actions/panelActions';
pushPanel,
updatePanel,
} from '../../../actions/panelActions';
import { navigateToDetailPanel } from '../../../actions/panelNavigationActions';
import TItemCardNew from '../../../components/TItemCard/TItemCard.new'; import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
import TScroller from '../../../components/TScroller/TScroller'; import TScroller from '../../../components/TScroller/TScroller';
import { panel_names } from '../../../utils/Config'; import { panel_names } from '../../../utils/Config';
@@ -49,14 +45,16 @@ const ItemCard = ({ onClick, itemInfo, searchQuery }) => {
onClick(ev); onClick(ev);
} }
// 순차 네비게이션 사용 (Chrome 68 호환) // 통합된 navigateToDetail 사용
dispatch(navigateToDetailPanel( dispatch(
patnrId, navigateFromSearch({
prdtId, patnrId,
searchQuery, prdtId,
currentSpot, searchQuery,
{ tab: 0 } currentSpot,
)); additionalInfo: { tab: 0 },
})
);
}, },
[onClick, dispatch, searchQuery] [onClick, dispatch, searchQuery]
); );
@@ -65,7 +63,7 @@ const ItemCard = ({ onClick, itemInfo, searchQuery }) => {
<> <>
<TScroller className={css.container} spotlightId={SpotlightIds.SEARCH_ITEM}> <TScroller className={css.container} spotlightId={SpotlightIds.SEARCH_ITEM}>
{itemInfo.map((item, index) => { {itemInfo.map((item, index) => {
const { thumbnail, title, dcPrice, price, soldout, contentId,euEnrgLblInfos } = item; const { thumbnail, title, dcPrice, price, soldout, contentId, euEnrgLblInfos } = item;
const tokens = contentId && contentId.split('_'); const tokens = contentId && contentId.split('_');
const patnrId = tokens?.[4] || ''; const patnrId = tokens?.[4] || '';
const prdtId = tokens?.[5] || ''; const prdtId = tokens?.[5] || '';