Files
shoptime/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx
optrader db7bc4b2ed [251210] feat: brandBestSelleInfo 처리
🕐 커밋 시간: 2025. 12. 10. 12:28:02

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

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/brandActions.js
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBestSeller/FeaturedBestSeller.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
2025-12-10 12:28:03 +09:00

1105 lines
39 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import classNames from "classnames";
import { useDispatch, useSelector } from "react-redux";
import { Job } from "@enact/core/util";
import Spotlight from "@enact/spotlight";
import { types } from "../../actions/actionTypes";
import {
getBrandBestSeller,
getBrandCategoryInfo,
getBrandCategoryProductInfo,
getBrandCreatorsInfo,
getBrandLayoutInfo,
getBrandList,
getBrandLiveChannelInfo,
getBrandRecommendedShowInfo,
getBrandSeriesInfo,
getBrandShopByShow,
getBrandShowroom,
getBrandTSVInfo,
} from "../../actions/brandActions";
import { changeAppStatus, setHidePopup } from "../../actions/commonActions";
import {
sendLogGNB,
sendLogPartners,
sendLogTotalRecommend,
} from "../../actions/logActions";
import { setMainLiveUpcomingAlarm } from "../../actions/mainActions";
import {
deleteMyUpcomingAlertShow,
getMyUpcomingAlertShow,
setMyUpcomingUseAlert,
} from "../../actions/myPageActions";
import { updatePanel } from "../../actions/panelActions";
import TBody from "../../components/TBody/TBody";
import TButton, { TYPES } from "../../components/TButton/TButton";
import TPanel from "../../components/TPanel/TPanel";
import TPopUp from "../../components/TPopUp/TPopUp";
import TVerticalPagenator from "../../components/TVerticalPagenator/TVerticalPagenator";
import usePrevious from "../../hooks/usePrevious";
import {
ACTIVE_POPUP,
LOG_CONTEXT_NAME,
LOG_MENU,
LOG_MESSAGE_ID,
panel_names,
} from "../../utils/Config";
import { $L } from "../../utils/helperMethods";
import { SpotlightIds } from "../../utils/SpotlightIds";
import Banner from "./Banner/Banner";
import FeaturedBestSeller from "./FeaturedBestSeller/FeaturedBestSeller";
import css from "./FeaturedBrandsPanel.module.less";
import FeaturedCategory from "./FeaturedCategory/FeaturedCategory";
import FeaturedCreators from "./FeaturedCreators/FeaturedCreators";
import LiveChannels from "./LiveChannels/LiveChannels";
import QuickMenu from "./QuickMenu/QuickMenu";
import RecommendedShows from "./RecommendedShows/RecommendedShows";
import Series from "./Series/Series";
import ShopByShow from "./ShopByShow/ShopByShow";
import Showroom from "./Showroom/Showroom";
import TodaysDeals from "./TodaysDeals/TodaysDeals";
import UpComing from "./UpComing/UpComing";
import { setContainerLastFocusedElement } from "@enact/spotlight/src/container";
import { sortedIndexOf } from "lodash";
const STRING_CONF = {
CANCEL: "CANCEL",
OK: "OK",
YES: "YES",
NO_SHOW_MESSAGE: "This show doesnt exist anymore",
REMINDER_ON_OFF_MESSAGE:
"Reminder Notification is turned off. Select Yes to turn on reminders.",
UPCOMING_TIME_CONFLICT_MESSAGE:
"A Reminder is already set on the selected schedule. Press OK to overwrite the existing Reminder.",
};
const TEMPLATE_CODE_CONF = {
LIVE_CHANNELS: "BRD00101",
UP_COMING: "BRD00102",
TODAYS_DEALS: "BRD00103",
BEST_SELLER: "BRD00104",
RECOMMENDED_SHOWS: "BRD00105",
FEATURED_CREATORS: "BRD00106",
SERIES: "BRD00107",
CATEGORY: "BRD00108",
SHOWROOM: "BRD00109",
NBCU: "BRD00110",
};
const DISPATCH_MAP = Object.freeze({
[TEMPLATE_CODE_CONF.LIVE_CHANNELS]: getBrandLiveChannelInfo,
[TEMPLATE_CODE_CONF.TODAYS_DEALS]: getBrandTSVInfo,
[TEMPLATE_CODE_CONF.BEST_SELLER]: getBrandBestSeller,
[TEMPLATE_CODE_CONF.RECOMMENDED_SHOWS]: getBrandRecommendedShowInfo,
[TEMPLATE_CODE_CONF.FEATURED_CREATORS]: getBrandCreatorsInfo,
[TEMPLATE_CODE_CONF.SERIES]: getBrandSeriesInfo,
[TEMPLATE_CODE_CONF.CATEGORY]: getBrandCategoryInfo,
[TEMPLATE_CODE_CONF.SHOWROOM]: getBrandShowroom,
[TEMPLATE_CODE_CONF.NBCU]: getBrandShopByShow,
});
const TOP_MARGIN = 36;
const hasTemplateCodeWithValue = (array, value) =>
array?.some((obj) => obj?.shptmBrndOptTpCd === value) ?? false;
const shouldRenderComponent = (data) => {
return (
(Array.isArray(data) && data.length > 0) ||
(data && typeof data === "object" && Object.keys(data).length > 0)
);
};
const findSelector = (selector, maxAttempts = 5, currentAttempts = 0) => {
try {
if (currentAttempts >= maxAttempts) {
throw new Error("selector not found");
}
const initialSelector = document.querySelector(selector);
if (initialSelector) {
return initialSelector;
} else {
return findSelector(selector, maxAttempts, currentAttempts + 1);
}
} catch (error) {
console.warn(error.message);
}
};
const findAndFocusFirstContainer = (timerRef) => {
if (typeof window === "object") {
const containers = document.querySelectorAll("[data-wheel-point]");
const firstContainer = Array.from(containers) //
.find((container) => container?.getAttribute("data-shelf-order") === "1");
if (firstContainer) {
Spotlight.focus(firstContainer);
clearTimeout(timerRef.current?.timer);
timerRef.current.timer = null;
timerRef.current.attemptCount = 0;
return;
}
if (timerRef.current.attemptCount < 5) {
timerRef.current.attemptCount += 1;
timerRef.current.timer = setTimeout(
() => findAndFocusFirstContainer(timerRef),
200
);
}
}
};
const getMessageByPopupType = (type) => {
switch (type) {
case ACTIVE_POPUP.reminderPopup:
return $L(STRING_CONF.REMINDER_ON_OFF_MESSAGE);
case ACTIVE_POPUP.timeConflictPopup:
return $L(STRING_CONF.UPCOMING_TIME_CONFLICT_MESSAGE);
case ACTIVE_POPUP.noShowPopup:
return $L(STRING_CONF.NO_SHOW_MESSAGE);
default:
return;
}
};
const getMenuByContainerId = (containerId) => {
if (!containerId) return;
const splittedId = containerId.split("-")[0];
switch (splittedId) {
case SpotlightIds.BRAND_QUICK_MENU:
return LOG_MENU.FEATURED_BRANDS_QUICK_MENU;
case TEMPLATE_CODE_CONF.LIVE_CHANNELS:
return LOG_MENU.FEATURED_BRANDS_LIVE_CHANNELS;
case TEMPLATE_CODE_CONF.UP_COMING:
return LOG_MENU.FEATURED_BRANDS_UPCOMING;
case TEMPLATE_CODE_CONF.TODAYS_DEALS:
return LOG_MENU.FEATURED_BRANDS_TODAYS_DEALS;
case TEMPLATE_CODE_CONF.BEST_SELLER:
return LOG_MENU.FEATURED_BRANDS_BEST_SELLER;
case TEMPLATE_CODE_CONF.RECOMMENDED_SHOWS:
return LOG_MENU.FEATURED_BRANDS_RECOMMENDED_SHOWS;
case TEMPLATE_CODE_CONF.FEATURED_CREATORS:
return LOG_MENU.FEATURED_BRANDS_FEATURED_CREATORS;
case TEMPLATE_CODE_CONF.SERIES:
return LOG_MENU.FEATURED_BRANDS_SERIES;
case TEMPLATE_CODE_CONF.CATEGORY:
return LOG_MENU.FEATURED_BRANDS_CATEGORY;
case TEMPLATE_CODE_CONF.SHOWROOM:
return LOG_MENU.FEATURED_BRANDS_SHOWROOM;
default:
return;
}
};
const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
const dispatch = useDispatch();
const cursorVisible = useSelector(
(state) => state.common.appStatus.cursorVisible
);
const activePopup = useSelector((state) => state.common.popup.activePopup);
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
const brandInfo = useSelector(
(state) => state.brand.brandInfoData.data.brandInfo
);
const brandTopImgInfo = useSelector(
(state) => state.brand.brandLayoutInfoData.data.brandTopImgInfo
);
const brandLayoutInfo = useSelector(
(state) => state.brand.brandLayoutInfoData.data.brandLayoutInfo
);
const brandChanInfo = useSelector(
(state) => state.brand.brandLiveChannelInfoData.data.brandChanInfo
);
const brandChannelCnt = useSelector(
(state) => state.brand.brandLiveChannelInfoData.data.brandChannelCnt
);
const brandLiveChannelUpcoming = useSelector(
(state) =>
state.brand.brandLiveChannelInfoData.data.brandLiveChannelUpcoming
);
const brandTsvInfo = useSelector(
(state) => state.brand.brandTsvInfoData.data.brandTsvInfo
);
const brandBestSellerInfo = useSelector(
(state) => state.brand.brandBestSellerData.data.brandBestSellerInfo
);
const brandBestSellerTitle = useSelector(
(state) => state.brand.brandBestSellerData.data.brandBestSellerTitle
);
const brandRecommendedShowCategoryInfo = useSelector(
(state) =>
state.brand.brandRecommendedShowInfoData.data
.brandRecommendedShowCategoryInfo
);
const brandRecommendedShowInfo = useSelector(
(state) =>
state.brand.brandRecommendedShowInfoData.data.brandRecommendedShowInfo
);
const brandCreatorsInfo = useSelector(
(state) => state.brand.brandCreatorsInfoData.data.brandCreatorsInfo
);
const barndCreatorsShowInfo = useSelector(
(state) => state.brand.brandCreatorsInfoData.data.barndCreatorsShowInfo
);
const brandSeriesGroupInfo = useSelector(
(state) => state.brand.brandSeriesInfoData.data.brandSeriesGroupInfo
);
const brandSeriesInfo = useSelector(
(state) => state.brand.brandSeriesInfoData.data.brandSeriesInfo
);
const brandCategoryInfo = useSelector(
(state) => state.brand.brandCategoryInfoData.data.brandCategoryInfo
);
const brandCategoryProductInfo = useSelector(
(state) => state.brand.brandCategoryInfoData.data.brandCategoryProductInfo
);
const brandShowroomInfo = useSelector(
(state) => state.brand.brandShowroomData.data.brandShowroomInfo
);
const brandShopByShowContsList = useSelector(
(state) => state.brand.brandShopByShowData.data.brandShopByShowContsList
);
const brandShopByShowContsInfo = useSelector(
(state) => state.brand.brandShopByShowData.data.brandShopByShowContsInfo
);
const [displayTopButton, setDisplayTopButton] = useState(false);
const [focusedContainerId, setFocusedContainerId] = useState(null);
const [isInitialFocusOccurred, setIsInitialFocusOccurred] = useState(null);
const [isLogGNBSent, setIsLogGNBSent] = useState(false);
const [selectedPatncNm, setSelectedPatncNm] = useState(null);
const [selectedPatnrId, setSelectedPatnrId] = useState(null);
const [selectedCatCd, setSelectedCatCd] = useState(null);
const [selectedHstNm, setSelectedHstNm] = useState(null);
const [selectedSeriesId, setSelectedSeriesId] = useState(null);
const [selectedCatCdLv1, setSelectedCatCdLv1] = useState(null);
const [selectedCatCdLv2, setSelectedCatCdLv2] = useState(null);
const brandInfoRef = usePrevious(brandInfo);
const displayTopButtonRef = usePrevious(displayTopButton);
const focusedContainerIdRef = usePrevious(focusedContainerId);
const initialFocusTimeoutJob = useRef(new Job((func) => func(), 0));
const alarmTimer = useRef(null);
const cbChangePageRef = useRef(null);
const lastMenuRef = useRef(null);
const orderableFlexContainerRef = useRef(null);
const prevSelectedPatncNmRef = useRef(null);
const noneTargetTimer = useRef({
timer: null,
attemptCount: 0,
});
const renderedShelfCountRef = useRef(0);
const fromDetail = panelInfo?.from && panelInfo.from === "detail";
const fromGNB = panelInfo?.from && panelInfo.from === "gnb";
const fromUpcoming = panelInfo?.from && panelInfo.from === "upcoming";
const fromQuickMenu = panelInfo?.from && panelInfo.from === "menu";
const hasQuickMenu = useMemo(
() => brandInfo && brandInfo.length > 1,
[brandInfo]
);
const sortedBrandLayoutInfo = useMemo(
() => brandLayoutInfo?.sort((a, b) => a.expsOrd - b.expsOrd) ?? [],
[brandLayoutInfo]
);
const doSendLogGNB = useCallback(
(containerId, shelfOrder) => {
if (
!selectedPatncNm ||
!getMenuByContainerId(containerId) ||
(getMenuByContainerId(containerId) === lastMenuRef.current &&
selectedPatncNm === prevSelectedPatncNmRef.current)
) {
return;
}
const templateCode = containerId?.split("-")[0] || containerId;
const foundElement = sortedBrandLayoutInfo.find(
(el) => el.shptmBrndOptTpCd === templateCode
);
const actualShelfOrder = foundElement ? foundElement.expsOrd : 0;
const selectedBrand = `${LOG_MENU.FEATURED_BRANDS}/${selectedPatncNm}`;
const currentShelf = `${getMenuByContainerId(containerId)}`;
const menu =
selectedBrand && currentShelf && `${selectedBrand} ${currentShelf}`;
dispatch(
sendLogTotalRecommend({
contextName: LOG_CONTEXT_NAME.FEATURED_BRANDS,
messageId: LOG_MESSAGE_ID.SHELF,
partner: selectedPatncNm,
shelfLocation: actualShelfOrder,
shelfId: containerId,
shelfTitle: currentShelf,
})
);
dispatch(sendLogGNB(menu));
setIsLogGNBSent(true);
prevSelectedPatncNmRef.current = selectedPatncNm;
lastMenuRef.current = getMenuByContainerId(containerId);
},
[selectedPatncNm, sortedBrandLayoutInfo]
);
const focusOnMount = useCallback((targetId) => {
initialFocusTimeoutJob.current.start(() => {
const focusTarget = findSelector(`[data-spotlight-id="${targetId}"]`);
if (focusTarget) Spotlight.focus(focusTarget);
else findAndFocusFirstContainer(noneTargetTimer);
});
}, []);
const handleItemFocus = useCallback(
(containerId, shelfOrder) => doSendLogGNB(containerId, shelfOrder),
[doSendLogGNB]
);
const handlePopupClick = useCallback(() => {
if (activePopup?.type === ACTIVE_POPUP.reminderPopup) {
dispatch(setMyUpcomingUseAlert({ upcomingAlamUseFlag: "Y" }));
}
if (activePopup?.type === ACTIVE_POPUP.timeConflictPopup) {
const { upcomingAlarmInfo, deletedAlertShows } = activePopup;
dispatch(setMainLiveUpcomingAlarm(upcomingAlarmInfo));
dispatch(deleteMyUpcomingAlertShow({ showList: deletedAlertShows }));
alarmTimer.current = setTimeout(
() => dispatch(getMyUpcomingAlertShow()),
200
);
}
dispatch(setHidePopup());
setTimeout(() => Spotlight.focus());
}, [dispatch, activePopup]);
const handlePopupClose = useCallback(() => {
dispatch(setHidePopup());
setTimeout(() => Spotlight.focus());
}, [dispatch]);
const handleTopButtonClick = useCallback(() => {
if (cbChangePageRef.current) cbChangePageRef.current(0, true);
let target;
if (hasQuickMenu) {
target = document.querySelector(`[data-menu-index="${0}"]`);
} else {
const paginatorItem = document.querySelector(`[data-wheel-point]`);
const spotlightItem = paginatorItem?.querySelector(`[data-spotlight-id]`);
target = spotlightItem?.querySelector(`[tabindex="${-1}"]`);
}
if (target) Spotlight.focus(target);
}, [hasQuickMenu]);
const onFocusedContainerId = useCallback(
(containerId) => setFocusedContainerId(containerId),
[]
);
const renderPageItem = useCallback(() => {
console.log("[FeaturedBrandsPanel] renderPageItem - sortedBrandLayoutInfo length:", sortedBrandLayoutInfo.length);
console.log("[FeaturedBrandsPanel] renderPageItem - sortedBrandLayoutInfo items:", sortedBrandLayoutInfo.map(el => el.shptmBrndOptTpCd));
return (
<>
{sortedBrandLayoutInfo.map((el, idx) => {
console.log("[FeaturedBrandsPanel] Processing template code:", el.shptmBrndOptTpCd);
switch (el.shptmBrndOptTpCd) {
case TEMPLATE_CODE_CONF.LIVE_CHANNELS: {
return (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
sortedBrandLayoutInfo,
TEMPLATE_CODE_CONF.LIVE_CHANNELS
) &&
shouldRenderComponent(brandChanInfo) && (
<LiveChannels
brandChanInfo={brandChanInfo}
brandChannelCnt={brandChannelCnt}
handleItemFocus={handleItemFocus}
isOnTop={isOnTop}
order={idx + 1}
shelfOrder={el.expsOrd}
shelfTitle={el.shptmBrndOptTpNm}
spotlightId={TEMPLATE_CODE_CONF.LIVE_CHANNELS}
selectedPatnrId={selectedPatnrId}
/>
)}
</React.Fragment>
);
}
case TEMPLATE_CODE_CONF.UP_COMING: {
return (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
sortedBrandLayoutInfo,
TEMPLATE_CODE_CONF.UP_COMING
) &&
shouldRenderComponent(brandLiveChannelUpcoming) && (
<UpComing
brandLiveChannelUpcoming={brandLiveChannelUpcoming}
handleItemFocus={handleItemFocus}
order={idx + 1}
selectedPatnrId={selectedPatnrId}
shelfOrder={el.expsOrd}
shelfTitle={el.shptmBrndOptTpNm}
spotlightId={TEMPLATE_CODE_CONF.UP_COMING}
/>
)}
</React.Fragment>
);
}
case TEMPLATE_CODE_CONF.TODAYS_DEALS: {
return (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
sortedBrandLayoutInfo,
TEMPLATE_CODE_CONF.TODAYS_DEALS
) &&
shouldRenderComponent(brandTsvInfo) && (
<TodaysDeals
brandTsvInfo={brandTsvInfo}
handleItemFocus={handleItemFocus}
hasQuickMenu={hasQuickMenu}
order={idx + 1}
shelfOrder={el.expsOrd}
shelfTitle={el.shptmBrndOptTpNm}
spotlightId={TEMPLATE_CODE_CONF.TODAYS_DEALS}
/>
)}
</React.Fragment>
);
}
case TEMPLATE_CODE_CONF.BEST_SELLER: {
console.log("[FeaturedBrandsPanel] BEST_SELLER - patnrId:", selectedPatnrId);
console.log("[FeaturedBrandsPanel] BEST_SELLER - hasTemplateCode:", hasTemplateCodeWithValue(sortedBrandLayoutInfo, TEMPLATE_CODE_CONF.BEST_SELLER));
console.log("[FeaturedBrandsPanel] BEST_SELLER - shouldRender:", shouldRenderComponent(brandBestSellerInfo));
console.log("[FeaturedBrandsPanel] BEST_SELLER - data:", brandBestSellerInfo);
return (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
sortedBrandLayoutInfo,
TEMPLATE_CODE_CONF.BEST_SELLER
) &&
shouldRenderComponent(brandBestSellerInfo) && (
<>
{console.log("[FeaturedBrandsPanel] Rendering FeaturedBestSeller for patnrId:", selectedPatnrId)}
<FeaturedBestSeller
brandBestSellerInfo={brandBestSellerInfo}
brandBestSellerTitle={brandBestSellerTitle}
handleItemFocus={handleItemFocus}
order={idx + 1}
shelfOrder={el.expsOrd}
shelfTitle={el.shptmBrndOptTpNm}
spotlightId={TEMPLATE_CODE_CONF.BEST_SELLER}
selectedPatnrId={selectedPatnrId}
/>
</>
)}
</React.Fragment>
);
}
case TEMPLATE_CODE_CONF.RECOMMENDED_SHOWS: {
return (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
sortedBrandLayoutInfo,
TEMPLATE_CODE_CONF.RECOMMENDED_SHOWS
) &&
shouldRenderComponent(brandRecommendedShowCategoryInfo) &&
shouldRenderComponent(brandRecommendedShowInfo) && (
<RecommendedShows
brandRecommendedShowCategoryInfo={
brandRecommendedShowCategoryInfo
}
brandRecommendedShowInfo={brandRecommendedShowInfo}
fromGNB={fromGNB}
fromQuickMenu={fromQuickMenu}
handleItemFocus={handleItemFocus}
order={idx + 1}
shelfOrder={el.expsOrd}
shelfTitle={el.shptmBrndOptTpNm}
spotlightId={TEMPLATE_CODE_CONF.RECOMMENDED_SHOWS}
selectedCatCd={selectedCatCd}
selectedPatncNm={selectedPatncNm}
selectedPatnrId={selectedPatnrId}
setSelectedCatCd={setSelectedCatCd}
/>
)}
</React.Fragment>
);
}
case TEMPLATE_CODE_CONF.FEATURED_CREATORS: {
return (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
sortedBrandLayoutInfo,
TEMPLATE_CODE_CONF.FEATURED_CREATORS
) &&
shouldRenderComponent(brandCreatorsInfo) &&
shouldRenderComponent(barndCreatorsShowInfo) && (
<FeaturedCreators
brandCreatorsInfo={brandCreatorsInfo}
barndCreatorsShowInfo={barndCreatorsShowInfo}
fromGNB={fromGNB}
fromQuickMenu={fromQuickMenu}
handleItemFocus={handleItemFocus}
order={idx + 1}
shelfOrder={el.expsOrd}
shelfTitle={el.shptmBrndOptTpNm}
selectedHstNm={selectedHstNm}
selectedPatncNm={selectedPatncNm}
selectedPatnrId={selectedPatnrId}
setSelectedHstNm={setSelectedHstNm}
spotlightId={TEMPLATE_CODE_CONF.FEATURED_CREATORS}
/>
)}
</React.Fragment>
);
}
case TEMPLATE_CODE_CONF.SERIES: {
return (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
sortedBrandLayoutInfo,
TEMPLATE_CODE_CONF.SERIES
) &&
shouldRenderComponent(brandSeriesGroupInfo) &&
shouldRenderComponent(brandSeriesInfo) && (
<Series
brandSeriesGroupInfo={brandSeriesGroupInfo}
brandSeriesInfo={brandSeriesInfo}
fromGNB={fromGNB}
fromQuickMenu={fromQuickMenu}
handleItemFocus={handleItemFocus}
order={idx + 1}
shelfOrder={el.expsOrd}
shelfTitle={el.shptmBrndOptTpNm}
spotlightId={TEMPLATE_CODE_CONF.SERIES}
selectedPatncNm={selectedPatncNm}
selectedPatnrId={selectedPatnrId}
selectedSeriesId={selectedSeriesId}
setSelectedSeriesId={setSelectedSeriesId}
/>
)}
</React.Fragment>
);
}
case TEMPLATE_CODE_CONF.CATEGORY: {
return (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
sortedBrandLayoutInfo,
TEMPLATE_CODE_CONF.CATEGORY
) &&
shouldRenderComponent(brandCategoryInfo) &&
shouldRenderComponent(brandCategoryProductInfo) && (
<FeaturedCategory
brandCategoryInfo={brandCategoryInfo}
brandCategoryProductInfo={brandCategoryProductInfo}
fromGNB={fromGNB}
fromQuickMenu={fromQuickMenu}
handleItemFocus={handleItemFocus}
order={idx + 1}
shelfOrder={el.expsOrd}
shelfTitle={el.shptmBrndOptTpNm}
spotlightId={TEMPLATE_CODE_CONF.CATEGORY}
selectedCatCdLv1={selectedCatCdLv1}
selectedCatCdLv2={selectedCatCdLv2}
selectedPatncNm={selectedPatncNm}
selectedPatnrId={selectedPatnrId}
setSelectedCatCdLv1={setSelectedCatCdLv1}
setSelectedCatCdLv2={setSelectedCatCdLv2}
/>
)}
</React.Fragment>
);
}
case TEMPLATE_CODE_CONF.SHOWROOM: {
return (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
sortedBrandLayoutInfo,
TEMPLATE_CODE_CONF.SHOWROOM
) &&
shouldRenderComponent(brandShowroomInfo) && (
<Showroom
brandShowroomInfo={brandShowroomInfo}
fromGNB={fromGNB}
fromQuickMenu={fromQuickMenu}
handleItemFocus={handleItemFocus}
order={idx + 1}
shelfOrder={el.expsOrd}
shelfTitle={el.shptmBrndOptTpNm}
selectedPatnrId={selectedPatnrId}
selectedPatncNm={selectedPatncNm}
spotlightId={TEMPLATE_CODE_CONF.SHOWROOM}
/>
)}
</React.Fragment>
);
}
case TEMPLATE_CODE_CONF.NBCU: {
console.log("[FeaturedBrandsPanel] NBCU - patnrId:", selectedPatnrId);
console.log("[FeaturedBrandsPanel] NBCU - hasTemplateCode:", hasTemplateCodeWithValue(sortedBrandLayoutInfo, TEMPLATE_CODE_CONF.NBCU));
console.log("[FeaturedBrandsPanel] NBCU - shouldRender:", shouldRenderComponent(brandShopByShowContsList));
console.log("[FeaturedBrandsPanel] NBCU - data:", brandShopByShowContsList);
return (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
sortedBrandLayoutInfo,
TEMPLATE_CODE_CONF.NBCU
) &&
shouldRenderComponent(brandShopByShowContsList) && (
<>
{console.log("[FeaturedBrandsPanel] Rendering ShopByShow for patnrId:", selectedPatnrId)}
<ShopByShow
brandShopByShowContsList={brandShopByShowContsList}
brandShopByShowContsInfo={brandShopByShowContsInfo}
handleItemFocus={handleItemFocus}
order={idx + 1}
shelfOrder={el.expsOrd}
shelfTitle={el.shptmBrndOptTpNm}
spotlightId={TEMPLATE_CODE_CONF.NBCU}
selectedPatnrId={selectedPatnrId}
/>
</>
)}
</React.Fragment>
);
}
}
})}
</>
);
}, [
brandBestSellerInfo,
brandBestSellerTitle,
brandCategoryInfo,
brandCategoryProductInfo,
brandChanInfo,
brandChannelCnt,
brandCreatorsInfo,
barndCreatorsShowInfo,
brandLiveChannelUpcoming,
brandRecommendedShowCategoryInfo,
brandRecommendedShowInfo,
brandSeriesGroupInfo,
brandSeriesInfo,
brandShowroomInfo,
brandShopByShowContsList,
brandShopByShowContsInfo,
brandTsvInfo,
fromGNB,
fromQuickMenu,
handleItemFocus,
hasQuickMenu,
isOnTop,
selectedCatCd,
selectedCatCdLv1,
selectedCatCdLv2,
selectedHstNm,
selectedPatncNm,
selectedPatnrId,
selectedSeriesId,
sortedBrandLayoutInfo,
]);
const resetStates = useCallback(() => {
setSelectedCatCd(null);
setSelectedHstNm(null);
setSelectedSeriesId(null);
setSelectedCatCdLv1(null);
setSelectedCatCdLv2(null);
setContainerLastFocusedElement(null, ["upcoming-list-id"]);
setContainerLastFocusedElement(null, ["featured-category-nav-id"]);
}, []);
// effect: reset the brand store and set the focusedContainerId and fetch brand list on initial render
useEffect(() => {
if (!fromDetail) {
const containerId =
fromUpcoming || panelInfo?.linkTpCd === "8002"
? TEMPLATE_CODE_CONF.LIVE_CHANNELS
: SpotlightIds.BRAND_QUICK_MENU;
setFocusedContainerId(containerId);
dispatch(getBrandList());
dispatch({ type: types.RESET_BRAND_STATE_EXCEPT_BRAND_INFO });
} else setFocusedContainerId(panelInfo?.focusedContainerId);
}, []);
// effect: layout information fetching due to partner id change
useEffect(() => {
if (!fromDetail) {
console.log("[FeaturedBrandsPanel] Layout Info Effect - patnrId:", panelInfo?.patnrId);
dispatch({ type: types.RESET_BRAND_LAYOUT_INFO });
dispatch(getBrandLayoutInfo({ patnrId: panelInfo?.patnrId }));
setIsInitialFocusOccurred(false);
setDisplayTopButton(false);
}
}, [panelInfo?.patnrId]);
// effect: set selectedPatnrId and selectedPatncNm
useEffect(() => {
if (brandInfo && panelInfo?.patnrId) {
const patnrId = panelInfo.patnrId;
const patncNm = brandInfo.find((b) => b?.patnrId === patnrId)?.patncNm;
if (patncNm) {
setSelectedPatncNm(patncNm);
}
// Detail에서 돌아와도 patnrId가 비어 있으면 다시 설정해 API 호출이 정상 동작하도록 보완
if (!fromDetail || !selectedPatnrId) {
setSelectedPatnrId(patnrId);
}
}
}, [brandInfo, panelInfo?.patnrId, selectedPatnrId, fromDetail]);
// effect: data fetching based on brandLayoutInfo and selectedPatnrId
useEffect(() => {
if (sortedBrandLayoutInfo && selectedPatnrId) {
console.log("[FeaturedBrandsPanel] Data Fetching Effect - selectedPatnrId:", selectedPatnrId);
console.log("[FeaturedBrandsPanel] sortedBrandLayoutInfo:", sortedBrandLayoutInfo);
Object.entries(DISPATCH_MAP) //
.forEach(([templateCode, action]) => {
if (hasTemplateCodeWithValue(sortedBrandLayoutInfo, templateCode)) {
// Detail 복귀 시 ShopByShow 데이터가 이미 있으면 재호출을 건너뛰어 선택 상태가 초기화되는 것을 방지
if (
templateCode === TEMPLATE_CODE_CONF.NBCU &&
fromDetail &&
shouldRenderComponent(brandShopByShowContsList)
) {
console.log("[FeaturedBrandsPanel] Skip re-fetch ShopByShow on return from detail");
return;
}
console.log("[FeaturedBrandsPanel] Fetching data for template:", templateCode, "patnrId:", selectedPatnrId);
dispatch(action({ patnrId: selectedPatnrId }));
}
});
resetStates();
}
}, [sortedBrandLayoutInfo, selectedPatnrId]);
useEffect(() => {
if (selectedCatCd) {
dispatch(
getBrandRecommendedShowInfo({
patnrId: panelInfo?.patnrId,
catCd: selectedCatCd,
})
);
}
}, [selectedCatCd]);
useEffect(() => {
if (selectedHstNm) {
dispatch(
getBrandCreatorsInfo({
patnrId: panelInfo?.patnrId,
hstNm: selectedHstNm,
})
);
}
}, [selectedHstNm]);
useEffect(() => {
if (selectedCatCdLv1 === "") {
dispatch(
getBrandCategoryInfo({
patnrId: panelInfo?.patnrId,
})
);
return;
}
if (selectedCatCdLv1) {
dispatch(
getBrandCategoryProductInfo({
patnrId: panelInfo?.patnrId,
catCdLv1: selectedCatCdLv1,
})
);
}
}, [selectedCatCdLv1]);
// effect: initial or top panel focus logic
useEffect(() => {
if (isOnTop && !isInitialFocusOccurred && selectedPatncNm) {
// case: gnb or quick menu
let targetId = "spotlightId-" + panelInfo?.patnrId;
// case: upcoming or system alarm
if (fromUpcoming || panelInfo?.linkTpCd === "8002") {
targetId = TEMPLATE_CODE_CONF.LIVE_CHANNELS;
if (typeof window === "object" && window.MutationObserver) {
const observer = new window.MutationObserver((mutations) => {
for (let idx = mutations.length; idx--; ) {
if (mutations[idx].addedNodes.length) {
for (let i = mutations[idx].addedNodes.length; i--; ) {
const node = mutations[idx].addedNodes[i];
if (node.getAttribute("data-spotlight-id") === targetId) {
if (cbChangePageRef.current) {
cbChangePageRef.current(targetId, false);
}
Spotlight.focus(node);
break;
}
}
}
}
});
if (orderableFlexContainerRef.current) {
observer.observe(orderableFlexContainerRef.current, {
childList: true,
});
}
return () => observer.disconnect();
}
}
// case: detail
if (fromDetail) {
if (panelInfo?.catCdLv1) setSelectedCatCdLv1(panelInfo.catCdLv1);
if (panelInfo?.catCdLv2) setSelectedCatCdLv2(panelInfo.catCdLv2);
if (panelInfo?.seriesId) setSelectedSeriesId(panelInfo.seriesId);
}
focusOnMount(panelInfo?.lastFocusedTargetId ?? targetId);
setIsInitialFocusOccurred(true);
}
}, [isOnTop, isInitialFocusOccurred, selectedPatncNm]);
// effect: top button display setting logic
useEffect(() => {
if (panelInfo?.displayTopButton) {
setDisplayTopButton(true);
return;
}
if (typeof window === "object" && window.MutationObserver) {
const observer = new window.MutationObserver((mutations) => {
let shouldDisplayTopButton = false;
for (let idx = mutations.length; idx--; ) {
if (mutations[idx].type === "childList") {
renderedShelfCountRef.current =
orderableFlexContainerRef.current?.children.length || 0;
}
if (renderedShelfCountRef.current > 1) {
shouldDisplayTopButton = true;
break;
}
}
if (shouldDisplayTopButton !== displayTopButtonRef.current) {
setDisplayTopButton(shouldDisplayTopButton);
}
});
if (orderableFlexContainerRef.current) {
observer.observe(orderableFlexContainerRef.current, {
childList: true,
});
}
return () => observer.disconnect();
}
}, [panelInfo?.displayTopButton, panelInfo?.patnrId]);
// effect: gnb log
useEffect(() => {
if (!isOnTop) {
prevSelectedPatncNmRef.current = null;
setIsInitialFocusOccurred(false);
}
if (focusedContainerId && isOnTop && selectedPatncNm) {
doSendLogGNB(focusedContainerId);
}
}, [doSendLogGNB, focusedContainerId, isOnTop, selectedPatncNm]);
// effect: partners log
useEffect(() => {
if (
isLogGNBSent &&
isInitialFocusOccurred &&
selectedPatnrId &&
selectedPatncNm
) {
dispatch(
sendLogPartners({
patncNm: selectedPatncNm,
patnrId: selectedPatnrId,
})
);
}
}, [isLogGNBSent, isInitialFocusOccurred, selectedPatnrId, selectedPatncNm]);
// effect: unmount
useEffect(() => {
return () => {
dispatch(
updatePanel({
name: panel_names.FEATURED_BRANDS_PANEL,
panelInfo: {
brandInfo: brandInfoRef.current,
displayTopButton: displayTopButtonRef.current,
focusedContainerId: focusedContainerIdRef.current,
from: "detail",
lastMenu: lastMenuRef.current,
},
})
);
clearTimeout(alarmTimer.current);
clearTimeout(noneTargetTimer.current);
initialFocusTimeoutJob.current.stop();
};
}, []);
return (
<TPanel
className={css.tPanel}
spotlightId={cursorVisible ? spotlightId : null}
>
<TBody
className={css.tBody}
scrollable={false}
spotlightDisabled={!isOnTop}
>
<TVerticalPagenator
cbChangePageRef={cbChangePageRef}
className={css.tVerticalPagenator}
defaultContainerId={panelInfo?.focusedContainerId}
deleteFocus
onFocusedContainerId={onFocusedContainerId}
spotlightId={SpotlightIds.BRAND_VERTICAL_PAGENATOR}
topMargin={TOP_MARGIN}
>
{brandInfo && brandInfo.length > 1 && (
<QuickMenu
brandInfo={brandInfo}
fromGNB={fromGNB}
fromQuickMenu={fromQuickMenu}
handleItemFocus={handleItemFocus}
panelPatnrId={panelInfo?.patnrId}
selectedPatnrId={selectedPatnrId}
spotlightId={SpotlightIds.BRAND_QUICK_MENU}
resetStates={resetStates}
/>
)}
{brandInfo && brandTopImgInfo && (
<Banner
brandInfo={brandInfo}
brandTopImgInfo={brandTopImgInfo}
panelPatnrId={panelInfo?.patnrId}
/>
)}
{sortedBrandLayoutInfo && (
<div
className={css.orderableFlexContainer}
ref={orderableFlexContainerRef}
>
{renderPageItem()}
</div>
)}
{displayTopButton && (
<TButton
className={classNames(css.tButton)}
data-wheel-point
onClick={handleTopButtonClick}
size={null}
spotlightId={SpotlightIds.BRAND_TOP_BUTTON}
type={TYPES.topButton}
/>
)}
</TVerticalPagenator>
</TBody>
{(activePopup?.type === ACTIVE_POPUP.reminderPopup ||
activePopup?.type === ACTIVE_POPUP.timeConflictPopup) && (
<TPopUp
button1Text={$L(STRING_CONF.OK)}
button2Text={$L(STRING_CONF.CANCEL)}
hasButton
hasText
kind="textPopup"
onClick={handlePopupClick}
onClose={handlePopupClose}
open={popupVisible}
text={getMessageByPopupType(activePopup?.type)}
/>
)}
</TPanel>
);
};
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(FeaturedBrandsPanel, propsAreEqual);