[251211] feat: FeaturedBrandsPanel , See More Products - 3

🕐 커밋 시간: 2025. 12. 11. 12:29:51

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

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/SeeMoreProducts/SeeMoreProducts.jsx
  + com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/SeeMoreProducts/SeeMoreProducts.module.less

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
This commit is contained in:
2025-12-11 12:29:51 +09:00
parent 05f5bf4d33
commit a85710421c
3 changed files with 328 additions and 5 deletions

View File

@@ -116,6 +116,7 @@ import UserReviews from '../ProductContentSection/UserReviews/UserReviews';
// import ViewAllReviewsButton from '../ProductContentSection/UserReviews/ViewAllReviewsButton'; // import ViewAllReviewsButton from '../ProductContentSection/UserReviews/ViewAllReviewsButton';
import YouMayAlsoLike import YouMayAlsoLike
from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike'; from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
import SeeMoreProducts from '../ProductContentSection/SeeMoreProducts/SeeMoreProducts';
import QRCode from '../ProductInfoSection/QRCode/QRCode'; import QRCode from '../ProductInfoSection/QRCode/QRCode';
import ProductOverview from '../ProductOverview/ProductOverview'; import ProductOverview from '../ProductOverview/ProductOverview';
// CSS imports // CSS imports
@@ -1005,6 +1006,7 @@ export default function ProductAllSection({
const [isShowUserReviewsFocused, setIsShowUserReviewsFocused] = useState(false); const [isShowUserReviewsFocused, setIsShowUserReviewsFocused] = useState(false);
// 🆕 [251210] patnrId=21 SEE MORE PRODUCTS 버튼 표시 여부 // 🆕 [251210] patnrId=21 SEE MORE PRODUCTS 버튼 표시 여부
const [hasSeeMoreProducts, setHasSeeMoreProducts] = useState(false); const [hasSeeMoreProducts, setHasSeeMoreProducts] = useState(false);
const [seeMoreProductsData, setSeeMoreProductsData] = useState([]);
const reviewTotalCount = stats.totalReviews; const reviewTotalCount = stats.totalReviews;
@@ -1346,8 +1348,25 @@ export default function ProductAllSection({
console.log(` - Price: ${product.priceInfo}`); console.log(` - Price: ${product.priceInfo}`);
console.log(` - Brand: ${product.brndNm || 'N/A'}`); console.log(` - Brand: ${product.brndNm || 'N/A'}`);
}); });
// 🆕 SeeMoreProducts 컴포넌트를 위한 데이터 변환
const formattedProducts = otherProducts.map(product => ({
prdtId: product.prdtId,
prdtNm: product.prdtNm,
priceInfo: product.priceInfo,
patncNm: foundConts.patncNm,
patnrId: foundConts.patnrId,
brndNm: product.brndNm,
imgUrl: product.prdtImgUrl,
lgCatCd: product.lgCatCd,
offerInfo: product.offerInfo,
}));
// YouMayAlsoLike 데이터 형식으로 맞추기
setSeeMoreProductsData(formattedProducts);
} else { } else {
console.log('[SEE MORE PRODUCTS] ❌ No other products in group - hiding button'); console.log('[SEE MORE PRODUCTS] ❌ No other products in group - hiding button');
setSeeMoreProductsData([]);
} }
} else { } else {
console.log('[SEE MORE PRODUCTS] ❌ No group found for current product - hiding button'); console.log('[SEE MORE PRODUCTS] ❌ No group found for current product - hiding button');
@@ -2060,11 +2079,12 @@ export default function ProductAllSection({
<div ref={seeMoreProductsRef}> <div ref={seeMoreProductsRef}>
<div id="scroll-marker-see-more-products" className={css.scrollMarker}></div> <div id="scroll-marker-see-more-products" className={css.scrollMarker}></div>
<div id="see-more-products-section" data-spotlight-id="see-more-products-area"> <div id="see-more-products-section" data-spotlight-id="see-more-products-area">
{/* TODO: 나중에 그룹 상품 표시 컴포넌트 추가 */} <SeeMoreProducts
<div style={{ padding: '20px', textAlign: 'center', color: '#666' }}> groupProducts={seeMoreProductsData}
<h3>SEE MORE PRODUCTS</h3> panelInfo={panelInfo}
<p>그룹 상품이 여기에 표시될 예정입니다</p> onFocus={() => handleButtonFocus('seemoreproducts')}
</div> onBlur={handleButtonBlur}
/>
</div> </div>
</div> </div>
)} )}

