import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import classNames from 'classnames'; import { useDispatch, useSelector, } from 'react-redux'; import Spotlight from '@enact/spotlight'; import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import Spottable from '@enact/spotlight/Spottable'; import { getContainerId } from '@enact/spotlight/src/container'; import btnPlay from '../../../../assets/images/btn/btn-play-thumb-nor.png'; import defaultLogoImg from '../../../../assets/images/ic-tab-partners-default@3x.png'; import emptyHorImage from '../../../../assets/images/img-home-banner-empty-hor.png'; import emptyVerImage from '../../../../assets/images/img-home-banner-empty-ver.png'; import defaultImageItem from '../../../../assets/images/img-thumb-empty-product@3x.png'; import liveShow from '../../../../assets/images/tag-liveshow.png'; import { setBannerIndex, updateHomeInfo, } from '../../../actions/homeActions'; import { sendLogTopContents, sendLogTotalRecommend, } from '../../../actions/logActions'; import { pushPanel, SOURCE_MENUS, } from '../../../actions/panelActions'; import { finishVideoPreview, startVideoPlayer, } from '../../../actions/playActions'; import CustomImage from '../../../components/CustomImage/CustomImage'; import usePriceInfo from '../../../hooks/usePriceInfo'; import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, LOG_TP_NO, panel_names, } from '../../../utils/Config'; import { $L, formatGMTString, } from '../../../utils/helperMethods'; import { TEMPLATE_CODE_CONF } from '../HomePanel'; import css from './RollingUnit.module.less'; const SpottableComponent = Spottable('div'); const Container = SpotlightContainerDecorator({ enterTo: 'last-focused', preserveId: true }, 'div'); const LINK_TYPES = { FEATURED_BRANDS: 'DSP00501', TRENDING_NOW: 'DSP00502', HOT_PICKS: 'DSP00503', ON_SALE: 'DSP00504', CATEGORY: 'DSP00505', PRODUCT_DETAIL: 'DSP00506', VOD: 'DSP00507', SHOW_DETAIL: 'DSP00508', THEME: 'DSP00509', JUSTFORYOU: 'DSP00510', }; const createPanelInfo = (data, categoryData = {}) => ({ patnrId: data.patnrId, prdtId: data.prdtId, curationId: data.lnkCurationId, lgCatCd: data.lgCatCd, lgCatNm: categoryData.lgCatNm, COUNT: categoryData.COUNT, currentSpot: null, dropDownTab: 0, tab: 0, focusedContainerId: null, }); export default function RollingUnit({ bannerData, spotlightId, isHorizontal, handleItemFocus, handleShelfFocus, videoPlayerable = false, }) { const rollingData = bannerData.bannerDetailInfos; const dispatch = useDispatch(); const { curationId, curationTitle } = useSelector((state) => state.home); const curtNm = useSelector((state) => state.home?.bannerData?.curtNm); const shptmTmplCd = useSelector((state) => state.home?.bannerData?.shptmTmplCd); const nowMenu = useSelector((state) => state.common.menu.nowMenu); const entryMenu = useSelector((state) => state.common.menu.entryMenu); const introTermsAgree = useSelector((state) => state.common.optionalTermsAgree); const homeCategory = useSelector((state) => state.home.menuData?.data?.homeCategory); const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd); const foryouInfos = useSelector((state) => state.foryou.recommendInfo.recommendProduct); const { userNumber } = useSelector((state) => state.common.appStatus.loginUserData); const playerPanelInfo = useSelector((state) => { const playerPanel = state.panels.panels.find((p) => p.name === panel_names.PLAYER_PANEL); return playerPanel?.panelInfo; }); const bannerId = `banner-${bannerData.banrLctnNo}`; const savedIndex = useSelector((state) => state.home.bannerIndices[bannerId]); // 컴포넌트 상단에서 필터링 const filteredRollingData = useMemo(() => { return rollingData.filter( (item) => (introTermsAgree === true && userNumber !== undefined && userNumber !== '') || item.shptmLnkTpCd !== 'DSP00510' ); }, [rollingData, introTermsAgree, userNumber]); // 이후 filteredRollingData 사용 const rollingDataLength = filteredRollingData.length; const [startIndex, setStartIndex] = useState(savedIndex !== undefined ? savedIndex : 0); const lastIndexRef = useRef(rollingDataLength - 1); const doRollingRef = useRef(false); const [unitHasFocus, setUnitHasFocus] = useState(false); const [contentsFocus, setContentsFocus] = useState(false); const [prevFocus, setPrevFocus] = useState(false); const [nextFocus, setNextFocus] = useState(false); const requestRef = useRef(); const previousTimeRef = useRef(); const arrRef = useRef([]); const bannerDataRef = useRef(bannerData); const filteredRollingDataRef = useRef(filteredRollingData); // filteredRollingDataRef 업데이트 useEffect(() => { filteredRollingDataRef.current = filteredRollingData; }, [filteredRollingData]); const topContentsLogInfo = useMemo(() => { if (filteredRollingDataRef.current && filteredRollingDataRef.current.length > 0) { const currentRollingData = filteredRollingDataRef.current[startIndex]; let contId, contNm; switch (currentRollingData?.shptmBanrTpCd) { // case: "LIVE" or "VOD" case 'DSP00301': case 'DSP00302': contId = currentRollingData?.showId; contNm = currentRollingData?.showNm; break; // case: "Image Banner" case 'DSP00303': contId = currentRollingData?.shptmLnkTpCd; contNm = currentRollingData?.shptmLnkTpNm; break; // case: "Today's Deals" default: contId = currentRollingData?.prdtId; contNm = currentRollingData?.prdtNm; break; } if ( currentRollingData?.shptmLnkTpCd === 'DSP00503' || // "Hot Picks" currentRollingData?.shptmLnkTpCd === 'DSP00509' // "Theme" ) { contNm = contNm + ' | ' + currentRollingData?.lnkCurationId; } return { banrNo: `${currentRollingData?.banrDpOrd}`, banrTpNm: currentRollingData?.vtctpYn ? currentRollingData.vtctpYn === 'Y' ? 'Vertical' : 'Horizontal' : '', contId, contNm, contTpNm: currentRollingData?.shptmBanrTpNm ?? '', dspyTpNm: bannerDataRef.current?.shptmDspyTpNm ?? '', expsOrd: bannerDataRef.current?.banrLctnNo ?? '', linkTpCd: '', patncNm: currentRollingData?.patncNm ?? '', patnrId: currentRollingData?.patnrId ?? '', tmplCd: shptmTmplCd, }; } return {}; }, [shptmTmplCd, startIndex]); const sendBannerLog = useCallback( (bannerClick) => { const data = filteredRollingDataRef.current[startIndex]; const newParams = bannerData.banrLctnNo === '2' ? { bannerType: 'Horizontal', } : { bannerType: 'Vertical', }; if (filteredRollingDataRef.current && nowMenu === LOG_MENU.HOME_TOP) { const logParams = { contextName: LOG_CONTEXT_NAME.HOME, messageId: bannerClick ? LOG_MESSAGE_ID.BANNER_CLICK : LOG_MESSAGE_ID.BANNER, curationId: curationId, curationTitle: curationTitle, contentType: data.shptmBanrTpNm, contentId: data.showId, contentTitle: data.showNm, productId: data.prdtId, productTitle: data.prdtNm, displayType: 'button', partner: data.patncNm, brand: data.brndNm, location: bannerData.banrLctnNo, ...newParams, }; dispatch(sendLogTotalRecommend(logParams)); } }, [dispatch, nowMenu, curationId, curationTitle] ); useEffect(() => { if (savedIndex !== undefined) { setStartIndex(savedIndex); } }, [savedIndex]); const animate = useCallback((time) => { if (previousTimeRef.current != undefined) { const deltaTime = time - previousTimeRef.current; if (deltaTime >= 10000 && doRollingRef.current) { setStartIndex((prevIndex) => (prevIndex === lastIndexRef.current ? 0 : prevIndex + 1)); previousTimeRef.current = time; } } else { previousTimeRef.current = time; } if (typeof window === 'object') { requestRef.current = window.requestAnimationFrame(animate); } }, []); // 롤링 이전 버튼 const handlePrev = useCallback(() => { if (startIndex === 0) { setStartIndex(lastIndexRef.current); return; } setStartIndex(startIndex - 1); }, [startIndex]); // 롤링 다음 버튼 const handleNext = useCallback(() => { if (lastIndexRef.current === startIndex) { setStartIndex(0); return; } setStartIndex(startIndex + 1); }, [startIndex]); // 베너 포커스 인 const onFocus = useCallback(() => { if (handleItemFocus) { handleItemFocus(); } setUnitHasFocus(true); }, [handleItemFocus]); // 배너 포커스 아웃 const onBlur = useCallback(() => { setUnitHasFocus(false); }, []); // 인디케이터 아래키 누를시 [<] const prevKeyDown = (event) => { if (event.key === 'ArrowDown') { setNextFocus(true); setContentsFocus(true); } }; // 인디케이터 아래키 누를시 [>] const nextKeyDown = (event) => { if (event.key === 'ArrowDown') { setPrevFocus(true); setContentsFocus(true); } }; // 인디케이터 포커스 const indicatorFocus = useCallback(() => { if (handleItemFocus) { handleItemFocus(); } setNextFocus(false); setContentsFocus(false); setPrevFocus(false); setUnitHasFocus(true); }, [handleItemFocus]); // 인디케이터 포커스 아웃 const indicatorBlur = useCallback(() => { setUnitHasFocus(false); setTimeout(() => { setNextFocus(false); setContentsFocus(false); }, 300); }, []); const shelfFocus = useCallback(() => { if (handleShelfFocus) { handleShelfFocus(); } }, [handleShelfFocus]); const categoryData = useMemo(() => { if ( filteredRollingData.length > 0 && Object.keys(filteredRollingData[startIndex]).length > 0 && filteredRollingData[startIndex].shptmLnkTpCd === LINK_TYPES.CATEGORY ) { if (homeCategory && homeCategory.length > 0) { const foundCategory = homeCategory.find( (data) => data.lgCatCd === filteredRollingData[startIndex].lgCatCd ); if (foundCategory) { return { lgCatNm: foundCategory.lgCatNm, COUNT: foundCategory.COUNT, }; } } } return {}; }, [homeCategory, filteredRollingData, startIndex]); const { originalPrice, discountedPrice, discountRate, offerInfo } = usePriceInfo(filteredRollingData.length > 0 ? filteredRollingData[startIndex].priceInfo : {}) || {}; // Just For You 데이터에서 첫 번째 상품 추출 const justForYouProduct = useMemo(() => { if (foryouInfos && foryouInfos.length > 0) { const justForYouShelf = foryouInfos.find( (shelf) => shelf.recommendTpCd === 'JUSTFORYOU' ); if (justForYouShelf && justForYouShelf.productInfos && justForYouShelf.productInfos.length > 0) { return justForYouShelf.productInfos[0]; } } return null; }, [foryouInfos]); // Just For You 상품의 가격 정보 const justForYouPriceInfo = usePriceInfo( justForYouProduct && justForYouProduct.priceInfo ? justForYouProduct.priceInfo : '' ) || { originalPrice: '', discountedPrice: '', discountRate: null, offerInfo: '', }; const handlePushPanel = useCallback( (name, panelInfo) => { const isDetailPanel = name === panel_names.DETAIL_PANEL; const enrichedPanelInfo = isDetailPanel ? { sourcePanel: panel_names.HOME_PANEL, sourceMenu: SOURCE_MENUS.HOME_ROLLING_UNIT, ...panelInfo, } : panelInfo; dispatch(pushPanel({ name, panelInfo: enrichedPanelInfo })); }, [dispatch] ); const handleStartVideoPlayer = useCallback( (playerInfo) => { dispatch(startVideoPlayer(playerInfo)); }, [dispatch] ); const imageBannerClick = useCallback(() => { // 필터링된 데이터가 비어있으면 return if (!filteredRollingData || filteredRollingData.length === 0) { return; } if (bannerId) { dispatch(setBannerIndex(bannerId, startIndex)); } const currentData = filteredRollingData[startIndex]; const linkType = currentData.shptmLnkTpCd; const bannerType = currentData.shptmBanrTpNm; if (bannerType === "Today's Deals") { handlePushPanel(panel_names.DETAIL_PANEL, createPanelInfo(currentData)); sendBannerLog(true); dispatch( sendLogTopContents({ ...topContentsLogInfo, inDt: formatGMTString(new Date()) ?? '', logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK, }) ); return; } switch (linkType) { case LINK_TYPES.FEATURED_BRANDS: handlePushPanel(panel_names.FEATURED_BRANDS_PANEL, { from: 'gnb', patnrId: currentData.patnrId, }); break; case LINK_TYPES.TRENDING_NOW: handlePushPanel(panel_names.TRENDING_NOW_PANEL, createPanelInfo(currentData)); break; case LINK_TYPES.HOT_PICKS: if (playerPanelInfo?.modal) { dispatch(finishVideoPreview()); } handlePushPanel(panel_names.HOT_PICKS_PANEL, createPanelInfo(currentData)); break; case LINK_TYPES.ON_SALE: handlePushPanel(panel_names.ON_SALE_PANEL, { lgCatCd: currentData.lgCatCd, }); break; case LINK_TYPES.CATEGORY: if (Object.keys(categoryData).length > 0) { handlePushPanel(panel_names.CATEGORY_PANEL, createPanelInfo(currentData, categoryData)); } break; case LINK_TYPES.PRODUCT_DETAIL: case LINK_TYPES.SHOW_DETAIL: handlePushPanel(panel_names.DETAIL_PANEL, createPanelInfo(currentData)); break; case LINK_TYPES.VOD: handlePushPanel(panel_names.THEME_CURATION_PANEL, { curationId: currentData.lnkCurationId, }); break; case LINK_TYPES.THEME: handlePushPanel(panel_names.THEME_CURATION_PANEL, { curationId: currentData.lnkCurationId, }); break; case LINK_TYPES.JUSTFORYOU: handlePushPanel(panel_names.JUST_FOR_YOU_TEST_PANEL, createPanelInfo(currentData)); break; default: return; } sendBannerLog(true); dispatch( sendLogTopContents({ ...topContentsLogInfo, inDt: formatGMTString(new Date()) ?? '', logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK, }) ); }, [ filteredRollingData, startIndex, bannerId, dispatch, categoryData, handlePushPanel, sendBannerLog, topContentsLogInfo, ]); const videoClick = useCallback(() => { // 필터링된 데이터가 비어있으면 return if (!filteredRollingData || filteredRollingData.length === 0) { return; } const lastFocusedTargetId = getContainerId(Spotlight.getCurrent()); const currentSpot = Spotlight.getCurrent(); if (lastFocusedTargetId) { dispatch( updateHomeInfo({ name: panel_names.HOME_PANEL, panelInfo: { lastFocusedTargetId, focusedContainerId: TEMPLATE_CODE_CONF.TOP, currentSpot: currentSpot?.getAttribute('data-spotlight-id'), }, }) ); } if (bannerId) { dispatch(setBannerIndex(bannerId, startIndex)); } const currentData = filteredRollingData[startIndex]; handleStartVideoPlayer({ showUrl: currentData.showUrl, patnrId: currentData.patnrId, showId: currentData.showId, shptmBanrTpNm: currentData.showId ? currentData.shptmBanrTpNm : 'MEDIA', lgCatCd: currentData.lgCatCd, chanId: currentData.brdcChnlId, modal: false, modalContainerId: null, modalClassName: css.videoModal, }); sendBannerLog(); dispatch( sendLogTopContents({ ...topContentsLogInfo, inDt: formatGMTString(new Date()) ?? '', logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK, }) ); }, [filteredRollingData, startIndex, bannerId, dispatch, handleStartVideoPlayer, topContentsLogInfo]); // 10초 롤링 useEffect(() => { // 데이터가 1개인 경우 및 포커스 있는 경우 롤링 하지 않음 lastIndexRef.current = rollingDataLength - 1; previousTimeRef.current = undefined; if (rollingDataLength <= 1 || unitHasFocus) { doRollingRef.current = false; if (typeof window === 'object') { window.cancelAnimationFrame(requestRef.current); } return; } doRollingRef.current = true; if (typeof window === 'object') { requestRef.current = window.requestAnimationFrame(animate); } return () => { doRollingRef.current = false; if (typeof window === 'object') { window.cancelAnimationFrame(requestRef.current); } }; }, [rollingDataLength, unitHasFocus]); useEffect(() => { let _nowMenu = nowMenu; let _entryMenu = entryMenu; if (nowMenu === LOG_MENU.HOME_TOP) { const params = { ...topContentsLogInfo, entryMenu: _entryMenu, inDt: formatGMTString(new Date()) ?? '', logTpNo: LOG_TP_NO.TOP_CONTENTS.VIEW, nowMenu: _nowMenu, }; return () => { if (arrRef.current.every((number) => number !== startIndex)) { dispatch(sendLogTopContents(params)); arrRef.current.push(startIndex); } }; } }, [dispatch, entryMenu, nowMenu, startIndex, topContentsLogInfo]); useEffect(() => { sendBannerLog(); }, [filteredRollingDataRef, nowMenu, startIndex]); useEffect(() => { if (nowMenu !== LOG_MENU.HOME_TOP) { arrRef.current = []; } }, [nowMenu]); return ( {filteredRollingData.length !== 1 ? ( ) : null} {/* 일반 Image Banner (Just For You, Today's Deals 제외) */} {filteredRollingData && filteredRollingData.length > 0 && filteredRollingData[startIndex].shptmBanrTpNm === 'Image Banner' && filteredRollingData[startIndex].shptmLnkTpNm !== 'Just For You' && filteredRollingData[startIndex].shptmLnkTpNm !== "Today's Deals" ? (
) : filteredRollingData && filteredRollingData.length > 0 && filteredRollingData[startIndex].shptmBanrTpNm === 'LIVE' ? (

{filteredRollingData[startIndex].tmnlImgPath == null ? ( ) : ( )}
{filteredRollingData[startIndex].tmnlImgPath == null ? ( '' ) : ( )}

) : filteredRollingData && filteredRollingData.length > 0 && filteredRollingData[startIndex].shptmBanrTpNm === 'VOD' ? (
{filteredRollingData[startIndex].tmnlImgPath == null ? ( ) : ( )}
{filteredRollingData[startIndex].tmnlImgPath == null ? ( '' ) : ( )}
{filteredRollingData[startIndex]?.patncLogoPath && (

)}
) : filteredRollingData && filteredRollingData.length > 0 && (filteredRollingData[startIndex].shptmBanrTpNm === "Today's Deals" || filteredRollingData[startIndex].shptmLnkTpNm === "Today's Deals") ? (
{$L("TODAY's DEALS")}
{parseFloat(originalPrice?.replace('$', '')) === 0 ? filteredRollingData[startIndex].offerInfo : discountRate ? discountedPrice : originalPrice} {discountRate && !isHorizontal && ( {originalPrice} )}
{isHorizontal && parseFloat(originalPrice?.replace('$', '')) !== 0 && ( {originalPrice} )}
) : filteredRollingData && filteredRollingData.length > 0 && filteredRollingData[startIndex].shptmLnkTpNm === "Just For You" ? (
{$L("Just For You")}
{justForYouProduct ? ( <> {parseFloat(justForYouPriceInfo.originalPrice?.replace('$', '') || '0') === 0 ? justForYouProduct.offerInfo : justForYouPriceInfo.discountRate ? justForYouPriceInfo.discountedPrice : justForYouPriceInfo.originalPrice} {justForYouPriceInfo.discountRate && !isHorizontal && ( {justForYouPriceInfo.originalPrice} )} ) : ( <> {parseFloat(originalPrice?.replace('$', '') || '0') === 0 ? filteredRollingData[startIndex]?.offerInfo : discountRate ? discountedPrice : originalPrice} {discountRate && !isHorizontal && ( {originalPrice} )} )}
{isHorizontal && justForYouProduct && parseFloat(justForYouPriceInfo.originalPrice?.replace('$', '') || '0') !== 0 && ( {justForYouPriceInfo.originalPrice} )}
) : null} {filteredRollingData.length !== 1 ? ( ) : null} ); }