🕐 커밋 시간: 2025. 12. 16. 14:29:22 📊 변경 통계: • 총 파일: 2개 • 추가: +14줄 • 삭제: -3줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx ~ com.twin.app.shoptime/src/views/TrendingNowPanel/TrendingNowPanel.jsx
449 lines
14 KiB
JavaScript
449 lines
14 KiB
JavaScript
import React, {
|
|
useCallback,
|
|
useEffect,
|
|
useRef,
|
|
} from 'react';
|
|
|
|
import classNames from 'classnames';
|
|
import { useDispatch } from 'react-redux';
|
|
|
|
import Spotlight from '@enact/spotlight';
|
|
import SpotlightContainerDecorator
|
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
|
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
|
|
|
|
import {
|
|
sendLogGNB,
|
|
sendLogTotalRecommend,
|
|
} from '../../actions/logActions';
|
|
import { getTop20Show } from '../../actions/mainActions';
|
|
import {
|
|
pushPanel,
|
|
updatePanel,
|
|
} from '../../actions/panelActions';
|
|
import { finishVideoPreview } from '../../actions/playActions';
|
|
import { getBestSeller } from '../../actions/productActions';
|
|
import SectionTitle from '../../components/SectionTitle/SectionTitle';
|
|
import TBody from '../../components/TBody/TBody';
|
|
import TButton, { TYPES } from '../../components/TButton/TButton';
|
|
import { removeDotAndColon } from '../../components/TItemCard/TItemCard';
|
|
import TItemCardNew from '../../components/TItemCard/TItemCard.new';
|
|
import TPanel from '../../components/TPanel/TPanel';
|
|
import TVerticalPagenator
|
|
from '../../components/TVerticalPagenator/TVerticalPagenator';
|
|
import useWhyDidYouUpdate from '../../hooks/useWhyDidYouUpdate';
|
|
import {
|
|
LOG_CONTEXT_NAME,
|
|
LOG_MENU,
|
|
LOG_MESSAGE_ID,
|
|
panel_names,
|
|
} from '../../utils/Config';
|
|
import { $L } from '../../utils/helperMethods';
|
|
import { SpotlightIds } from '../../utils/SpotlightIds';
|
|
import PopularShowIndicator from './PopularShow/PopularShowIndicator';
|
|
import css from './TrendingNowPanel.module.less';
|
|
|
|
const Container = SpotlightContainerDecorator(
|
|
{ enterTo: "last-focused" },
|
|
"div"
|
|
);
|
|
const BestSellerContainer = SpotlightContainerDecorator(
|
|
{ enterTo: "last-focused" },
|
|
"div"
|
|
);
|
|
const STRING_CONF = {
|
|
POPULAR_SHOW: "POPULAR SHOW",
|
|
BEST_SELLER: "BEST SELLER",
|
|
};
|
|
|
|
const TrendingNowPanel = ({ panelInfo, spotlightId, isOnTop, ...rest }) => {
|
|
const dispatch = useDispatch();
|
|
const { USE_SELECTOR, USE_STATE } = useWhyDidYouUpdate(spotlightId, {
|
|
panelInfo,
|
|
isOnTop,
|
|
...rest,
|
|
});
|
|
|
|
const topInfos = USE_SELECTOR(
|
|
"topInfos",
|
|
(state) => state.main.top20ShowData?.topInfos
|
|
);
|
|
const bestSeller = USE_SELECTOR(
|
|
"bestSeller",
|
|
(state) => state.product?.bestSellerData?.bestSeller
|
|
);
|
|
|
|
const [showButton, setShowButton] = USE_STATE("showButton", true);
|
|
const [selectedIndex, setSelectedIndex] = USE_STATE("selectedIndex", 0);
|
|
|
|
const cbChangePageRef = useRef(null);
|
|
const isInitialRef = useRef(true);
|
|
const currentSentMenuRef = useRef(null);
|
|
const focusedContainerIdRef = useRef(panelInfo?.focusedContainerId);
|
|
|
|
// effect: data fetching when no data exists in the store
|
|
useEffect(() => {
|
|
if (!topInfos) dispatch(getTop20Show());
|
|
if (!bestSeller) dispatch(getBestSeller());
|
|
}, []);
|
|
|
|
// effect: focus handling on initial render
|
|
useEffect(() => {
|
|
if (isInitialRef.current && !panelInfo?.currentSpot) {
|
|
if (topInfos && bestSeller) {
|
|
let target;
|
|
switch (panelInfo?.pageName) {
|
|
case "PS":
|
|
target = SpotlightIds.TRENDING_NOW_POPULAR_SHOW;
|
|
break;
|
|
case "BS":
|
|
target = SpotlightIds.TRENDING_NOW_BEST_SELLER;
|
|
break;
|
|
}
|
|
|
|
Spotlight.focus(target ?? spotlightId);
|
|
isInitialRef.current = false;
|
|
}
|
|
}
|
|
}, [topInfos, bestSeller]);
|
|
|
|
// effect: set the index when returning to the details or entering through a deep-link
|
|
useEffect(() => {
|
|
if (panelInfo?.selectedIndex) setSelectedIndex(panelInfo.selectedIndex);
|
|
}, []);
|
|
|
|
// effect: focus recovery when returning to the details
|
|
useEffect(() => {
|
|
if (isOnTop && panelInfo?.currentSpot) {
|
|
Spotlight.focus(panelInfo.currentSpot);
|
|
}
|
|
}, [isOnTop]);
|
|
|
|
// effect: for the GNB log
|
|
useEffect(() => {
|
|
if (isOnTop) {
|
|
let menu;
|
|
// case: entering through a deep link or the Home, and returning from the player or details
|
|
if (panelInfo?.focusedContainerId) {
|
|
menu =
|
|
panelInfo.focusedContainerId !==
|
|
SpotlightIds.TRENDING_NOW_POPULAR_SHOW
|
|
? LOG_MENU.TRENDING_NOW_BEST_SELLER
|
|
: LOG_MENU.TRENDING_NOW_POPULAR_SHOWS;
|
|
}
|
|
// case: entering through the GNB
|
|
else {
|
|
menu = LOG_MENU.TRENDING_NOW_POPULAR_SHOWS;
|
|
}
|
|
|
|
if (menu) dispatch(sendLogGNB(menu));
|
|
}
|
|
}, [isOnTop]);
|
|
|
|
const onFocusedContainerId = useCallback((containerId) => {
|
|
focusedContainerIdRef.current = containerId;
|
|
}, []);
|
|
|
|
const handleScroll = useCallback((e) => {
|
|
setShowButton(e.scrollTop === 0);
|
|
|
|
if (e.scrollTop !== 0) dispatch(finishVideoPreview());
|
|
}, []);
|
|
|
|
const handleSpotlight = useCallback(
|
|
(spotlightId) => (e) => {
|
|
e.stopPropagation();
|
|
if (spotlightId) Spotlight.focus(spotlightId);
|
|
},
|
|
[]
|
|
);
|
|
|
|
const handleIndicatorClick = useCallback(
|
|
(direction) => () => {
|
|
if (!topInfos) return;
|
|
|
|
const isPrev = direction === "prev";
|
|
const isNext = direction === "next";
|
|
const isEdgeIndex =
|
|
(isPrev && selectedIndex === 1) ||
|
|
(isNext && selectedIndex === topInfos.length - 2);
|
|
|
|
if (isEdgeIndex) Spotlight.focus(SpotlightIds.TRENDING_NOW_POPULAR_VIDEO);
|
|
|
|
setSelectedIndex((prev) => prev + (isPrev ? -1 : isNext ? 1 : 0));
|
|
setContainerLastFocusedElement(null, [
|
|
SpotlightIds.TRENDING_NOW_POPULAR_GRID_LIST,
|
|
]);
|
|
},
|
|
[selectedIndex, topInfos]
|
|
);
|
|
|
|
const handleBestSellerClick = useCallback(
|
|
({ patnrId, prdtId }) =>
|
|
() => {
|
|
const currentSpot =
|
|
Spotlight.getCurrent()?.getAttribute("data-spotlight-id") || null;
|
|
|
|
dispatch(
|
|
updatePanel({
|
|
name: panel_names.TRENDING_NOW_PANEL,
|
|
panelInfo: {
|
|
currentSpot,
|
|
focusedContainerId: focusedContainerIdRef.current,
|
|
selectedIndex,
|
|
},
|
|
})
|
|
);
|
|
|
|
dispatch(
|
|
pushPanel({
|
|
name: panel_names.DETAIL_PANEL,
|
|
panelInfo: { patnrId, prdtId },
|
|
})
|
|
);
|
|
},
|
|
[selectedIndex]
|
|
);
|
|
|
|
const handleBestSellerFocus = useCallback(() => {
|
|
handleItemFocus(LOG_MENU.TRENDING_NOW_BEST_SELLER, STRING_CONF.BEST_SELLER);
|
|
}, [handleItemFocus]);
|
|
|
|
const handleItemClick = useCallback(() => {
|
|
const currentSpot =
|
|
Spotlight.getCurrent()?.getAttribute("data-spotlight-id") || null;
|
|
|
|
dispatch(
|
|
updatePanel({
|
|
name: panel_names.TRENDING_NOW_PANEL,
|
|
panelInfo: {
|
|
currentSpot,
|
|
focusedContainerId: focusedContainerIdRef.current,
|
|
selectedIndex,
|
|
},
|
|
})
|
|
);
|
|
}, [selectedIndex]);
|
|
|
|
const handleItemFocus = useCallback(
|
|
(nowMenu, currentShelf) => {
|
|
setTimeout(() => dispatch(sendLogGNB(nowMenu)));
|
|
|
|
if (nowMenu !== currentSentMenuRef.current) {
|
|
let order;
|
|
let spotlightId;
|
|
switch (currentShelf) {
|
|
case STRING_CONF.POPULAR_SHOW:
|
|
order = 1;
|
|
spotlightId = SpotlightIds.TRENDING_NOW_POPULAR_SHOW;
|
|
break;
|
|
case STRING_CONF.BEST_SELLER:
|
|
order = 2;
|
|
spotlightId = SpotlightIds.TRENDING_NOW_BEST_SELLER;
|
|
break;
|
|
}
|
|
|
|
if (order && spotlightId) {
|
|
const params = {
|
|
contextName: LOG_CONTEXT_NAME.TRENDING_NOW,
|
|
messageId: LOG_MESSAGE_ID.SHELF,
|
|
shelfLocation: order,
|
|
shelfId: spotlightId,
|
|
shelfTitle: currentShelf,
|
|
};
|
|
dispatch(sendLogTotalRecommend(params));
|
|
|
|
currentSentMenuRef.current = nowMenu;
|
|
}
|
|
}
|
|
},
|
|
[currentSentMenuRef]
|
|
);
|
|
|
|
const handleTopButtonClick = useCallback(() => {
|
|
if (cbChangePageRef.current) {
|
|
cbChangePageRef.current(0, true);
|
|
}
|
|
|
|
let target = SpotlightIds.TRENDING_NOW_POPULAR_VIDEO;
|
|
|
|
if (topInfos.length === 0 && bestSeller && bestSeller.length > 0) {
|
|
target = `spotlightId-bestSeller-${bestSeller[0]?.prdtId}`;
|
|
}
|
|
Spotlight.focus(target);
|
|
setContainerLastFocusedElement(null, [
|
|
SpotlightIds.TRENDING_NOW_BEST_SELLER,
|
|
]);
|
|
}, [bestSeller, topInfos]);
|
|
|
|
const getFirstLabel = useCallback((rankOrder) => {
|
|
switch (rankOrder) {
|
|
case 1:
|
|
return `${rankOrder}st,`;
|
|
case 2:
|
|
return `${rankOrder}nd,`;
|
|
case 3:
|
|
return `${rankOrder}rd,`;
|
|
default:
|
|
return `${rankOrder}th,`;
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<div className={css.trendingNowWrap}>
|
|
{isOnTop && selectedIndex >= 1 && showButton && (
|
|
<TButton
|
|
className={classNames(css.button, css.prevBtn)}
|
|
onClick={handleIndicatorClick("prev")}
|
|
onSpotlightRight={handleSpotlight(
|
|
SpotlightIds.TRENDING_NOW_POPULAR_VIDEO
|
|
)}
|
|
onSpotlightDown={handleSpotlight(
|
|
SpotlightIds.TRENDING_NOW_BEST_SELLER
|
|
)}
|
|
spotlightId={SpotlightIds.TRENDING_NOW_PREV_INDICATOR}
|
|
ariaLabel="Move to left"
|
|
/>
|
|
)}
|
|
<TPanel spotlightId={spotlightId}>
|
|
<TBody
|
|
className={css.trendingNow}
|
|
scrollable={false}
|
|
spotlightDisabled={!isOnTop}
|
|
>
|
|
<TVerticalPagenator
|
|
className={css.tVerticalPagenator}
|
|
spotlightId={SpotlightIds.TRENDING_NOW_VERTICAL_PAGINATOR}
|
|
defaultContainerId={panelInfo?.focusedContainerId}
|
|
onScroll={handleScroll}
|
|
onFocusedContainerId={onFocusedContainerId}
|
|
cbChangePageRef={cbChangePageRef}
|
|
topMargin={36}
|
|
>
|
|
{topInfos && topInfos?.length > 0 && (
|
|
<Container
|
|
className={css.popularContainer}
|
|
spotlightId={SpotlightIds.TRENDING_NOW_POPULAR_SHOW}
|
|
data-wheel-point={true}
|
|
>
|
|
<SectionTitle
|
|
title={$L(STRING_CONF.POPULAR_SHOW)}
|
|
label="POPULAR SHOW, Heading1"
|
|
/>
|
|
<PopularShowIndicator
|
|
topInfos={topInfos}
|
|
selectedIndex={selectedIndex}
|
|
onSpotlightDown={handleSpotlight(
|
|
SpotlightIds.TRENDING_NOW_BEST_SELLER
|
|
)}
|
|
onSpotlightRight={handleSpotlight(
|
|
SpotlightIds.TRENDING_NOW_NEXT_INDICATOR
|
|
)}
|
|
handleItemFocus={handleItemFocus}
|
|
handleItemClick={handleItemClick}
|
|
isOnTop={isOnTop}
|
|
currentShelf={STRING_CONF.POPULAR_SHOW}
|
|
/>
|
|
</Container>
|
|
)}
|
|
{bestSeller && bestSeller?.length > 0 && (
|
|
<BestSellerContainer
|
|
className={css.bestContainer}
|
|
spotlightId={SpotlightIds.TRENDING_NOW_BEST_SELLER}
|
|
data-wheel-point
|
|
>
|
|
<SectionTitle
|
|
title={$L(STRING_CONF.BEST_SELLER)}
|
|
data-title-index="TNBestSellerTitle"
|
|
/>
|
|
<div className={css.itemList}>
|
|
{bestSeller.map((item, index) => (
|
|
<TItemCardNew
|
|
className={css.trendingBestItem}
|
|
contextName={LOG_CONTEXT_NAME.TRENDING_NOW}
|
|
messageId={LOG_MESSAGE_ID.SHELF_CLICK}
|
|
shelfLocation={2}
|
|
shelfTitle={STRING_CONF.BEST_SELLER}
|
|
shelfId={SpotlightIds.TRENDING_NOW_BEST_SELLER}
|
|
order={item.rankOrd}
|
|
catNm={item.lgCatNm}
|
|
patnerName={item.patncNm}
|
|
brandName={item.brndNm}
|
|
data-wheel-point={index >= 5}
|
|
firstLabel={getFirstLabel(item.rankOrd)}
|
|
imageAlt={item.prdtId}
|
|
imageSource={item.imgUrl}
|
|
key={item.prdtId}
|
|
isBestSeller
|
|
label={index * 1 + 1 + " of " + bestSeller.length}
|
|
lastLabel=" go to detail, button"
|
|
offerInfo={item.offerInfo}
|
|
onFocus={handleBestSellerFocus}
|
|
onClick={handleBestSellerClick(item)}
|
|
priceInfo={item.priceInfo}
|
|
productName={item.prdtNm}
|
|
productId={item.prdtId}
|
|
rank={item.rankOrd}
|
|
spotlightId={
|
|
"spotlightId-" +
|
|
"bestSeller-" +
|
|
removeDotAndColon(item.prdtId)
|
|
}
|
|
euEnrgLblInfos={item.euEnrgLblInfos}
|
|
/>
|
|
))}
|
|
</div>
|
|
</BestSellerContainer>
|
|
)}
|
|
<Container>
|
|
{((topInfos && topInfos?.length > 0) ||
|
|
(bestSeller && bestSeller?.length > 10)) && (
|
|
<TButton
|
|
onClick={handleTopButtonClick}
|
|
size={null}
|
|
spotlightId={SpotlightIds.TRENDING_NOW_TOP_BUTTON}
|
|
ariaLabel="Move to Top"
|
|
data-wheel-point
|
|
type={TYPES.topButton}
|
|
/>
|
|
)}
|
|
</Container>
|
|
</TVerticalPagenator>
|
|
</TBody>
|
|
</TPanel>
|
|
{isOnTop && topInfos &&
|
|
topInfos?.length > 0 &&
|
|
selectedIndex !== topInfos?.length - 1 &&
|
|
showButton && (
|
|
<TButton
|
|
className={classNames(css.button, css.nextBtn)}
|
|
onClick={handleIndicatorClick("next")}
|
|
onSpotlightDown={handleSpotlight(
|
|
SpotlightIds.TRENDING_NOW_BEST_SELLER
|
|
)}
|
|
spotlightId={SpotlightIds.TRENDING_NOW_NEXT_INDICATOR}
|
|
ariaLabel="Move to right"
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
const propsAreEqual = (prev, next) => {
|
|
const keys = Object.keys(prev);
|
|
const nextKeys = Object.keys(next);
|
|
// if (!next.isOnTop) {
|
|
// //ignore event on background
|
|
// return true;
|
|
// }
|
|
if (keys.length !== nextKeys.length) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < keys.length; i++) {
|
|
if (prev[keys[i]] !== next[keys[i]]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
export default React.memo(TrendingNowPanel, propsAreEqual);
|