[251209] feat: NBCU-ShopByShow-1
🕐 커밋 시간: 2025. 12. 09. 14:33:20 📊 변경 통계: • 총 파일: 15개 • 추가: +128줄 • 삭제: -11줄 📁 추가된 파일: + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShow.jsx + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShow.module.less + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowContents.jsx + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowContents.module.less + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowList.jsx + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowList.module.less + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowNav/ShopByShowNav.jsx + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowNav/ShopByShowNav.module.less 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/actionTypes.js ~ com.twin.app.shoptime/src/actions/brandActions.js ~ com.twin.app.shoptime/src/api/apiConfig.js ~ com.twin.app.shoptime/src/reducers/brandReducer.js ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBestSeller/FeaturedBestSellerList/FeaturedBestSellerList.jsx ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/QuickMenuItem/QuickMenuItem.jsx 🔧 주요 변경 내용: • 타입 시스템 안정성 강화 • 핵심 비즈니스 로직 개선 • API 서비스 레이어 개선 • 중간 규모 기능 개선 • 모듈 구조 개선
This commit is contained in:
@@ -130,6 +130,7 @@ export const types = {
|
||||
GET_BRAND_CREATORS_INFO: 'GET_BRAND_CREATORS_INFO',
|
||||
GET_BRAND_SHOWROOM: 'GET_BRAND_SHOWROOM',
|
||||
GET_BRAND_RECENTLY_AIRED: 'GET_BRAND_RECENTLY_AIRED',
|
||||
GET_BRAND_SHOP_BY_SHOW: 'GET_BRAND_SHOP_BY_SHOW',
|
||||
SET_BRAND_LIVE_CHANNEL_UPCOMING: 'SET_BRAND_LIVE_CHANNEL_UPCOMING',
|
||||
SET_BRAND_CHAN_INFO: 'SET_BRAND_CHAN_INFO',
|
||||
RESET_BRAND_STATE: 'RESET_BRAND_STATE',
|
||||
|
||||
@@ -37,10 +37,12 @@ export const getBrandList = () => (dispatch, getState) => {
|
||||
export const getBrandLayoutInfo = (props) => (dispatch, getState) => {
|
||||
const { patnrId } = props;
|
||||
|
||||
console.log("[getBrandLayoutInfo] Called - patnrId:", patnrId);
|
||||
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
||||
|
||||
const onSuccess = (response) => {
|
||||
// dlog("getBrandLayoutInfo onSuccess ", response.data);
|
||||
console.log("[getBrandLayoutInfo] onSuccess - patnrId:", patnrId, "data:", response.data.data);
|
||||
|
||||
dispatch({
|
||||
type: types.GET_BRAND_LAYOUT_INFO,
|
||||
@@ -53,6 +55,7 @@ export const getBrandLayoutInfo = (props) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.log("[getBrandLayoutInfo] onFail - patnrId:", patnrId, "error:", error);
|
||||
derror('getBrandLayoutInfo onFail ', error);
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
};
|
||||
@@ -336,10 +339,12 @@ export const getBrandCategoryProductInfo = (props) => (dispatch, getState) => {
|
||||
export const getBrandBestSeller = (props) => (dispatch, getState) => {
|
||||
const { patnrId } = props;
|
||||
|
||||
console.log("[getBrandBestSeller] Called - patnrId:", patnrId);
|
||||
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
||||
|
||||
const onSuccess = (response) => {
|
||||
// dlog("getBrandBestSeller onSuccess ", response.data);
|
||||
console.log("[getBrandBestSeller] onSuccess - patnrId:", patnrId, "data:", response.data.data);
|
||||
|
||||
dispatch({
|
||||
type: types.GET_BRAND_BEST_SELLER,
|
||||
@@ -352,6 +357,7 @@ export const getBrandBestSeller = (props) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.log("[getBrandBestSeller] onFail - patnrId:", patnrId, "error:", error);
|
||||
derror('getBrandBestSeller onFail ', error);
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
};
|
||||
@@ -386,6 +392,39 @@ export const getBrandShowroom = (props) => (dispatch, getState) => {
|
||||
TAxios(dispatch, getState, 'get', URLS.GET_BRAND_SHOWROOM, { patnrId }, {}, onSuccess, onFail);
|
||||
};
|
||||
|
||||
// Featured Brands SHOP BY SHOW 정보 조회 IF-LGSP-376
|
||||
export const getBrandShopByShow = (props) => (dispatch, getState) => {
|
||||
const { patnrId, contsId } = props;
|
||||
|
||||
console.log("[getBrandShopByShow] Called - patnrId:", patnrId, "contsId:", contsId);
|
||||
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log("[getBrandShopByShow] onSuccess - patnrId:", patnrId, "data:", response.data.data);
|
||||
|
||||
dispatch({
|
||||
type: types.GET_BRAND_SHOP_BY_SHOW,
|
||||
payload: {
|
||||
data: response.data.data,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.log("[getBrandShopByShow] onFail - patnrId:", patnrId, "error:", error);
|
||||
derror('getBrandShopByShow onFail ', error);
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
};
|
||||
|
||||
// patnrId: 필수, contsId: 선택
|
||||
const params = contsId ? { patnrId, contsId } : { patnrId };
|
||||
|
||||
TAxios(dispatch, getState, 'get', URLS.GET_BRAND_SHOP_BY_SHOW, params, {}, onSuccess, onFail);
|
||||
};
|
||||
|
||||
// Featured Brands Recently Aired 조회 IF-LGSP-373
|
||||
export const getBrandRecentlyAired = (props) => (dispatch, getState) => {
|
||||
const { patnrId } = props;
|
||||
|
||||
@@ -55,6 +55,7 @@ export const URLS = {
|
||||
GET_BRAND_CREATORS_INFO: "/lgsp/v1/brand/creators.lge",
|
||||
GET_BRAND_SHOWROOM: "/lgsp/v1/brand/showroom.lge",
|
||||
GET_BRAND_RECENTLY_AIRED: "/lgsp/v1/brand/recently/aired.lge",
|
||||
GET_BRAND_SHOP_BY_SHOW: "/lgsp/v1/brand/shopByShow.lge",
|
||||
|
||||
//on-sale controller
|
||||
GET_ON_SALE_INFO: "/lgsp/v1/onsale/onsale.lge",
|
||||
|
||||
@@ -44,6 +44,10 @@ const initialState = {
|
||||
brandRecentlyAiredData: {
|
||||
data: {},
|
||||
},
|
||||
|
||||
brandShopByShowData: {
|
||||
data: {},
|
||||
},
|
||||
};
|
||||
|
||||
export const brandReducer = (state = initialState, action) => {
|
||||
@@ -155,6 +159,12 @@ export const brandReducer = (state = initialState, action) => {
|
||||
brandRecentlyAiredData: action.payload,
|
||||
};
|
||||
|
||||
case types.GET_BRAND_SHOP_BY_SHOW:
|
||||
return {
|
||||
...state,
|
||||
brandShopByShowData: action.payload,
|
||||
};
|
||||
|
||||
case types.SET_BRAND_LIVE_CHANNEL_UPCOMING:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -60,6 +60,10 @@ export default function FeaturedBestSellerList({
|
||||
|
||||
const panelInfo = useSelector((state) => state.panels.panels[0]?.panelInfo);
|
||||
|
||||
console.log("[FeaturedBestSellerList] Mounted - selectedPatnrId:", selectedPatnrId);
|
||||
console.log("[FeaturedBestSellerList] brandBestSellerInfo:", brandBestSellerInfo);
|
||||
console.log("[FeaturedBestSellerList] Data count:", brandBestSellerInfo?.length || 0);
|
||||
|
||||
const cursorVisible = useSelector(
|
||||
(state) => state.common.appStatus.cursorVisible
|
||||
);
|
||||
@@ -155,6 +159,9 @@ export default function FeaturedBestSellerList({
|
||||
lgCatNm,
|
||||
euEnrgLblInfos,
|
||||
} = brandBestSellerInfo[index];
|
||||
|
||||
console.log("[FeaturedBestSellerList] renderItem - index:", index, "patnrId:", patnrId, "rankOrd:", rankOrd, "prdtNm:", prdtNm);
|
||||
|
||||
const rankText =
|
||||
rankOrd === 1
|
||||
? rankOrd + "st,"
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
getBrandLiveChannelInfo,
|
||||
getBrandRecommendedShowInfo,
|
||||
getBrandSeriesInfo,
|
||||
getBrandShopByShow,
|
||||
getBrandShowroom,
|
||||
getBrandTSVInfo,
|
||||
} from "../../actions/brandActions";
|
||||
@@ -63,6 +64,7 @@ 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";
|
||||
@@ -90,6 +92,7 @@ const TEMPLATE_CODE_CONF = {
|
||||
SERIES: "BRD00107",
|
||||
CATEGORY: "BRD00108",
|
||||
SHOWROOM: "BRD00109",
|
||||
NBCU: "BRD00110",
|
||||
};
|
||||
|
||||
const DISPATCH_MAP = Object.freeze({
|
||||
@@ -101,6 +104,7 @@ const DISPATCH_MAP = Object.freeze({
|
||||
[TEMPLATE_CODE_CONF.SERIES]: getBrandSeriesInfo,
|
||||
[TEMPLATE_CODE_CONF.CATEGORY]: getBrandCategoryInfo,
|
||||
[TEMPLATE_CODE_CONF.SHOWROOM]: getBrandShowroom,
|
||||
[TEMPLATE_CODE_CONF.NBCU]: getBrandShopByShow,
|
||||
});
|
||||
|
||||
const TOP_MARGIN = 36;
|
||||
@@ -263,6 +267,12 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
||||
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);
|
||||
@@ -412,9 +422,12 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
||||
);
|
||||
|
||||
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 (
|
||||
@@ -485,6 +498,10 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -492,15 +509,18 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
||||
TEMPLATE_CODE_CONF.BEST_SELLER
|
||||
) &&
|
||||
shouldRenderComponent(brandBestSellerInfo) && (
|
||||
<FeaturedBestSeller
|
||||
brandBestSellerInfo={brandBestSellerInfo}
|
||||
handleItemFocus={handleItemFocus}
|
||||
order={idx + 1}
|
||||
shelfOrder={el.expsOrd}
|
||||
shelfTitle={el.shptmBrndOptTpNm}
|
||||
spotlightId={TEMPLATE_CODE_CONF.BEST_SELLER}
|
||||
selectedPatnrId={selectedPatnrId}
|
||||
/>
|
||||
<>
|
||||
{console.log("[FeaturedBrandsPanel] Rendering FeaturedBestSeller for patnrId:", selectedPatnrId)}
|
||||
<FeaturedBestSeller
|
||||
brandBestSellerInfo={brandBestSellerInfo}
|
||||
handleItemFocus={handleItemFocus}
|
||||
order={idx + 1}
|
||||
shelfOrder={el.expsOrd}
|
||||
shelfTitle={el.shptmBrndOptTpNm}
|
||||
spotlightId={TEMPLATE_CODE_CONF.BEST_SELLER}
|
||||
selectedPatnrId={selectedPatnrId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -650,6 +670,36 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
||||
</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>
|
||||
);
|
||||
}
|
||||
}
|
||||
})}
|
||||
</>
|
||||
@@ -668,6 +718,8 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
||||
brandSeriesGroupInfo,
|
||||
brandSeriesInfo,
|
||||
brandShowroomInfo,
|
||||
brandShopByShowContsList,
|
||||
brandShopByShowContsInfo,
|
||||
brandTsvInfo,
|
||||
fromGNB,
|
||||
fromQuickMenu,
|
||||
@@ -711,6 +763,7 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
||||
// 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);
|
||||
@@ -733,9 +786,12 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
||||
// 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)) {
|
||||
console.log("[FeaturedBrandsPanel] Fetching data for template:", templateCode, "patnrId:", selectedPatnrId);
|
||||
dispatch(action({ patnrId: selectedPatnrId }));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -49,10 +49,13 @@ const QuickMenuItem = ({
|
||||
}, [handleStopScrolling, itemIndex]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
console.log("[QuickMenuItem] Click - patnrId:", patnrId, "currentPatnrId:", selectedPatnrId ?? panelInfo?.patnrId);
|
||||
if (patnrId === (selectedPatnrId ?? panelInfo?.patnrId)) {
|
||||
console.log("[QuickMenuItem] Already selected, returning");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[QuickMenuItem] Switching to patnrId:", patnrId);
|
||||
const from = "menu";
|
||||
const name = panel_names.FEATURED_BRANDS_PANEL;
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import React, { memo, useCallback, useState } from "react";
|
||||
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
|
||||
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
|
||||
import { $L } from "../../../utils/helperMethods";
|
||||
import css from "./ShopByShow.module.less";
|
||||
import ShopByShowList from "./ShopByShowList/ShopByShowList";
|
||||
|
||||
const STRING_CONF = {
|
||||
SHOP_BY_SHOW: "SHOP BY SHOW",
|
||||
};
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ leaveFor: { right: "" }, enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
|
||||
const ShopByShow = ({
|
||||
brandShopByShowContsList,
|
||||
brandShopByShowContsInfo,
|
||||
handleItemFocus,
|
||||
order,
|
||||
shelfOrder,
|
||||
spotlightId,
|
||||
selectedPatnrId,
|
||||
shelfTitle,
|
||||
}) => {
|
||||
const [firstChk, setFirstChk] = useState(0);
|
||||
|
||||
const _handleItemFocus = useCallback(() => {
|
||||
if (handleItemFocus) handleItemFocus(spotlightId, shelfOrder);
|
||||
|
||||
const c = Spotlight.getCurrent();
|
||||
if (firstChk === 0) {
|
||||
if (c) {
|
||||
let cAriaLabel = c.getAttribute("aria-label");
|
||||
if (cAriaLabel) {
|
||||
cAriaLabel = "shop-by-show, Heading1," + cAriaLabel;
|
||||
c.setAttribute("aria-label", cAriaLabel);
|
||||
}
|
||||
}
|
||||
setFirstChk(1);
|
||||
} else if (firstChk === 1) {
|
||||
if (c) {
|
||||
let cAriaLabel = c.getAttribute("aria-label");
|
||||
if (cAriaLabel) {
|
||||
const newcAriaLabel = cAriaLabel.replace(
|
||||
"shop-by-show, Heading1,",
|
||||
""
|
||||
);
|
||||
c.setAttribute("aria-label", newcAriaLabel);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}, [handleItemFocus, firstChk]);
|
||||
|
||||
return (
|
||||
<Container
|
||||
className={css.container}
|
||||
data-shelf-order={order}
|
||||
data-wheel-point
|
||||
spotlightId={spotlightId}
|
||||
>
|
||||
<SectionTitle
|
||||
title={$L(STRING_CONF.SHOP_BY_SHOW)}
|
||||
data-title="shop-by-show"
|
||||
label="shop-by-show Heading 1"
|
||||
/>
|
||||
<ShopByShowList
|
||||
brandShopByShowContsList={brandShopByShowContsList}
|
||||
brandShopByShowContsInfo={brandShopByShowContsInfo}
|
||||
handleItemFocus={_handleItemFocus}
|
||||
selectedPatnrId={selectedPatnrId}
|
||||
spotlightId={spotlightId}
|
||||
shelfOrder={shelfOrder}
|
||||
shelfTitle={shelfTitle}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ShopByShow);
|
||||
@@ -0,0 +1,12 @@
|
||||
@import "../../../style/CommonStyle.module.less";
|
||||
@import "../../../style/utils.module.less";
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-bottom: 58px;
|
||||
|
||||
> h2 {
|
||||
margin-bottom: 24px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import React, { memo, useCallback } from "react";
|
||||
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
|
||||
import TItemCardNew, { removeDotAndColon } from "../../../../../components/TItemCard/TItemCard.new";
|
||||
import TVirtualGridList from "../../../../../components/TVirtualGridList/TVirtualGridList";
|
||||
import {
|
||||
LOG_CONTEXT_NAME,
|
||||
LOG_MESSAGE_ID,
|
||||
panel_names,
|
||||
} from "../../../../../utils/Config";
|
||||
import { getTranslate3dValueByDirection } from "../../../../../utils/helperMethods";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import { pushPanel, updatePanel } from "../../../../../actions/panelActions";
|
||||
import { useDispatch } from "react-redux";
|
||||
import css from "./ShopByShowContents.module.less";
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ leaveFor: { right: "" }, enterTo: null },
|
||||
"div"
|
||||
);
|
||||
|
||||
const ShopByShowContents = memo(({
|
||||
clctId,
|
||||
clctNm,
|
||||
brandProductInfos,
|
||||
contentsIndex,
|
||||
handleItemFocus,
|
||||
selectedPatnrId,
|
||||
spotlightId,
|
||||
shelfOrder,
|
||||
shelfTitle,
|
||||
getScrollTo,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleClick = useCallback(
|
||||
(patnrId, prdtId) => (e) => {
|
||||
const tItemCard = e.currentTarget;
|
||||
const lastFocusedTarget = Spotlight.getCurrent();
|
||||
const lastFocusedTargetId = lastFocusedTarget?.getAttribute("data-spotlight-id");
|
||||
const xContainer = tItemCard?.parentNode?.parentNode;
|
||||
|
||||
if (lastFocusedTargetId && xContainer) {
|
||||
const section = "shop-by-show";
|
||||
const x = getTranslate3dValueByDirection(xContainer);
|
||||
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.FEATURED_BRANDS_PANEL,
|
||||
panelInfo: {
|
||||
lastFocusedTargetId,
|
||||
patnrId,
|
||||
section,
|
||||
x,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: { patnrId, prdtId },
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
if (handleItemFocus) {
|
||||
handleItemFocus();
|
||||
}
|
||||
}, [handleItemFocus]);
|
||||
|
||||
return (
|
||||
<Container
|
||||
className={css.container}
|
||||
data-wheel-point
|
||||
spotlightId={`${spotlightId}-${contentsIndex}`}
|
||||
>
|
||||
<h3 data-collection-subtitle={clctNm}>{clctNm}</h3>
|
||||
{brandProductInfos && brandProductInfos.length > 0 && (
|
||||
<TVirtualGridList
|
||||
cbScrollTo={getScrollTo}
|
||||
className={css.tVirtualGridList}
|
||||
dataSize={brandProductInfos.length}
|
||||
direction="horizontal"
|
||||
itemHeight={438}
|
||||
itemWidth={324}
|
||||
spacing={18}
|
||||
renderItem={({ index, ...rest }) => {
|
||||
if (!brandProductInfos || !brandProductInfos[index]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const product = brandProductInfos[index];
|
||||
const {
|
||||
prdtImgUrl,
|
||||
prdtOfferId,
|
||||
patnrId = "21",
|
||||
prdtNm,
|
||||
prdtId,
|
||||
prdtPrice,
|
||||
patncNm,
|
||||
brndNm,
|
||||
lgCatNm,
|
||||
euEnrgLblInfos,
|
||||
} = product;
|
||||
|
||||
return (
|
||||
<TItemCardNew
|
||||
catNm={lgCatNm}
|
||||
contextName={LOG_CONTEXT_NAME.FEATURED_BRANDS}
|
||||
messageId={LOG_MESSAGE_ID.SHELF_CLICK}
|
||||
patnerName={patncNm || "Peacock | Shop The Moment"}
|
||||
brandName={brndNm}
|
||||
shelfId={spotlightId}
|
||||
shelfLocation={shelfOrder}
|
||||
shelfTitle={shelfTitle}
|
||||
imageAlt={prdtNm}
|
||||
imageSource={prdtImgUrl}
|
||||
onClick={handleClick(patnrId, prdtId)}
|
||||
onFocus={handleFocus}
|
||||
offerInfo={prdtOfferId}
|
||||
priceInfo={prdtPrice}
|
||||
productId={prdtId}
|
||||
productName={prdtNm}
|
||||
spotlightId={"shop-by-show-spotlightId-" + removeDotAndColon(prdtId)}
|
||||
label={index * 1 + 1 + " of " + brandProductInfos.length}
|
||||
lastLabel=" go to detail, button"
|
||||
euEnrgLblInfos={euEnrgLblInfos}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
|
||||
export default ShopByShowContents;
|
||||
@@ -0,0 +1,32 @@
|
||||
@import "../../../../../style/CommonStyle.module.less";
|
||||
@import "../../../../../style/utils.module.less";
|
||||
|
||||
.container {
|
||||
padding-left: 60px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
> h3 {
|
||||
position: relative;
|
||||
.font(@fontFamily: @arialFontBold, @fontSize: 36px);
|
||||
color: @COLOR_GRAY08;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
> div:nth-child(2) {
|
||||
.flex(@justifyCenter: flex-start);
|
||||
.size(@w: 100%, @h: auto);
|
||||
}
|
||||
}
|
||||
|
||||
.container:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tVirtualGridList {
|
||||
padding-right: 18px;
|
||||
.size(@h: 438px);
|
||||
|
||||
> div:nth-child(3) {
|
||||
right: -18px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
|
||||
import { Job } from '@enact/core/util';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import {
|
||||
getContainerId,
|
||||
setContainerLastFocusedElement,
|
||||
} from '@enact/spotlight/src/container';
|
||||
|
||||
import { getBrandShopByShow } from '../../../../actions/brandActions';
|
||||
import useScrollTo from '../../../../hooks/useScrollTo';
|
||||
import css from './ShopByShowList.module.less';
|
||||
import ShopByShowNav from './ShopByShowNav/ShopByShowNav';
|
||||
import ShopByShowContents from './ShopByShowContents/ShopByShowContents';
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ leaveFor: { right: "" }, enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
|
||||
export default function ShopByShowList({
|
||||
brandShopByShowContsList,
|
||||
brandShopByShowContsInfo,
|
||||
handleItemFocus,
|
||||
selectedPatnrId,
|
||||
spotlightId,
|
||||
shelfTitle,
|
||||
shelfOrder,
|
||||
}) {
|
||||
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||
const dispatch = useDispatch();
|
||||
const panelInfo = useSelector((state) => state.panels.panels[0]?.panelInfo);
|
||||
const scrollLeftJob = useRef(new Job((func) => func(), 0));
|
||||
const [selectedClctId, setSelectedClctId] = useState(null);
|
||||
|
||||
const brandShopByShowClctInfos = brandShopByShowContsInfo?.brandShopByShowClctInfos || [];
|
||||
|
||||
const filteredCollections = selectedClctId
|
||||
? brandShopByShowClctInfos.filter(({ clctId }) => clctId === selectedClctId)
|
||||
: brandShopByShowClctInfos;
|
||||
|
||||
useEffect(() => {
|
||||
if (panelInfo?.section !== "shop-by-show" || !panelInfo?.x) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollLeftJobValue = scrollLeftJob.current;
|
||||
const { x } = panelInfo;
|
||||
|
||||
scrollLeftJobValue.start(() => scrollLeft({ x }));
|
||||
|
||||
return () => scrollLeftJobValue.stop();
|
||||
}, [panelInfo, scrollLeft]);
|
||||
|
||||
useEffect(() => {
|
||||
const containerId = "shop-by-show-list-id";
|
||||
const container = document.getElementById(containerId);
|
||||
|
||||
if (container) {
|
||||
const childrenId = getContainerId(container?.children[0].children[0]);
|
||||
|
||||
if (childrenId) {
|
||||
setContainerLastFocusedElement(null, [containerId, childrenId]);
|
||||
}
|
||||
}
|
||||
|
||||
scrollLeft();
|
||||
}, [scrollLeft, selectedPatnrId]);
|
||||
|
||||
const handleTabClick = useCallback((contsId) => {
|
||||
dispatch(getBrandShopByShow({ patnrId: selectedPatnrId, contsId }));
|
||||
}, [selectedPatnrId, dispatch]);
|
||||
|
||||
const _handleItemFocus = useCallback(() => {
|
||||
if (handleItemFocus) {
|
||||
handleItemFocus();
|
||||
}
|
||||
}, [handleItemFocus]);
|
||||
|
||||
return (
|
||||
<Container
|
||||
className={css.container}
|
||||
id={"shop-by-show-list-id"}
|
||||
spotlightId={"shop-by-show-list-id"}
|
||||
>
|
||||
<div className={css.tabsContainer}>
|
||||
{brandShopByShowContsList && brandShopByShowContsList.map((item) => (
|
||||
<button
|
||||
key={item.contsId}
|
||||
className={`${css.tabButton} ${brandShopByShowContsInfo?.contsId === item.contsId ? css.active : ''}`}
|
||||
onClick={() => handleTabClick(item.contsId)}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
>
|
||||
{item.contsNm}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<ShopByShowNav
|
||||
brandShopByShowClctInfos={brandShopByShowClctInfos}
|
||||
handleItemFocus={_handleItemFocus}
|
||||
selectedClctId={selectedClctId}
|
||||
setSelectedClctId={setSelectedClctId}
|
||||
/>
|
||||
|
||||
{filteredCollections.map((collection, collIdx) => (
|
||||
<ShopByShowContents
|
||||
key={`${spotlightId}-${collIdx}`}
|
||||
clctId={collection.clctId}
|
||||
clctNm={collection.clctNm}
|
||||
brandProductInfos={collection.brandProductInfos}
|
||||
contentsIndex={collIdx}
|
||||
handleItemFocus={_handleItemFocus}
|
||||
selectedPatnrId={selectedPatnrId}
|
||||
spotlightId={spotlightId}
|
||||
shelfOrder={shelfOrder}
|
||||
shelfTitle={shelfTitle}
|
||||
getScrollTo={getScrollTo}
|
||||
/>
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
@import "../../../../style/CommonStyle.module.less";
|
||||
@import "../../../../style/utils.module.less";
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tabsContainer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 0 60px 24px 60px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.tabButton {
|
||||
flex-shrink: 0;
|
||||
padding: 8px 16px;
|
||||
background-color: transparent;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #000;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #000;
|
||||
border-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import React, { memo, useCallback } from "react";
|
||||
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
|
||||
import TButton, { TYPES } from "../../../../../components/TButton/TButton";
|
||||
import TScroller from "../../../../../components/TScroller/TScroller";
|
||||
import useScrollTo from "../../../../../hooks/useScrollTo";
|
||||
import css from "./ShopByShowNav.module.less";
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ leaveFor: { right: "" }, enterTo: "last-focused" },
|
||||
"nav"
|
||||
);
|
||||
|
||||
const STRING_CONF = {
|
||||
ALL: "ALL",
|
||||
};
|
||||
|
||||
export default memo(function ShopByShowNav({
|
||||
brandShopByShowClctInfos,
|
||||
handleItemFocus,
|
||||
selectedClctId,
|
||||
setSelectedClctId,
|
||||
}) {
|
||||
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||
|
||||
const handleClick = useCallback(
|
||||
(clctId, clctNm) => () => {
|
||||
setSelectedClctId(clctId);
|
||||
},
|
||||
[setSelectedClctId]
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
if (handleItemFocus) {
|
||||
handleItemFocus();
|
||||
}
|
||||
}, [handleItemFocus]);
|
||||
|
||||
const selectedText = !selectedClctId ? "Selected " : "";
|
||||
const allLabelText = selectedText + "ALL 1 of " + (brandShopByShowClctInfos?.length + 1 || 1);
|
||||
|
||||
return (
|
||||
<Container className={css.nav} id="shop-by-show-nav-id" spotlightId="shop-by-show-nav-id">
|
||||
<TScroller cbScrollTo={getScrollTo} direction="horizontal" noScrollByWheel>
|
||||
<ul>
|
||||
<li>
|
||||
<TButton
|
||||
className={!selectedClctId && css.selected}
|
||||
onClick={handleClick(null, null)}
|
||||
onFocus={handleFocus}
|
||||
selected={!selectedClctId}
|
||||
type={TYPES.oneDepthCategory}
|
||||
ariaLabel={allLabelText}
|
||||
>
|
||||
ALL
|
||||
</TButton>
|
||||
</li>
|
||||
{brandShopByShowClctInfos &&
|
||||
brandShopByShowClctInfos.map(({ clctId, clctNm }, index) => (
|
||||
<li key={"shop-by-show-clct-" + index}>
|
||||
<TButton
|
||||
className={selectedClctId && selectedClctId === clctId && css.selected}
|
||||
onClick={handleClick(clctId, clctNm)}
|
||||
onFocus={handleFocus}
|
||||
selected={selectedClctId && selectedClctId === clctId}
|
||||
type={TYPES.oneDepthCategory}
|
||||
ariaLabel={
|
||||
selectedClctId && selectedClctId === clctId
|
||||
? "Selected " + clctNm + " " + (index * 1 + 2) + " of " + (brandShopByShowClctInfos.length + 1)
|
||||
: "" + clctNm + " " + (index * 1 + 2) + " of " + (brandShopByShowClctInfos.length + 1)
|
||||
}
|
||||
>
|
||||
{clctNm}
|
||||
</TButton>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</TScroller>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
@import "../../../../../style/CommonStyle.module.less";
|
||||
@import "../../../../../style/utils.module.less";
|
||||
|
||||
.nav {
|
||||
position: relative;
|
||||
.size(@w: 100%, @h: 162px);
|
||||
margin-bottom: 30px;
|
||||
padding-right: 1px;
|
||||
z-index: 2;
|
||||
|
||||
> div:nth-child(1) {
|
||||
.size(@w: inherit, @h: inherit);
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
.size(@w: 100%, @h: 144px);
|
||||
background-color: #ddd;
|
||||
content: "";
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: inherit;
|
||||
padding-left: 60px;
|
||||
border-bottom: 18px solid transparent;
|
||||
|
||||
li {
|
||||
flex: none;
|
||||
margin-right: 12px;
|
||||
|
||||
> div {
|
||||
position: relative;
|
||||
|
||||
&.selected {
|
||||
&::before {
|
||||
position: absolute;
|
||||
bottom: -62px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
.size(@w: 0, @h: 0);
|
||||
border-top: 18px solid #ddd;
|
||||
border-right: 18px solid transparent;
|
||||
border-bottom: 18px solid transparent;
|
||||
border-left: 18px solid transparent;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user