View File

@@ -0,0 +1,197 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Job } from '@enact/core/util';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import { clearThemeDetail } from '../../../../actions/homeActions';
import { finishModalMediaForce } from '../../../../actions/mediaActions';
import { popPanel, pushPanel, updatePanel } from '../../../../actions/panelActions';
import { finishVideoPreview } from '../../../../actions/playActions';
import THeader from '../../../../components/THeader/THeader';
import TItemCardNew from '../../../../components/TItemCard/TItemCard.new';
import TVerticalPagenator from '../../../../components/TVerticalPagenator/TVerticalPagenator';
import useScrollTo from '../../../../hooks/useScrollTo';
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../../utils/Config';
import { $L } from '../../../../utils/helperMethods';
import css from './SeeMoreProducts.module.less';
const SpottableComponent = Spottable('div');
const Container = SpotlightContainerDecorator(
{
enterTo: 'last-focused',
leaveFor: {
left: 'spotlight-product-info-section-container',
},
},
'div'
);
export default function SeeMoreProducts({
groupProducts,
panelInfo,
onFocus,
onBlur,
}) {
const { getScrollTo, scrollLeft } = useScrollTo();
const [newGroupProductData, setNewGroupProductData] = useState([]);
const dispatch = useDispatch();
const focusedContainerIdRef = useRef(null);
const panels = useSelector((state) => state.panels.panels);
const themeProductInfos = useSelector((state) => state.home.themeCurationDetailInfoData);
const launchedFromPlayer = useMemo(() => {
const detailPanelIndex = panels.findIndex(({ name }) => name === 'detailpanel');
const playerPanelIndex = panels.findIndex(({ name }) => name === 'playerpanel');
return detailPanelIndex - 1 === playerPanelIndex;
}, [panels]);
const onFocusedContainerId = useCallback((containerId) => {
focusedContainerIdRef.current = containerId;
}, []);
const _onFocus = useCallback(() => {
if (onFocus) {
onFocus();
}
}, [onFocus]);
const _onBlur = useCallback(() => {
if (onBlur) {
onBlur();
}
}, [onBlur]);
// 그룹 상품 데이터 처리 (YOU MAY ALSO LIKE와 동일 로직)
useEffect(() => {
console.log('[SeeMoreProducts] 그룹 상품 데이터 처리:', {
originalData: groupProducts,
originalLength: groupProducts?.length || 0,
hasData: !!(groupProducts && groupProducts.length > 0)
});
if (groupProducts && groupProducts.length > 0) {
// 최대 9개로 제한 (YOU MAY ALSO LIKE와 동일)
const processedData = groupProducts.length > 9
? groupProducts.slice(0, groupProducts.length - 1)
: groupProducts;
console.log('[SeeMoreProducts] 처리된 데이터 설정:', {
processedLength: processedData.length,
processedData
});
setNewGroupProductData(processedData);
} else {
console.log('[SeeMoreProducts] 데이터 없음 - 빈 배열 설정');
setNewGroupProductData([]);
}
}, [groupProducts]);
const cursorOpen = useRef(new Job((func) => func(), 1000));
return (
<div>
{newGroupProductData && newGroupProductData.length > 0 && (
<TVerticalPagenator
spotlightId={'detail_seeMoreProducts_area'}
data-wheel-point={true}
className={css.tVerticalPagenator}
defaultContainerId={panelInfo?.focusedContainerId}
onFocusedContainerId={onFocusedContainerId}
topMargin={36}
>
<Container className={css.container} onFocus={_onFocus} onBlur={_onBlur}>
<THeader title={$L('SEE MORE PRODUCTS')} className={css.tHeader} />
<div className={css.renderCardContainer}>
{newGroupProductData?.map((product, index) => {
const {
imgUrl: prdtImgUrl, // 이미지 URL 변경
patnrId,
prdtId,
prdtNm,
priceInfo,
offerInfo,
patncNm,
brndNm,
lgCatCd,
euEnrgLblInfos,
} = product;
const handleItemClick = () => {
console.log('[SeeMoreProducts] 상품 클릭:', product);
// Promise 체이닝으로 순서 보장 (YOU MAY ALSO LIKE와 동일)
Promise.resolve()
.then(() => {
// 1. 기존 비디오 강제 종료
dispatch(finishVideoPreview());
dispatch(finishModalMediaForce());
if (themeProductInfos && themeProductInfos.length > 0) {
dispatch(clearThemeDetail());
}
})
.then(() => {
// 2. 비디오 종료 후 새로운 상품으로 업데이트
dispatch(
updatePanel({
name: panel_names.DETAIL_PANEL,
panelInfo: {
showNm: panelInfo?.showNm,
showId: panelInfo?.showId,
liveFlag: panelInfo?.liveFlag,
thumbnailUrl: panelInfo?.thumbnailUrl,
patnrId,
prdtId,
launchedFromPlayer: launchedFromPlayer,
fromPanel: {
fromSeeMoreProducts: true, // 🆕 SeeMoreProducts에서 선택된 상품임을 표시
},
},
})
);
cursorOpen.current.stop();
});
};
// prdtId가 없는 경우를 대비한 안정적인 key 생성
const itemKey = prdtId ? `${patnrId}-${prdtId}` : `see-more-product-${index}`;
// 🆕 [251210] TItemCardNew에 spotlightId와 spottable 설정 추가
return (
<TItemCardNew
key={itemKey}
className={css.itemCardNew}
contextName={LOG_CONTEXT_NAME.YOUMAYLIKE}
messageId={LOG_MESSAGE_ID.CONTENTCLICK}
productId={prdtId}
nowProductId={panelInfo?.prdtId}
nowProductTitle={panelInfo?.prdtNm}
nowCategory={panelInfo?.catNm}
catNm={lgCatCd}
patnerName={patncNm}
brandName={brndNm}
imageAlt={prdtId}
imageSource={prdtImgUrl}
priceInfo={priceInfo}
offerInfo={offerInfo}
productName={prdtNm}
onClick={handleItemClick}
label={index * 1 + 1 + ' of ' + newGroupProductData.length}
lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos}
/>
);
})}
</div>
</Container>
</TVerticalPagenator>
)}
</div>
);
}

