[251019] fix: PlayerPanel Optimization-1

🕐 커밋 시간: 2025. 10. 19. 21:45:39

📊 변경 통계:
  • 총 파일: 11개
  • 추가: +119줄
  • 삭제: -101줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/productActions.js
  ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.jsx
  ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js
  ~ com.twin.app.shoptime/src/hooks/useReviews/useReviews.js
  ~ com.twin.app.shoptime/src/reducers/productReducer.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabButton/PlayerTabButton.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/FeaturedShowContents.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/actions/productActions.js (javascript):
    🔄 Modified: resetShowAllReviews()
  📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx (javascript):
    🔄 Modified: SpotlightContainerDecorator()
  📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabButton/PlayerTabButton.jsx (javascript):
     Deleted: handleTabOnClick()
  📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx (javascript):
    🔄 Modified: Spottable()

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • UI 컴포넌트 아키텍처 개선
This commit is contained in:
2025-10-19 21:45:44 +09:00
parent c44866f2d1
commit 95bb25a135
11 changed files with 195 additions and 188 deletions

View File

@@ -280,15 +280,15 @@ export const getUserReviews = (requestParams) => (dispatch, getState) => {
// }); // });
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('[UserReviews]-API ✅ API 성공 응답:', { // console.log('[UserReviews]-API ✅ API 성공 응답:', {
status: response.status, // status: response.status,
statusText: response.statusText, // statusText: response.statusText,
headers: response.headers, // headers: response.headers,
retCode: response.data && response.data.retCode, // retCode: response.data && response.data.retCode,
retMsg: response.data && response.data.retMsg, // retMsg: response.data && response.data.retMsg,
hasData: !!(response.data && response.data.data), // hasData: !!(response.data && response.data.data),
fullResponse: response.data, // fullResponse: response.data,
}); // });
// ==================== [임시 테스트] 빈 리뷰 응답 시뮬레이션 - 코멘트 처리 ==================== // ==================== [임시 테스트] 빈 리뷰 응답 시뮬레이션 - 코멘트 처리 ====================
// 30% 확률로 리뷰 없는 상품 응답 반환 (테스트용) // 30% 확률로 리뷰 없는 상품 응답 반환 (테스트용)
@@ -311,16 +311,16 @@ export const getUserReviews = (requestParams) => (dispatch, getState) => {
} */ } */
// ==================== [임시 테스트] 끝 - 코멘트 처리 ==================== // ==================== [임시 테스트] 끝 - 코멘트 처리 ====================
if (response.data && response.data.data) { // if (response.data && response.data.data) {
console.log('[UserReviews] 📊 API 데이터 상세:', { // console.log('[UserReviews] 📊 API 데이터 상세:', {
reviewListLength: response.data.data.reviewList ? response.data.data.reviewList.length : 0, // reviewListLength: response.data.data.reviewList ? response.data.data.reviewList.length : 0,
reviewDetail: response.data.data.reviewDetail, // reviewDetail: response.data.data.reviewDetail,
reviewList_sample: // reviewList_sample:
(response.data.data.reviewList && response.data.data.reviewList[0]) || 'empty', // (response.data.data.reviewList && response.data.data.reviewList[0]) || 'empty',
totRvwCnt: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwCnt, // totRvwCnt: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwCnt,
totRvwAvg: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwAvg, // totRvwAvg: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwAvg,
}); // });
} // }
// 실제 API 응답에서 data 부분 추출 // 실제 API 응답에서 data 부분 추출
const apiData = extractReviewApiData(response.data); const apiData = extractReviewApiData(response.data);

View File

