[FeaturedBrandsPanel] section, SERIES 추가 및 수정

Detail Notes :

1. SERIES, Nav & Contents 추가
2. 각 section, 불필요 re-rendering 방지 작업
This commit is contained in:
younghoon100.park
2024-02-28 18:59:24 +09:00
parent c4220c139a
commit 2f1e617221
20 changed files with 444 additions and 49 deletions

View File

@@ -1,8 +1,8 @@
import React from "react";
import React, { memo } from "react";
import css from "./Banner.module.less";
export default function Banner({ brandTopImgInfo, selectedBrandInfo }) {
export default memo(function Banner({ brandTopImgInfo, selectedBrandInfo }) {
const { topImgAlt, topImgPath } = brandTopImgInfo;
const { logoImgAlt, logoImgPath, patncNm } = selectedBrandInfo;
@@ -15,4 +15,4 @@ export default function Banner({ brandTopImgInfo, selectedBrandInfo }) {
<img src={topImgPath} alt={topImgAlt} />
</div>
);
}
});

View File

@@ -1,4 +1,4 @@
import React from "react";
import React, { memo } from "react";
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
import { $L } from "../../../utils/helperMethods";
@@ -9,11 +9,11 @@ const STRING_CONF = {
BEST_SELLER: $L("BEST SELLER"),
};
export default function FeaturedBestSeller({ brandBestSellerInfo }) {
export default memo(function FeaturedBestSeller({ brandBestSellerInfo }) {
return (
<div className={css.container}>
<SectionTitle title={STRING_CONF.BEST_SELLER} />
<FeaturedBestSellerList brandBestSellerInfo={brandBestSellerInfo} />
</div>
);
}
});

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
@@ -14,7 +14,9 @@ import {
getBrandTSVInfo,
} from "../../actions/brandActions";
import TBody from "../../components/TBody/TBody";
import TButton, { TYPES } from "../../components/TButton/TButton";
import TPanel from "../../components/TPanel/TPanel";
import useScrollTo from "../../hooks/useScrollTo";
import Banner from "./Banner/Banner";
import FeaturedBestSeller from "./FeaturedBestSeller/FeaturedBestSeller";
import css from "./FeaturedBrandsPanel.module.less";
@@ -36,6 +38,8 @@ const isNotEmptyObject = (object) => {
};
export default function FeaturedBrandsPanel() {
const { getScrollTo, scrollTop } = useScrollTo();
const dispatch = useDispatch();
const panelInfo = useSelector((state) => state.panels.panels[0].panelInfo);
@@ -92,10 +96,11 @@ export default function FeaturedBrandsPanel() {
const [selectedSeriesId, setSelectedSeriesId] = useState();
const [selectedCatCdLv1, setSelectedCatCdLv1] = useState();
const [selectedCatCdLv2, setSelectedCatCdLv2] = useState();
const [targetId, setTargetId] = useState();
useEffect(() => {
if (!brandInfo) {
console.log("@@ [initial sideEffect]");
// console.log("@@ [initial sideEffect]");
dispatch(getBrandList());
}
@@ -103,7 +108,7 @@ export default function FeaturedBrandsPanel() {
useEffect(() => {
if (brandInfo && selectedPatnrId) {
console.log("@@ [patnrId sideEffect]");
// console.log("@@ [patnrId sideEffect]");
setSelectedBrandInfo(findItemByPatnrId(brandInfo, selectedPatnrId));
setSelectedCatCd();
@@ -133,7 +138,7 @@ export default function FeaturedBrandsPanel() {
useEffect(() => {
if (selectedCatCd) {
console.log("@@ [catCd sideEffect]");
// console.log("@@ [catCd sideEffect]");
dispatch(
getBrandRecommendedShowInfo({
@@ -146,7 +151,7 @@ export default function FeaturedBrandsPanel() {
useEffect(() => {
if (selectedHstNm) {
console.log("@@ [hstNm sideEffect]");
// console.log("@@ [hstNm sideEffect]");
dispatch(
getBrandCreatorsInfo({ patnrId: selectedPatnrId, hstNm: selectedHstNm })
@@ -156,22 +161,20 @@ export default function FeaturedBrandsPanel() {
useEffect(() => {
if (selectedSeriesId) {
console.log("@@ [seriedId sideEffect]");
// console.log("@@ [seriedId sideEffect]");
dispatch(
dispatch(
getBrandSeriesInfo({
patnrId: selectedPatnrId,
seriesId: selectedSeriesId,
})
)
getBrandSeriesInfo({
patnrId: selectedPatnrId,
seriesId: selectedSeriesId,
})
);
}
}, [selectedSeriesId, dispatch]);
useEffect(() => {
if (selectedCatCdLv1 || selectedCatCdLv1 === "") {
console.log("@@ [catCdLv1 sideEffect]");
// console.log("@@ [catCdLv1 sideEffect]");
dispatch(
getBrandCategoryInfo({
@@ -185,7 +188,7 @@ export default function FeaturedBrandsPanel() {
useEffect(() => {
if (selectedCatCdLv2 || selectedCatCdLv2 === "") {
console.log("@@ [catCdLv2 sideEffect]");
// console.log("@@ [catCdLv2 sideEffect]");
dispatch(
getBrandCategoryInfo({
@@ -201,10 +204,18 @@ export default function FeaturedBrandsPanel() {
// @@pyh Todo, 진입시 focuse 기능 추가
}, []);
const handleTopButtonClick = useCallback(() => {
if (!targetId) {
return scrollTop();
}
// @@pyh Todo, 상단 Top Contents의 불확실성으로 Scenario 개정 후 작업
}, [targetId]);
return (
/* scenario page 98 */
<TPanel className={css.container}>
<TBody className={css.tBody}>
<TBody className={css.tBody} cbScrollTo={getScrollTo}>
{brandInfo && brandInfo.length > 1 && (
<QuickMenu
brandInfo={brandInfo}
@@ -265,7 +276,13 @@ export default function FeaturedBrandsPanel() {
{brandSeriesGroupInfo?.length > 0 &&
brandSeriesInfo && ( // @@pyh Todo, 현재 데이터 존재 하지 않음 ,노출 조건 문의 (현재 노출 조건 존재 하지 않음)
<Series />
<Series
brandSeriesGroupInfo={brandSeriesGroupInfo}
brandSeriesInfo={brandSeriesInfo}
selectedPatnrId={selectedPatnrId}
selectedSeriesId={selectedSeriesId}
setSelectedSeriesId={setSelectedSeriesId}
/>
)}
{brandCategoryInfo &&
@@ -279,6 +296,12 @@ export default function FeaturedBrandsPanel() {
setSelectedCatCdLv2={setSelectedCatCdLv2}
/>
)}
<TButton
onClick={handleTopButtonClick}
size={null}
type={TYPES.topButton}
/>
</TBody>
</TPanel>
);

View File

@@ -1,4 +1,4 @@
import React from "react";
import React, { memo } from "react";
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
import { $L } from "../../../utils/helperMethods";
@@ -11,7 +11,7 @@ const STRING_CONF = {
CATEGORY: $L("CATEGORY"),
};
export default function FeaturedCategory({
export default memo(function FeaturedCategory({
brandCategoryInfo,
brandCategoryProductInfo,
selectedCatCdLv1,
@@ -19,12 +19,12 @@ export default function FeaturedCategory({
setSelectedCatCdLv1,
setSelectedCatCdLv2,
}) {
const catCdLv1Array = brandCategoryInfo.map((obj) => obj["catCdLv1"]);
const catCdArrayLv1 = brandCategoryInfo.map((obj) => obj["catCdLv1"]);
const brandCategoryProductInfoLv1 = brandCategoryProductInfo.filter(
({ catCd }) => catCdLv1Array.includes(catCd)
({ catCd }) => catCdArrayLv1.includes(catCd)
);
const brandCategoryProductInfoLv2 = brandCategoryProductInfo.filter(
({ catCd }) => !catCdLv1Array.includes(catCd)
({ catCd }) => !catCdArrayLv1.includes(catCd)
);
return (
@@ -87,4 +87,4 @@ export default function FeaturedCategory({
)}
</div>
);
}
});

View File

@@ -1,4 +1,4 @@
import React from "react";
import React, { memo } from "react";
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
import { $L } from "../../../utils/helperMethods";
@@ -10,7 +10,7 @@ const STRING_CONF = {
FEATURED_CREATORS: $L("FEATURED CREATORS"),
};
export default function FeaturedCreators({
export default memo(function FeaturedCreators({
brandCreatorsInfo,
barndCreatorsShowInfo,
selectedHstNm,
@@ -66,4 +66,4 @@ export default function FeaturedCreators({
)}
</div>
);
}
});

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from "react";
import React, { memo, useCallback } from "react";
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
import { $L } from "../../../utils/helperMethods";
@@ -18,9 +18,11 @@ import LiveVideoCard from "./LiveVideoCard/LiveVideoCard";
// soldoutFlag,
// }
const LIVE_CHANNELS_STRING = "LIVE CHANNELS";
const STRING_CONF = {
LIVE_CHANNELS: $L("LIVE CHANNELS"),
};
export default function LiveChannels({ brandChanInfo, brandChannelCnt }) {
export default memo(function LiveChannels({ brandChanInfo, brandChannelCnt }) {
const {
alamDispFlag, // UpComming 알람 여부
brandProductInfo, // 상품 정보 Array
@@ -47,7 +49,7 @@ export default function LiveChannels({ brandChanInfo, brandChannelCnt }) {
<>
{brandChannelCnt > 0 && (
<div className={css.container}>
<SectionTitle title={$L(LIVE_CHANNELS_STRING)} />
<SectionTitle title={STRING_CONF.LIVE_CHANNELS} />
{brandChannelCnt === 1 && (
// @@pyh Todo, 오류 또는 장애 발생으로, Live방송이 나오지 않을 경우 처리 (Scenario, page 102)
@@ -81,4 +83,4 @@ export default function LiveChannels({ brandChanInfo, brandChannelCnt }) {
)}
</>
);
}
});

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from "react";
import React, { memo, useCallback } from "react";
import { VirtualGridList } from "@enact/sandstone/VirtualList";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
@@ -17,7 +17,7 @@ const Container = SpotlightContainerDecorator(
"nav"
);
export default function QuickMenu({
export default memo(function QuickMenu({
brandInfo,
selectedPatnrId,
setSelectedPatnrId,
@@ -58,4 +58,4 @@ export default function QuickMenu({
)}
</Container>
);
}
});

View File

@@ -9,6 +9,10 @@ import css from "./QuickMenuItem.module.less";
const SpottableComponent = Spottable("div");
const STRING_CONF = {
NEW: $L("NEW"),
};
export default memo(function QuickMenuItem({
brandInfo,
index,
@@ -42,7 +46,7 @@ export default memo(function QuickMenuItem({
{...rest}
>
<img src={logoImgPath} alt={logoImgAlt} />
{newFlag === "Y" && <div>{$L("NEW")}</div>}
{newFlag === "Y" && <div>{STRING_CONF.NEW}</div>}
<span />
</SpottableComponent>
);

View File

@@ -1,4 +1,4 @@
import React from "react";
import React, { memo } from "react";
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
import { $L } from "../../../utils/helperMethods";
@@ -10,7 +10,7 @@ const STRING_CONF = {
RECOMMENDED_SHOWS: $L("RECOMMENDED SHOWS"),
};
export default function RecommendedShows({
export default memo(function RecommendedShows({
brandRecommendedShowCategoryInfo,
brandRecommendedShowInfo,
selectedCatCd,
@@ -53,4 +53,4 @@ export default function RecommendedShows({
)}
</div>
);
}
});

View File

@@ -1,17 +1,61 @@
import React from "react";
import React, { memo } from "react";
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
import { $L } from "../../../utils/helperMethods";
import css from "./Series.module.less";
import SeriesContents from "./SeriesContents/SeriesContents";
import SeriesNav from "./SeriesNav/SeriesNav";
const STRING_CONF = {
SERIES: $L("SERIES"),
};
export default function Series() {
export default memo(function Series({
brandSeriesGroupInfo,
brandSeriesInfo,
selectedPatnrId,
selectedSeriesId,
setSelectedSeriesId,
}) {
return (
<div className={css.container}>
<SectionTitle title={STRING_CONF.SERIES} />
<SeriesNav
brandSeriesInfo={brandSeriesInfo}
selectedPatnrId={selectedPatnrId}
selectedSeriesId={selectedSeriesId}
setSelectedSeriesId={setSelectedSeriesId}
/>
{brandSeriesGroupInfo &&
brandSeriesGroupInfo.map(
(
// @@pyh Todo, delete comment, later
{
brandSeriesProductInfo,
// logoImgAlt,
// logoImgNm,
// logoImgPath,
patnrId,
// prdtTotalCnt,
seriesId,
seriesImgUrl,
seriesNm,
},
index
) => (
<SeriesContents
brandSeriesProductInfo={brandSeriesProductInfo}
index={index}
key={"brandSeriesGroupInfo-" + index}
patnrId={patnrId}
selectedPatnrId={selectedPatnrId}
selectedSeriesId={selectedSeriesId}
seriesId={seriesId}
seriesImgUrl={seriesImgUrl}
seriesNm={seriesNm}
/>
)
)}
</div>
);
}
});

View File

@@ -0,0 +1,46 @@
import React from "react";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import Spottable from "@enact/spotlight/Spottable";
import css from "./SeriesContents.module.less";
import SeriesProductList from "./SeriesProductList/SeriesProductList";
const Container = SpotlightContainerDecorator(
{ leaveFor: { right: "" }, enterTo: "last-focused" },
"div"
);
const SpottableComponent = Spottable("figure");
export default function SeriesContents({
brandSeriesProductInfo,
index,
patnrId,
selectedPatnrId,
selectedSeriesId,
seriesId,
seriesImgUrl,
seriesNm,
}) {
return (
<Container
className={css.container}
id={"featuredSeriesContentsId-" + index}
spotlightId={"featuredSeriesContentsId-" + index}
>
<h3>{seriesNm}</h3>
<div>
<SpottableComponent>
<img src={seriesImgUrl} alt={seriesNm} />
</SpottableComponent>
<SeriesProductList
brandSeriesProductInfo={brandSeriesProductInfo}
selectedSeriesId={selectedSeriesId}
selectedPatnrId={selectedPatnrId}
patnrId={patnrId}
/>
</div>
</Container>
);
}

View File

@@ -0,0 +1,40 @@
@import "../../../../style/CommonStyle.module.less";
@import "../../../../style/utils.module.less";
.container {
margin-bottom: 36px;
padding-left: 60px;
// contents title
h3 {
margin-bottom: 18px;
.font(@fontFamily: @arialFontBold, @fontSize: 36px);
color: @COLOR_GRAY08;
}
// contents content
> div:nth-child(2) {
display: flex;
.size(@w: 100%, @h:438px);
figure {
position: relative;
.size(@w: 663px, @h:438px);
padding: 18px;
background-color: @COLOR_WHITE;
border: solid 1px @COLOR_WHITE;
border-radius: 12px;
img {
.size(@w: 627px, @h:402px);
object-fit: cover;
}
&:focus {
&::after {
.focused(@boxShadow: 22px, @borderRadius: 12px);
}
}
}
}
}

View File

@@ -0,0 +1,72 @@
import React, { useCallback, useEffect } from "react";
import { useDispatch } from "react-redux";
import { pushPanel } from "../../../../../actions/panelActions";
import TItemCard from "../../../../../components/TItemCard/TItemCard";
import TVirtualGridList from "../../../../../components/TVirtualGridList/TVirtualGridList";
import useScrollTo from "../../../../../hooks/useScrollTo";
import { panel_names } from "../../../../../utils/Config";
import css from "./SeriesProductList.module.less";
export default function SeriesProductList({
brandSeriesProductInfo,
selectedPatnrId,
selectedSeriesId,
patnrId,
}) {
const { getScrollTo, scrollLeft } = useScrollTo();
const dispatch = useDispatch();
useEffect(() => {
scrollLeft();
}, [selectedPatnrId, selectedSeriesId]);
const renderItem = useCallback(
({ index, ...rest }) => {
const { prdtId, prdtImgUrl, prdtNm, priceInfo, soldoutFlag } =
brandSeriesProductInfo[index];
const handleClick = () => {
dispatch(
pushPanel({
name: panel_names.DETAIL_PANEL,
panelInfo: { patnrId, prdtId },
})
);
};
return (
<TItemCard
imageAlt={prdtNm}
imageSource={prdtImgUrl}
onClick={handleClick}
priceInfo={priceInfo}
productId={prdtId}
productName={prdtNm}
soldoutFlag={soldoutFlag}
{...rest}
/>
);
},
[brandSeriesProductInfo, dispatch, patnrId]
);
return (
<div className={css.container}>
{brandSeriesProductInfo && (
<TVirtualGridList
cbScrollTo={getScrollTo}
className={css.tVirtualGridList}
dataSize={brandSeriesProductInfo.length}
direction="horizontal"
itemHeight={438}
itemWidth={324}
spacing={18}
renderItem={renderItem}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,7 @@
@import "../../../../../style/utils.module.less";
.container {
.flex();
width: calc(100% - 663px);
padding-left: 18px;
}

View File

@@ -0,0 +1,6 @@
{
"main": "SeriesProductList.jsx",
"styles": [
"SeriesProductList.module.less"
]
}

View File

@@ -0,0 +1,6 @@
{
"main": "SeriesContents.jsx",
"styles": [
"SeriesContents.module.less"
]
}

View File

@@ -0,0 +1,91 @@
import React, { useCallback, useEffect } from "react";
import Scroller from "@enact/sandstone/Scroller";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import TButton, { TYPES } from "../../../../components/TButton/TButton";
import useScrollReset from "../../../../hooks/useScrollReset";
import useScrollTo from "../../../../hooks/useScrollTo";
import { $L } from "../../../../utils/helperMethods";
import css from "./SeriesNav.module.less";
const Container = SpotlightContainerDecorator(
{ leaveFor: { right: "" }, enterTo: "last-focused" },
"nav"
);
const STRING_CONF = {
ALL: $L("ALL"),
};
export default function SeriesNav({
brandSeriesInfo,
selectedPatnrId,
selectedSeriesId,
setSelectedSeriesId,
}) {
const { getScrollTo, scrollLeft } = useScrollTo();
const { handleScrollReset, handleStopScrolling } = useScrollReset(
scrollLeft,
true
);
const handleBlur = useCallback(() => {
handleStopScrolling();
}, []);
const handleClick = useCallback((seriesId) => {
setSelectedSeriesId(seriesId);
}, []);
const handleFocus = useCallback(() => {
handleScrollReset();
}, []);
useEffect(() => {
scrollLeft();
}, [selectedPatnrId]);
return (
<Container className={css.nav}>
<Scroller
cbScrollTo={getScrollTo}
direction="horizontal"
horizontalScrollbar="hidden"
noScrollByWheel
scrollMode="translate"
>
<ul>
<li>
<TButton
className={!selectedSeriesId && css.selected}
onBlur={handleBlur}
onClick={() => handleClick("")}
onFocus={handleFocus}
selected={!selectedSeriesId}
type={TYPES.oneDepthCategory}
>
{STRING_CONF.ALL}
</TButton>
</li>
{brandSeriesInfo &&
brandSeriesInfo.map(({ seriesId, seriesNm }, index) => (
<li key={"brandSeriesInfo-" + index}>
<TButton
className={
selectedSeriesId &&
selectedSeriesId === seriesId &&
css.selected
}
onClick={() => handleClick(seriesId)}
selected={selectedSeriesId && selectedSeriesId === seriesId}
type={TYPES.oneDepthCategory}
>
{seriesNm}
</TButton>
</li>
))}
</ul>
</Scroller>
</Container>
);
}

View File

@@ -0,0 +1,48 @@
@import "../../../../style/CommonStyle.module.less";
@import "../../../../style/utils.module.less";
.nav {
.size(@w: 100%, @h: 162px);
margin-bottom: 12px;
background-color: #ddd;
ul {
display: flex;
align-items: center;
height: inherit;
padding-left: 60px;
border-bottom: 18px solid @BG_COLOR_01;
&::before {
position: absolute;
right: -100%;
bottom: 0;
.size(@w: 100%, @h: 18px);
background-color: @BG_COLOR_01;
content: "";
}
li {
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: "";
}
}
}
}
}
}

View File

@@ -0,0 +1,6 @@
{
"main": "SeriesNav.jsx",
"styles": [
"SeriesNav.module.less"
]
}

View File

@@ -1,4 +1,4 @@
import React from "react";
import React, { memo } from "react";
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
import { $L } from "../../../utils/helperMethods";
@@ -9,11 +9,11 @@ const STRING_CONF = {
UPCOMING: $L("UPCOMING"),
};
export default function UpComing({ brandLiveChannelUpcoming }) {
export default memo(function UpComing({ brandLiveChannelUpcoming }) {
return (
<div className={css.container}>
<SectionTitle title={STRING_CONF.UPCOMING} />
<UpComingList brandLiveChannelUpcoming={brandLiveChannelUpcoming} />
</div>
);
}
});