[FeaturedBrandsPanel] section, Showroom 추가

Detail Notes :
1. UI, UX 추가
This commit is contained in:
younghoon100.park
2024-04-01 15:20:46 +09:00
parent 2867059f08
commit b99c16bc39
16 changed files with 551 additions and 23 deletions

View File

@@ -21,6 +21,7 @@ import {
getBrandLiveChannelInfo,
getBrandRecommendedShowInfo,
getBrandSeriesInfo,
getBrandShowroom,
getBrandTSVInfo,
} from "../../actions/brandActions";
import { changeAppStatus, setHidePopup } from "../../actions/commonActions";
@@ -39,6 +40,7 @@ import LiveChannels from "./LiveChannels/LiveChannels";
import QuickMenu from "./QuickMenu/QuickMenu";
import RecommendedShows from "./RecommendedShows/RecommendedShows";
import Series from "./Series/Series";
import Showroom from "./Showroom/Showroom";
import TodaysDeals from "./TodaysDeals/TodaysDeals";
import UpComing from "./UpComing/UpComing";
@@ -74,6 +76,7 @@ const TEMPLATE_CODE_CONF = {
FEATURED_CREATORS: "BRD00106",
SERIES: "BRD00107",
CATEGORY: "BRD00108",
SHOWROOM: "BRD00109",
};
const STRING_CONF = {
@@ -181,6 +184,13 @@ export default function FeaturedBrandsPanel() {
(state) => state.brand.brandCategoryInfoData.status
);
const brandShowroomInfo = useSelector(
(state) => state.brand.brandShowroomData.data.brandShowroomInfo
);
const brandShowroomStatus = useSelector(
(state) => state.brand.brandShowroomData.status
);
const [selectedPatnrId, setSelectedPatnrId] = useState();
const [selectedBrandInfo, setSelectedBrandInfo] = useState();
const [selectedCatCd, setSelectedCatCd] = useState();
@@ -216,6 +226,7 @@ export default function FeaturedBrandsPanel() {
brandCreatorsInfoDataStatus,
brandSeriesInfoDataStatus,
brandCategoryInfoDataStatus,
brandShowroomStatus,
].some((status) => status !== "fulfilled"),
[
brandInfoDataStatus,
@@ -227,10 +238,10 @@ export default function FeaturedBrandsPanel() {
brandCreatorsInfoDataStatus,
brandSeriesInfoDataStatus,
brandCategoryInfoDataStatus,
brandShowroomStatus,
]
);
// loading
useEffect(() => {
const showLoadingPanel = isLoading
? { show: true, type: "wait" }
@@ -270,8 +281,6 @@ export default function FeaturedBrandsPanel() {
};
}, []);
// @@pyh Todo, test ref
const timerRef = useRef();
useEffect(() => {
if (brandInfo && selectedPatnrId) {
setSelectedBrandInfo(findItemByValue(brandInfo, selectedPatnrId));
@@ -281,32 +290,26 @@ export default function FeaturedBrandsPanel() {
setSelectedCatCdLv1();
setSelectedCatCdLv2();
clearTimeout(timerRef.current);
dispatch(getBrandLayoutInfo({ patnrId: selectedPatnrId }));
dispatch(getBrandLiveChannelInfo({ patnrId: selectedPatnrId }));
dispatch(getBrandTSVInfo({ patnrId: selectedPatnrId }));
dispatch(getBrandBestSeller({ patnrId: selectedPatnrId }));
dispatch(getBrandRecommendedShowInfo({ patnrId: selectedPatnrId }));
dispatch(getBrandCreatorsInfo({ patnrId: selectedPatnrId }));
dispatch(getBrandSeriesInfo({ patnrId: selectedPatnrId }));
dispatch(
getBrandCategoryInfo({
patnrId: selectedPatnrId,
})
);
dispatch(getBrandRecommendedShowInfo({ patnrId: selectedPatnrId }));
// @@pyh Todo, test timeout
timerRef.current = setTimeout(() => {
dispatch(getBrandLiveChannelInfo({ patnrId: selectedPatnrId }));
dispatch(getBrandLayoutInfo({ patnrId: selectedPatnrId }));
dispatch(getBrandSeriesInfo({ patnrId: selectedPatnrId }));
dispatch(getBrandTSVInfo({ patnrId: selectedPatnrId }));
dispatch(getBrandBestSeller({ patnrId: selectedPatnrId }));
dispatch(getBrandCreatorsInfo({ patnrId: selectedPatnrId }));
}, 50);
dispatch(
getBrandShowroom({
patnrId: selectedPatnrId,
})
);
}
}, [brandInfo, dispatch, selectedPatnrId]);
// @@pyh Todo, test unmounted
useEffect(() => {
return () => clearTimeout(timerRef.current);
}, []);
useEffect(() => {
if (selectedCatCd) {
dispatch(
@@ -372,7 +375,7 @@ export default function FeaturedBrandsPanel() {
scrollTop();
}, []);
// @@pyh Todo, @@test, temporary func, edited later
// pyh Todo, test, temporary func, edited later
const handlePopupClick = useCallback(() => {
dispatch({
type: "SET_MY_UPCOMING_USE_ALERT",
@@ -382,11 +385,31 @@ export default function FeaturedBrandsPanel() {
dispatch(setHidePopup());
}, [dispatch]);
// @@pyh Todo, @@test, temporary func, edited later
// pyh Todo, test, temporary func, edited later
const handlePopupClose = useCallback(() => {
dispatch(setHidePopup());
}, [dispatch]);
// pyh, test func by boss
const handleTopButtonClick2 = useCallback(() => {
const ar = [1, 2, 4, 9, 11];
const randomIndex = Math.floor(Math.random() * ar.length);
const randomIndex2 = Math.floor(Math.random() * ar.length);
dispatch(
getBrandCategoryInfo({
patnrId: ar[randomIndex],
})
);
setTimeout(() => {
dispatch(
getBrandRecommendedShowInfo({
patnrId: ar[randomIndex2],
})
);
}, 10);
}, []);
return (
/* scenario page 98 */
<TPanel className={css.tPanel}>
@@ -547,6 +570,21 @@ export default function FeaturedBrandsPanel() {
setSelectedCatCdLv2={setSelectedCatCdLv2}
/>
)}
{hasTemplateCodeWithValue(
brandLayoutInfo,
TEMPLATE_CODE_CONF.SHOWROOM
) &&
shouldRenderComponent(brandShowroomInfo) && (
<Showroom
brandShowroomInfo={brandShowroomInfo}
order={getOrderByValue(
brandLayoutInfo,
TEMPLATE_CODE_CONF.SHOWROOM
)}
selectedPatnrId={selectedPatnrId}
/>
)}
</div>
)}

View File

@@ -0,0 +1,70 @@
import React, { memo, useEffect, useMemo, useState } from "react";
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
import { $L } from "../../../utils/helperMethods";
import css from "./Showroom.module.less";
import ShowroomContents from "./ShowroomContents/ShowroomContents";
import ShowroomNav from "./ShowroomNav/ShowroomNav";
const STRING_CONF = {
SHOWROOM: $L("SHOWROOM"),
};
export default memo(function Showroom({
brandShowroomInfo,
order,
selectedPatnrId,
}) {
const [selectedRoomId, setSelectedRoomId] = useState();
const orderStyle = useMemo(() => ({ order: order }), [order]);
const selectedRoomThemeInfos = useMemo(
() =>
brandShowroomInfo //
.find((info) => info.roomId === selectedRoomId)?.roomThemeInfos,
[brandShowroomInfo, selectedRoomId]
);
useEffect(() => {
if (!selectedRoomId) {
setSelectedRoomId(brandShowroomInfo[0]?.roomId);
}
}, [brandShowroomInfo]);
return (
<div className={css.container} style={orderStyle}>
<SectionTitle title={STRING_CONF.SHOWROOM} />
<ShowroomNav
brandShowroomInfo={brandShowroomInfo}
selectedPatnrId={selectedPatnrId}
selectedRoomId={selectedRoomId}
setSelectedRoomId={setSelectedRoomId}
/>
{selectedRoomThemeInfos &&
selectedRoomThemeInfos.map(
(
{
roomThemeProducts,
themeExpsOrd,
// themeId,
themeImgUrl,
themeNm,
},
index
) => (
<ShowroomContents
key={"roomThemeInfos-" + index}
roomThemeProducts={roomThemeProducts}
selectedPatnrId={selectedPatnrId}
selectedRoomId={selectedRoomId}
themeExpsOrd={themeExpsOrd}
// themeId={themeId}
themeImgUrl={themeImgUrl}
themeNm={themeNm}
/>
)
)}
</div>
);
});

View File

@@ -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;
}
}

View File

@@ -0,0 +1,72 @@
import React, { useEffect } from "react";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import {
getContainerId,
setContainerLastFocusedElement,
} from "@enact/spotlight/src/container";
import css from "./ShowroomContents.module.less";
import ShowroomProductList from "./ShowroomProductList/ShowroomProductList";
import ShowroomThumbnailCard from "./ShowroomThumbnailCard/ShowroomThumbnailCard";
const Container = SpotlightContainerDecorator(
{ leaveFor: { right: "" }, enterTo: "last-focused" },
"div"
);
export default function ShowroomContents({
roomThemeProducts,
selectedPatnrId,
selectedRoomId,
themeExpsOrd,
// themeId,
themeImgUrl,
themeNm,
...rest
}) {
useEffect(() => {
const containerId = "showroomContentsId-" + themeExpsOrd;
const container = document.getElementById(containerId);
if (container) {
const thumbnailCard = container.children[1].children[0].children[0];
thumbnailCard &&
setContainerLastFocusedElement(thumbnailCard, [containerId]);
const gridListId = getContainerId(
container.children[1].children[1].children[0].children[0]
);
const gridList =
container.children[1].children[1].children[0].children[0];
gridList && setContainerLastFocusedElement(null, [gridListId]);
}
}, [selectedPatnrId, selectedRoomId]);
const handleClick = () => {
// pyh, add something later..
};
return (
<Container
className={css.container}
id={"showroomContentsId-" + themeExpsOrd}
spotlightId={"showroomContentsId-" + themeExpsOrd}
{...rest}
>
<h3>{themeNm}</h3>
<div>
<ShowroomThumbnailCard
thumbnailUrl={themeImgUrl}
onClick={handleClick}
/>
<ShowroomProductList
roomThemeProducts={roomThemeProducts}
selectedPatnrId={selectedPatnrId}
selectedRoomId={selectedRoomId}
/>
</div>
</Container>
);
}

View File

@@ -0,0 +1,19 @@
@import "../../../../style/CommonStyle.module.less";
@import "../../../../style/utils.module.less";
.container {
padding-left: 60px;
margin-bottom: 14px;
> h3 {
margin-bottom: -4px;
.font(@fontFamily: @arialFontBold, @fontSize: 36px);
color: @COLOR_GRAY08;
}
> div {
.flex(@justifyCenter: flex-start);
.size(@w: 100%, @h: auto);
margin-bottom: -8px;
}
}

View File

@@ -0,0 +1,78 @@
import React, { memo, 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 "./ShowroomProductList.module.less";
export default memo(function ShowroomProductList({
roomThemeProducts,
selectedPatnrId,
selectedRoomId,
}) {
const { getScrollTo, scrollLeft } = useScrollTo();
const dispatch = useDispatch();
useEffect(() => {
scrollLeft();
}, [selectedPatnrId, selectedRoomId]);
const renderItem = useCallback(
({ index, ...rest }) => {
const {
offerInfo,
patnrId,
// prdtExpsOrd,
prdtId,
prdtImgUrl,
prdtNm,
priceInfo,
} = roomThemeProducts[index];
const handleClick = () => {
dispatch(
pushPanel({
name: panel_names.DETAIL_PANEL,
panelInfo: { patnrId, prdtId },
})
);
};
return (
<TItemCard
imageAlt={prdtNm}
imageSource={prdtImgUrl}
onClick={handleClick}
offerInfo={offerInfo}
priceInfo={priceInfo}
productId={prdtId}
productName={prdtNm}
{...rest}
/>
);
},
[roomThemeProducts, dispatch]
);
return (
<div className={css.container}>
{roomThemeProducts && (
<TVirtualGridList
cbScrollTo={getScrollTo}
className={css.tVirtualGridList}
dataSize={roomThemeProducts.length}
direction="horizontal"
itemHeight={438}
itemWidth={324}
spacing={18}
renderItem={renderItem}
/>
)}
</div>
);
});

View File

@@ -0,0 +1,17 @@
@import "../../../../../style/utils.module.less";
.container {
.flex();
overflow: hidden;
.size(@w: calc(100% - 663px), @h: 482px);
padding-left: 18px;
// tVirtualGridListContainer
> div:nth-child(1) {
.size(@w: 100%, @h: inherit);
> div:nth-child(1) {
padding: 22px 0;
}
}
}

View File

@@ -0,0 +1,6 @@
{
"main": "ShowroomProductList.jsx",
"style": [
"ShowroomProductList.moduel.less"
]
}

View File

@@ -0,0 +1,16 @@
import React, { memo } from "react";
import Spottable from "@enact/spotlight/Spottable";
import CustomImage from "../../../../../components/CustomImage/CustomImage";
import css from "./ShowroomThumbnailCard.module.less";
const SpottableComponent = Spottable("figure");
export default memo(function ShowroomThumbnailCard({ thumbnailUrl, ...rest }) {
return (
<SpottableComponent className={css.card} {...rest}>
<CustomImage alt="" delay={0} loading="lazy" src={thumbnailUrl} />
</SpottableComponent>
);
});

View File

@@ -0,0 +1,22 @@
@import "../../../../../style/CommonStyle.module.less";
@import "../../../../../style/utils.module.less";
.card {
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,6 @@
{
"main": "ShowroomThumbnailCard.jsx",
"style": [
"ShowroomThumbnailCard.module.less"
]
}

View File

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

View File

@@ -0,0 +1,98 @@
import React, { useCallback, useEffect } from "react";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import {
getContainerId,
setContainerLastFocusedElement,
} from "@enact/spotlight/src/container";
import TButton, { TYPES } from "../../../../components/TButton/TButton";
import TScroller from "../../../../components/TScroller/TScroller";
import useScrollReset from "../../../../hooks/useScrollReset";
import useScrollTo from "../../../../hooks/useScrollTo";
import css from "./ShowroomNav.module.less";
const Container = SpotlightContainerDecorator(
{ leaveFor: { right: "" }, enterTo: "last-focused" },
"nav"
);
export default function ShowroomNav({
brandShowroomInfo,
selectedPatnrId,
selectedRoomId,
setSelectedRoomId,
}) {
const { getScrollTo, scrollLeft } = useScrollTo();
const { handleScrollReset, handleStopScrolling } = useScrollReset(
scrollLeft,
true
);
useEffect(() => {
const containerId = "showroomNavId";
const container = document.getElementById(containerId);
if (container) {
const childrenId = getContainerId(container?.children[0].children[0]);
childrenId &&
setContainerLastFocusedElement(null, [containerId, childrenId]);
}
scrollLeft();
}, [selectedPatnrId]);
const handleBlur = useCallback((index) => {
if (index !== 0) {
return;
}
handleStopScrolling();
}, []);
const handleClick = useCallback((roomId) => {
setSelectedRoomId(roomId);
}, []);
const handleFocus = useCallback((index) => {
if (index !== 0) {
return;
}
handleScrollReset();
}, []);
return (
<Container
className={css.nav}
id={"showroomNavId"}
spotlightId={"showroomNavId"}
>
<TScroller
cbScrollTo={getScrollTo}
direction="horizontal"
noScrollByWheel
>
<ul>
{brandShowroomInfo &&
brandShowroomInfo.map(({ roomId, roomNm }, index) => (
<li key={"brandShowroomInfo-" + roomId}>
<TButton
className={
selectedRoomId && selectedRoomId === roomId && css.selected
}
onBlur={() => handleBlur(index)}
onClick={() => handleClick(roomId)}
onFocus={() => handleFocus(index)}
selected={selectedRoomId && selectedRoomId === roomId}
type={TYPES.oneDepthCategory}
>
{roomNm}
</TButton>
</li>
))}
</ul>
</TScroller>
</Container>
);
}

View File

@@ -0,0 +1,56 @@
@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;
// tScroller
> 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: "";
}
}
}
}
}
}

View File

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

View File

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