@@ -1064,9 +1064,7 @@ const VideoPlayerBase = class extends React.Component {
* @public * @public
*/ */
showControls = () => { showControls = () => {
console.log('[VideoPlayer] showControls 호출');
if (this.props.disabled) { if (this.props.disabled) {
console.log('[VideoPlayer] disabled true - return');
return; return;
} }
@@ -1138,10 +1136,6 @@ const VideoPlayerBase = class extends React.Component {
* @public * @public
*/ */
toggleControls = () => { toggleControls = () => {
console.log(
'[VideoPlayer] toggleControls 호출, mediaControlsVisible:',
this.state.mediaControlsVisible
);
if (this.state.mediaControlsVisible) { if (this.state.mediaControlsVisible) {
this.hideControls(); this.hideControls();
} else { } else {

View File

@@ -867,19 +867,11 @@ const VideoPlayerBase = class extends React.Component {
this.props.belowContentsVisible !== undefined && this.props.belowContentsVisible !== undefined &&
prevProps.belowContentsVisible !== this.props.belowContentsVisible prevProps.belowContentsVisible !== this.props.belowContentsVisible
) { ) {
console.log('[VideoPlayer] belowContentsVisible 변경 감지:', {
prev: prevProps.belowContentsVisible,
current: this.props.belowContentsVisible,
mediaControlsVisible: this.state.mediaControlsVisible,
});
if (this.props.belowContentsVisible && !this.state.mediaControlsVisible) { if (this.props.belowContentsVisible && !this.state.mediaControlsVisible) {
// TabContainerV2가 표시될 때 controls도 표시 // TabContainerV2가 표시될 때 controls도 표시
console.log('[VideoPlayer] TabContainerV2 표시 - controls 강제 표시');
this.showControls(); this.showControls();
} else if (!this.props.belowContentsVisible && this.state.mediaControlsVisible) { } else if (!this.props.belowContentsVisible && this.state.mediaControlsVisible) {
// TabContainerV2가 숨겨질 때 controls도 숨김 // TabContainerV2가 숨겨질 때 controls도 숨김
console.log('[VideoPlayer] TabContainerV2 숨김 - controls 숨김');
this.hideControls(); this.hideControls();
} }
} }
@@ -1049,7 +1041,6 @@ const VideoPlayerBase = class extends React.Component {
// TabContainerV2가 표시 중이면 자동으로 닫지 않음 // TabContainerV2가 표시 중이면 자동으로 닫지 않음
if (this.props.tabContainerVersion === 2 && this.props.belowContentsVisible) { if (this.props.tabContainerVersion === 2 && this.props.belowContentsVisible) {
console.log('[VideoPlayer] TabContainerV2 표시 중 - autoClose 비활성화');
return; return;
} }
@@ -1116,9 +1107,7 @@ const VideoPlayerBase = class extends React.Component {
* @public * @public
*/ */
showControls = () => { showControls = () => {
console.log('[VideoPlayer] showControls 호출');
if (this.props.disabled) { if (this.props.disabled) {
console.log('[VideoPlayer] disabled true - return');
return; return;
} }
@@ -1190,10 +1179,6 @@ const VideoPlayerBase = class extends React.Component {
* @public * @public
*/ */
toggleControls = () => { toggleControls = () => {
console.log(
'[VideoPlayer] toggleControls 호출, mediaControlsVisible:',
this.state.mediaControlsVisible
);
if (this.state.mediaControlsVisible) { if (this.state.mediaControlsVisible) {
this.hideControls(); this.hideControls();
} else { } else {
@@ -1988,12 +1973,9 @@ const VideoPlayerBase = class extends React.Component {
// Player Interaction events // Player Interaction events
// //
onVideoClick = () => { onVideoClick = () => {
console.log('[VideoPlayer] onVideoClick 호출');
// 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;
console.log('[VideoPlayer] 클릭 - 상태 동기화 토글:', willShowControls);
// belowContentsVisible을 먼저 변경 (componentDidUpdate에서 mediaControls 동기화됨) // belowContentsVisible을 먼저 변경 (componentDidUpdate에서 mediaControls 동기화됨)
this.props.setBelowContentsVisible(willShowControls); this.props.setBelowContentsVisible(willShowControls);
@@ -2002,7 +1984,6 @@ const VideoPlayerBase = class extends React.Component {
// 외부 onClick 핸들러가 있으면 호출 // 외부 onClick 핸들러가 있으면 호출
if (this.props.onClick) { if (this.props.onClick) {
console.log('[VideoPlayer] onClick 호출');
this.props.onClick(); this.props.onClick();
return; return;
} }

View File

@@ -239,20 +239,20 @@ const useReviews = (prdtId, patnrId) => {
}); });
// 별점이 없는 리뷰들 로그 출력 // 별점이 없는 리뷰들 로그 출력
if (noStarReviews.length > 0) { // if (noStarReviews.length > 0) {
console.log('[UserReviews]-NoStar 별점 없는 리뷰들:', { // console.log('[UserReviews]-NoStar 별점 없는 리뷰들:', {
totalReviews: allReviews.length, // totalReviews: allReviews.length,
noStarCount: noStarReviews.length, // noStarCount: noStarReviews.length,
noStarReviews: noStarReviews.map((review) => ({ // noStarReviews: noStarReviews.map((review) => ({
rvwId: review.rvwId, // rvwId: review.rvwId,
rvwScr: review.rvwScr, // rvwScr: review.rvwScr,
rvwRtng: review.rvwRtng, // rvwRtng: review.rvwRtng,
rating: review.rating, // rating: review.rating,
rvwCtnt: review.rvwCtnt?.substring(0, 50) + '...', // rvwCtnt: review.rvwCtnt?.substring(0, 50) + '...',
wrtrNknm: review.wrtrNknm, // wrtrNknm: review.wrtrNknm,
})), // })),
}); // });
} // }
// 별점이 있는 리뷰만 카운트 (1~5점) // 별점이 있는 리뷰만 카운트 (1~5점)
let ratingSum = 0; let ratingSum = 0;
@@ -538,14 +538,14 @@ const useReviews = (prdtId, patnrId) => {
const apiTotalCount = reviewDetail && reviewDetail.totRvwCnt ? reviewDetail.totRvwCnt : 0; const apiTotalCount = reviewDetail && reviewDetail.totRvwCnt ? reviewDetail.totRvwCnt : 0;
const actualReviewCount = allReviews.length; const actualReviewCount = allReviews.length;
if (apiTotalCount > actualReviewCount && actualReviewCount > 0) { // if (apiTotalCount > actualReviewCount && actualReviewCount > 0) {
console.log('[UserReviews]-API Mismatch API 개수와 실제 개수 불일치:', { // console.log('[UserReviews]-API Mismatch API 개수와 실제 개수 불일치:', {
apiTotalCount, // apiTotalCount,
actualReviewCount, // actualReviewCount,
difference: apiTotalCount - actualReviewCount, // difference: apiTotalCount - actualReviewCount,
reason: 'API가 최대 100개만 반환하는 페이징 제한', // reason: 'API가 최대 100개만 반환하는 페이징 제한',
}); // });
} // }
return { return {
totalReviews: actualReviewCount, // 실제로 받은 리뷰 개수 사용 (API 제한 반영) totalReviews: actualReviewCount, // 실제로 받은 리뷰 개수 사용 (API 제한 반영)

View File

@@ -1,5 +1,5 @@
import { types } from "../actions/actionTypes"; import { types } from '../actions/actionTypes';
import { curry, get, set } from "../utils/fp"; import { curry, get, set } from '../utils/fp';
const initialState = { const initialState = {
bestSellerData: {}, bestSellerData: {},
@@ -12,62 +12,57 @@ const initialState = {
// FP: handlers map (curried), pure and immutable updates only // FP: handlers map (curried), pure and immutable updates only
const handleBestSeller = curry((state, action) => const handleBestSeller = curry((state, action) =>
set("bestSellerData", get("payload", action), state) set('bestSellerData', get('payload', action), state)
); );
const handleProductOption = curry((state, action) => const handleProductOption = curry((state, action) =>
set("prdtOptInfo", get(["payload", "prdtOptInfo"], action), state) set('prdtOptInfo', get(['payload', 'prdtOptInfo'], action), state)
); );
const handleProductGroup = curry((state, action) => const handleProductGroup = curry((state, action) =>
set("groupInfo", get(["payload", "groupInfo"], action), state) set('groupInfo', get(['payload', 'groupInfo'], action), state)
); );
const handleProductImageLength = curry((state, action) => const handleProductImageLength = curry((state, action) =>
set( set('productImageLength', (get('payload', action) ? get('payload', action) : 0) + 1, state)
"productImageLength",
(get("payload", action) ? get("payload", action) : 0) + 1,
state
)
); );
const handleVideoIndicatorFocus = curry((state, action) => const handleVideoIndicatorFocus = curry((state, action) =>
set("videoIndicatorFocus", get("payload", action), state) set('videoIndicatorFocus', get('payload', action), state)
); );
const handleClearProductDetail = curry((state) => set("prdtOptInfo", null, state)); const handleClearProductDetail = curry((state) => set('prdtOptInfo', null, state));
const handleProductOptionId = curry((state, action) => const handleProductOptionId = curry((state, action) =>
set("prodOptCdCval", get("payload", action), state) set('prodOptCdCval', get('payload', action), state)
); );
// 유저 리뷰 데이터 핸들러 추가 // 유저 리뷰 데이터 핸들러 추가
const handleUserReview = curry((state, action) => { const handleUserReview = curry((state, action) => {
const reviewData = get("payload", action); const reviewData = get('payload', action);
const prdtId = get(["payload", "prdtId"], action); const prdtId = get(['payload', 'prdtId'], action);
console.log("[UserReviews] Reducer - Storing review data:", { // console.log("[UserReviews] Reducer - Storing review data:", {
prdtId: prdtId, // prdtId: prdtId,
hasData: !!reviewData, // hasData: !!reviewData,
reviewListLength: reviewData && reviewData.reviewList ? reviewData.reviewList.length : 0, // reviewListLength: reviewData && reviewData.reviewList ? reviewData.reviewList.length : 0,
totalCount: reviewData && reviewData.reviewDetail ? reviewData.reviewDetail.totRvwCnt : 0 // totalCount: reviewData && reviewData.reviewDetail ? reviewData.reviewDetail.totRvwCnt : 0
}); // });
return set("reviewData", reviewData, return set('reviewData', reviewData, set('loadedPrdtId', prdtId, state));
set("loadedPrdtId", prdtId, state));
}); });
// showAllReviews 토글 핸들러 // showAllReviews 토글 핸들러
const handleToggleShowAllReviews = curry((state, action) => { const handleToggleShowAllReviews = curry((state, action) => {
const currentValue = get("showAllReviews", state); const currentValue = get('showAllReviews', state);
// console.log("[UserReviews] Toggle showAllReviews:", !currentValue); // console.log("[UserReviews] Toggle showAllReviews:", !currentValue);
return set("showAllReviews", !currentValue, state); return set('showAllReviews', !currentValue, state);
}); });
// showAllReviews 초기화 핸들러 // showAllReviews 초기화 핸들러
const handleResetShowAllReviews = curry((state, action) => { const handleResetShowAllReviews = curry((state, action) => {
// console.log("[UserReviews] Reset showAllReviews to false"); // console.log("[UserReviews] Reset showAllReviews to false");
return set("showAllReviews", false, state); return set('showAllReviews', false, state);
}); });
const handlers = { const handlers = {
@@ -84,7 +79,7 @@ const handlers = {
}; };
export const productReducer = (state = initialState, action = {}) => { export const productReducer = (state = initialState, action = {}) => {
const type = get("type", action); const type = get('type', action);
const handler = handlers[type]; const handler = handlers[type];
return handler ? handler(state, action) : state; return handler ? handler(state, action) : state;
}; };

View File

@@ -169,12 +169,12 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
}, []); }, []);
// 이미지 클릭으로 All-Images 모드 팝업 열기 // 이미지 클릭으로 All-Images 모드 팝업 열기
const handleOpenAllImagesPopup = useCallback((imageIndex = 0) => { // const handleOpenAllImagesPopup = useCallback((imageIndex = 0) => {
console.log('[UserReviews] Opening popup in All-Images mode, image index:', imageIndex); // console.log('[UserReviews] Opening popup in All-Images mode, image index:', imageIndex);
setSelectedImageIndex(imageIndex); // setSelectedImageIndex(imageIndex);
setPopupMode('all-images'); // setPopupMode('all-images');
setIsPopupOpen(true); // setIsPopupOpen(true);
}, []); // }, []);
const handleClosePopup = useCallback(() => { const handleClosePopup = useCallback(() => {
// console.log("[UserReviews] Closing popup and resetting mode"); // console.log("[UserReviews] Closing popup and resetting mode");

View File

@@ -269,7 +269,9 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
const vodLogParamsRef = useRef(null); const vodLogParamsRef = useRef(null);
const mediaLogParamsRef = useRef(null); const mediaLogParamsRef = useRef(null);
const prevNowMenuRef = useRef(null); const prevNowMenuRef = useRef(null);
const watchInterval = useRef(null); const watchIntervalLive = useRef(null);
const watchIntervalVod = useRef(null);
const watchIntervalMedia = useRef(null);
// useEffect(() => { // useEffect(() => {
// console.log("###videoLoaded", videoLoaded); // console.log("###videoLoaded", videoLoaded);
// if (nowMenu) { // if (nowMenu) {
@@ -443,7 +445,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
) { ) {
let watchStrtDt = formatGMTString(new Date()); let watchStrtDt = formatGMTString(new Date());
watchInterval.current = setInterval(() => { watchIntervalLive.current = setInterval(() => {
let watchEndDt = formatGMTString(new Date()); let watchEndDt = formatGMTString(new Date());
let watchRecord = { let watchRecord = {
...liveLogParamsRef.current, ...liveLogParamsRef.current,
@@ -458,7 +460,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
...status, ...status,
isModalLiveLogReady: false, isModalLiveLogReady: false,
})); }));
clearInterval(watchInterval.current); clearInterval(watchIntervalLive.current);
dispatch( dispatch(
sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () => sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () =>
dispatch(changeLocalSettings({ watchRecord: {} })) dispatch(changeLocalSettings({ watchRecord: {} }))
@@ -476,7 +478,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
) { ) {
let watchStrtDt = formatGMTString(new Date()); let watchStrtDt = formatGMTString(new Date());
watchInterval.current = setInterval(() => { watchIntervalLive.current = setInterval(() => {
let watchEndDt = formatGMTString(new Date()); let watchEndDt = formatGMTString(new Date());
let watchRecord = { let watchRecord = {
...liveLogParamsRef.current, ...liveLogParamsRef.current,
@@ -491,7 +493,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
...status, ...status,
isFullLiveLogReady: false, isFullLiveLogReady: false,
})); }));
clearInterval(watchInterval.current); clearInterval(watchIntervalLive.current);
dispatch( dispatch(
sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () => sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () =>
dispatch(changeLocalSettings({ watchRecord: {} })) dispatch(changeLocalSettings({ watchRecord: {} }))
@@ -509,7 +511,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
) { ) {
let watchStrtDt = formatGMTString(new Date()); let watchStrtDt = formatGMTString(new Date());
watchInterval.current = setInterval(() => { watchIntervalLive.current = setInterval(() => {
let watchEndDt = formatGMTString(new Date()); let watchEndDt = formatGMTString(new Date());
let watchRecord = { let watchRecord = {
...liveLogParamsRef.current, ...liveLogParamsRef.current,
@@ -524,7 +526,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
...status, ...status,
isDetailLiveLogReady: false, isDetailLiveLogReady: false,
})); }));
clearInterval(watchInterval.current); clearInterval(watchIntervalLive.current);
dispatch( dispatch(
sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () => sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () =>
dispatch(changeLocalSettings({ watchRecord: {} })) dispatch(changeLocalSettings({ watchRecord: {} }))
@@ -596,7 +598,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
) { ) {
let watchStrtDt = formatGMTString(new Date()); let watchStrtDt = formatGMTString(new Date());
watchInterval.current = setInterval(() => { watchIntervalVod.current = setInterval(() => {
let watchEndDt = formatGMTString(new Date()); let watchEndDt = formatGMTString(new Date());
let watchRecord = { let watchRecord = {
...vodLogParamsRef.current, ...vodLogParamsRef.current,
@@ -611,7 +613,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
...status, ...status,
isModalVodLogReady: false, isModalVodLogReady: false,
})); }));
clearInterval(watchInterval.current); clearInterval(watchIntervalVod.current);
dispatch( dispatch(
sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () => sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () =>
dispatch(changeLocalSettings({ watchRecord: {} })) dispatch(changeLocalSettings({ watchRecord: {} }))
@@ -629,7 +631,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
) { ) {
let watchStrtDt = formatGMTString(new Date()); let watchStrtDt = formatGMTString(new Date());
watchInterval.current = setInterval(() => { watchIntervalVod.current = setInterval(() => {
let watchEndDt = formatGMTString(new Date()); let watchEndDt = formatGMTString(new Date());
let watchRecord = { let watchRecord = {
...vodLogParamsRef.current, ...vodLogParamsRef.current,
@@ -644,7 +646,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
...status, ...status,
isFullVodLogReady: false, isFullVodLogReady: false,
})); }));
clearInterval(watchInterval.current); clearInterval(watchIntervalVod.current);
dispatch( dispatch(
sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () => sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () =>
dispatch(changeLocalSettings({ watchRecord: {} })) dispatch(changeLocalSettings({ watchRecord: {} }))
@@ -662,7 +664,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
) { ) {
let watchStrtDt = formatGMTString(new Date()); let watchStrtDt = formatGMTString(new Date());
watchInterval.current = setInterval(() => { watchIntervalVod.current = setInterval(() => {
let watchEndDt = formatGMTString(new Date()); let watchEndDt = formatGMTString(new Date());
let watchRecord = { let watchRecord = {
...vodLogParamsRef.current, ...vodLogParamsRef.current,
@@ -677,7 +679,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
...status, ...status,
isDetailVodLogReady: false, isDetailVodLogReady: false,
})); }));
clearInterval(watchInterval.current); clearInterval(watchIntervalVod.current);
dispatch( dispatch(
sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () => sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () =>
dispatch(changeLocalSettings({ watchRecord: {} })) dispatch(changeLocalSettings({ watchRecord: {} }))
@@ -752,7 +754,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
if (logStatus.isModalMediaLogReady && panelInfo?.modal) { if (logStatus.isModalMediaLogReady && panelInfo?.modal) {
let watchStrtDt = formatGMTString(new Date()); let watchStrtDt = formatGMTString(new Date());
watchInterval.current = setInterval(() => { watchIntervalMedia.current = setInterval(() => {
let watchEndDt = formatGMTString(new Date()); let watchEndDt = formatGMTString(new Date());
let watchRecord = { let watchRecord = {
...mediaLogParamsRef.current, ...mediaLogParamsRef.current,
@@ -767,7 +769,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
...status, ...status,
isModalMediaLogReady: false, isModalMediaLogReady: false,
})); }));
clearInterval(watchInterval.current); clearInterval(watchIntervalMedia.current);
dispatch( dispatch(
sendLogVOD({ ...mediaLogParamsRef.current, watchStrtDt }, () => sendLogVOD({ ...mediaLogParamsRef.current, watchStrtDt }, () =>
dispatch(changeLocalSettings({ watchRecord: {} })) dispatch(changeLocalSettings({ watchRecord: {} }))
@@ -781,7 +783,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
if (logStatus.isFullMediaLogReady && !panelInfo?.modal) { if (logStatus.isFullMediaLogReady && !panelInfo?.modal) {
let watchStrtDt = formatGMTString(new Date()); let watchStrtDt = formatGMTString(new Date());
watchInterval.current = setInterval(() => { watchIntervalMedia.current = setInterval(() => {
let watchEndDt = formatGMTString(new Date()); let watchEndDt = formatGMTString(new Date());
let watchRecord = { let watchRecord = {
...mediaLogParamsRef.current, ...mediaLogParamsRef.current,
@@ -796,7 +798,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
...status, ...status,
isFullMediaLogReady: false, isFullMediaLogReady: false,
})); }));
clearInterval(watchInterval.current); clearInterval(watchIntervalMedia.current);
dispatch( dispatch(
sendLogVOD({ ...mediaLogParamsRef.current, watchStrtDt }, () => sendLogVOD({ ...mediaLogParamsRef.current, watchStrtDt }, () =>
dispatch(changeLocalSettings({ watchRecord: {} })) dispatch(changeLocalSettings({ watchRecord: {} }))

View File

@@ -1,21 +1,21 @@
import React, { useCallback, useEffect } from "react"; import React, { useCallback, useEffect } from 'react';
import classNames from "classnames"; import classNames from 'classnames';
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 Spottable from "@enact/spotlight/Spottable"; import Spottable from '@enact/spotlight/Spottable';
import { updatePanel } from "../../../../actions/panelActions"; import { updatePanel } from '../../../../actions/panelActions';
import { panel_names } from "../../../../utils/Config"; import { panel_names } from '../../../../utils/Config';
import { SpotlightIds } from "../../../../utils/SpotlightIds"; import { SpotlightIds } from '../../../../utils/SpotlightIds';
import css from "./PlayerTabButton.module.less"; import css from './PlayerTabButton.module.less';
const SpottableComponent = Spottable("button"); const SpottableComponent = Spottable('button');
const Container = SpotlightContainerDecorator( const Container = SpotlightContainerDecorator(
{ enterTo: "default-element", preserveld: true }, { enterTo: 'default-element', preserveld: true },
"div" 'div'
); );
export default function PlayerTabButton({ export default function PlayerTabButton({
sideContentsVisible, sideContentsVisible,
@@ -26,7 +26,8 @@ export default function PlayerTabButton({
const dispatch = useDispatch(); const dispatch = useDispatch();
const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd); const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
const handleTabOnClick = (e) => { const handleTabOnClick = useCallback(
(e) => {
dispatch( dispatch(
updatePanel({ updatePanel({
name: panel_names.PLAYER_PANEL, name: panel_names.PLAYER_PANEL,
@@ -36,12 +37,16 @@ export default function PlayerTabButton({
}) })
); );
setSideContentsVisible((prev) => !prev); setSideContentsVisible((prev) => !prev);
setTimeout(() => {
// microtask 큐를 사용하여 상태 업데이트 이후 포커스 이동
Promise.resolve().then(() => {
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON); Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
}, 0); });
e.stopPropagation(); e.stopPropagation();
}; },
[dispatch, setSideContentsVisible]
);
const _onSpotlightLeft = useCallback( const _onSpotlightLeft = useCallback(
(e) => { (e) => {
@@ -52,7 +57,7 @@ export default function PlayerTabButton({
} }
if (!sideContentsVisible) { if (!sideContentsVisible) {
e.stopPropagation(); e.stopPropagation();
Spotlight.focus("videoIndicator-up-button"); Spotlight.focus('videoIndicator-up-button');
} }
}, },
[sideContentsVisible] [sideContentsVisible]
@@ -60,10 +65,10 @@ export default function PlayerTabButton({
const onSpotlightDown = useCallback( const onSpotlightDown = useCallback(
(e) => { (e) => {
if (countryCode !== "US") { if (countryCode !== 'US') {
if (videoType === "VOD") { if (videoType === 'VOD') {
Spotlight.focus("videoIndicator-up-button"); Spotlight.focus('videoIndicator-up-button');
} else if (videoType === "LIVE") { } else if (videoType === 'LIVE') {
Spotlight.focus(SpotlightIds.PLAYER_SLIDER); Spotlight.focus(SpotlightIds.PLAYER_SLIDER);
} }
return; return;
@@ -80,7 +85,7 @@ export default function PlayerTabButton({
(e) => { (e) => {
if (!sideContentsVisible) { if (!sideContentsVisible) {
e.stopPropagation(); e.stopPropagation();
Spotlight.focus("videoIndicator-up-button"); Spotlight.focus('videoIndicator-up-button');
} }
}, },
[sideContentsVisible] [sideContentsVisible]

View File

@@ -1,19 +1,19 @@
import React, { useCallback, useEffect, useMemo, useRef } from "react"; import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { useDispatch } from "react-redux"; import { useDispatch } from 'react-redux';
import Spotlight from "@enact/spotlight"; import Spotlight from '@enact/spotlight';
import defaultImage from "../../../../../assets/images/img-thumb-empty-144@3x.png"; import defaultImage from '../../../../../assets/images/img-thumb-empty-144@3x.png';
import { updatePanel } from "../../../../actions/panelActions"; import { updatePanel } from '../../../../actions/panelActions';
import TVirtualGridList from "../../../../components/TVirtualGridList/TVirtualGridList"; import TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList';
import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from "../../../../utils/Config"; import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../../../utils/Config';
import { $L, removeSpecificTags } from "../../../../utils/helperMethods"; import { $L, removeSpecificTags } from '../../../../utils/helperMethods';
import PlayerItemCard, { TYPES } from "../../PlayerItemCard/PlayerItemCard"; import PlayerItemCard, { TYPES } from '../../PlayerItemCard/PlayerItemCard';
import ListEmptyContents from "../TabContents/ListEmptyContents/ListEmptyContents"; import ListEmptyContents from '../TabContents/ListEmptyContents/ListEmptyContents';
import css from "./LiveChannelContents.module.less"; import css from './LiveChannelContents.module.less';
import { getMainCategoryShowDetail } from "../../../../actions/mainActions"; import { getMainCategoryShowDetail } from '../../../../actions/mainActions';
import { sendLogTotalRecommend } from "../../../../actions/logActions"; import { sendLogTotalRecommend } from '../../../../actions/logActions';
export default function FeaturedShowContents({ export default function FeaturedShowContents({
featuredShowsInfos, featuredShowsInfos,
@@ -25,10 +25,12 @@ export default function FeaturedShowContents({
tabIndex, tabIndex,
handleItemFocus, handleItemFocus,
tabTitle, tabTitle,
panelInfo panelInfo,
}) { }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const isClickBlocked = useRef(false); const isClickBlocked = useRef(false);
const blockTimeoutRef = useRef(null);
const handleFocus = useCallback( const handleFocus = useCallback(
() => () => { () => () => {
if (handleItemFocus) { if (handleItemFocus) {
@@ -61,8 +63,8 @@ export default function FeaturedShowContents({
category: catNm, category: catNm,
partner: patncNm, partner: patncNm,
contextName: LOG_CONTEXT_NAME.SHOW, contextName: LOG_CONTEXT_NAME.SHOW,
messageId: LOG_MESSAGE_ID.CONTENTCLICK messageId: LOG_MESSAGE_ID.CONTENTCLICK,
} };
dispatch(sendLogTotalRecommend(params)); dispatch(sendLogTotalRecommend(params));
//중복클릭방지 //중복클릭방지
if (isClickBlocked.current) { if (isClickBlocked.current) {
@@ -70,8 +72,15 @@ export default function FeaturedShowContents({
} }
isClickBlocked.current = true; isClickBlocked.current = true;
setTimeout(() => {
// 이전 타이머가 있으면 정리
if (blockTimeoutRef.current) {
clearTimeout(blockTimeoutRef.current);
}
blockTimeoutRef.current = setTimeout(() => {
isClickBlocked.current = false; isClickBlocked.current = false;
blockTimeoutRef.current = null;
}, 600); }, 600);
if (currentVideoShowId && currentVideoShowId === showId) { if (currentVideoShowId && currentVideoShowId === showId) {
@@ -107,29 +116,30 @@ export default function FeaturedShowContents({
spotlightId={`tabChannel-video-${index}`} spotlightId={`tabChannel-video-${index}`}
videoVerticalVisible={videoVerticalVisible} videoVerticalVisible={videoVerticalVisible}
selectedIndex={index} selectedIndex={index}
currentVideoVisible={ currentVideoVisible={currentVideoShowId === featuredShowsInfos[index].showId}
currentVideoShowId === featuredShowsInfos[index].showId
}
/> />
); );
}, },
[ [featuredShowsInfos, currentVideoInfo, currentVideoShowId, videoVerticalVisible, isClickBlocked]
featuredShowsInfos,
currentVideoInfo,
currentVideoShowId,
videoVerticalVisible,
isClickBlocked,
]
); );
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
Spotlight.focus("tabChannel-video-" + selectedIndex); Spotlight.focus('tabChannel-video-' + selectedIndex);
}); });
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [featuredShowsInfos]); }, [featuredShowsInfos]);
// cleanup useEffect
useEffect(() => {
return () => {
if (blockTimeoutRef.current) {
clearTimeout(blockTimeoutRef.current);
}
};
}, []);
return ( return (
<> <>
<div className={css.container}> <div className={css.container}>

View File

@@ -29,6 +29,8 @@ export default function LiveChannelContents({
}) { }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const isClickBlocked = useRef(false); const isClickBlocked = useRef(false);
const blockTimeoutRef = useRef(null);
const handleFocus = useCallback( const handleFocus = useCallback(
() => () => { () => () => {
if (handleItemFocus) { if (handleItemFocus) {
@@ -75,8 +77,15 @@ export default function LiveChannelContents({
} }
isClickBlocked.current = true; isClickBlocked.current = true;
setTimeout(() => {
// 이전 타이머가 있으면 정리
if (blockTimeoutRef.current) {
clearTimeout(blockTimeoutRef.current);
}
blockTimeoutRef.current = setTimeout(() => {
isClickBlocked.current = false; isClickBlocked.current = false;
blockTimeoutRef.current = null;
}, 600); }, 600);
if (!showId) return; if (!showId) return;
@@ -144,6 +153,15 @@ export default function LiveChannelContents({
const containerClass = version === 2 ? cssV2.container : css.container; const containerClass = version === 2 ? cssV2.container : css.container;
// cleanup useEffect
useEffect(() => {
return () => {
if (blockTimeoutRef.current) {
clearTimeout(blockTimeoutRef.current);
}
};
}, []);
return ( return (
<> <>
<div className={containerClass}> <div className={containerClass}>

View File

@@ -120,9 +120,11 @@ export default function TabContainerV2({
// tabIndex가 2(ShopNowButton)로 변경되면 자동으로 포커스 이동 // tabIndex가 2(ShopNowButton)로 변경되면 자동으로 포커스 이동
useEffect(() => { useEffect(() => {
if (tabIndex === 2) { if (tabIndex === 2) {
setTimeout(() => { const timeoutId = setTimeout(() => {
Spotlight.focus('below-tab-shop-now-button'); Spotlight.focus('below-tab-shop-now-button');
}, 100); }, 100);
return () => clearTimeout(timeoutId);
} }
}, [tabIndex]); }, [tabIndex]);