View File

@@ -0,0 +1,106 @@
@import "../../../../style/CommonStyle.module.less";
@import "../../../../style/utils.module.less";
// .container {
// .size(@w: 874px,@h:500px);
// .itemWrapper {
// .size(@w: 874px,@h:500px);
// .item {
// .size(@w: 300px,@h:300px);
// }
// }
// }
.tVerticalPagenator {
.size(@w: 1114px, @h: auto); // 마진 포함 전체 크기 (1054px + 60px)
max-width: 1114px;
// padding-left: 30px; // 좌측 30px 마진
// padding-right: 30px; // 우측 30px 마진
box-sizing: border-box;
// .sectionTitle {
// .font(@fontFamily: @baseFont, @fontSize: 30px);
// min-height: 56px;
// font-weight: 700;
// color: rgba(234, 234, 234, 1);
// // margin: 30px 0 20px 0;
// }
.tHeader {
background: transparent;
.size(@w: 1144px, @h: 36px); // 마진 제외 콘텐츠 크기
max-width: 1144px;
margin-bottom: 20px;
> div {
.size(@w:100%,@h:100%);
padding: 0;
}
.averageOverallRating {
.size(@w: 176px,@h:30px);
}
span {
font-size: 30px;
font-weight: 700;
height: 36px;
color: rgba(234, 234, 234, 1);
}
}
.container {
width: 100%;
.flex(@direction:column,@alignCenter:flex-start);
flex-wrap: wrap;
margin-top: 34px;
// > div {
// margin: 0 15px 15px 0;
// }
.renderCardContainer {
width: 1144px;
display: flex;
flex-wrap: wrap;
// margin-top: 34px;
> div.itemCardNew {
/* item card */
margin: 0 15px 15px 0;
.size(@w:360px,@h:494px);
background-color: rgba(51, 51, 51, 0.95);
border: none;
> div:nth-child(1) {
/* img wrapper*/
.size(@w:323px,@h:323px);
> img {
.size(@w:100%,@h:100%);
}
}
> div:nth-child(2) {
margin-top: 15px;
/* desc wrapper */
> div > div > h3 {
/* title */
color: rgba(234, 234, 234, 1);
.size(@w:100%,@h:64px);
line-height: 31px;
}
> p {
/* priceInfo */
height: 43px;
text-align: center;
> span {
font-size: 24px;
}
}
}
// width: 100%;
// padding-left: 60px;
// overflow: unset;
}
}
}
}