[상품 상세] 리뷰 및 디테일 변경건.#3

1. 상품 상세 진입시 초기 포커스 shop by mobile 로 변경.
		- spotlightids추가.
	2. 상품상세 scroll down추가.
		- 하단부 도달했을때 노출되지않도록 처리.
		- 클릭시 200px씩 이동.
	3. 리뷰팝업 부분 스타일변경
		- 호버시 이미지 확대부분 부자연스럽지 않도록 변경.
	4. 상품 상세 우측 부분에서의 포커스 이동시 좌측 버튼부분의 호버처리.
		- 포커스 이동시에 자연스럽게 호버 이동가능하도록 변경
This commit is contained in:
junghoon86.park
2025-09-12 10:39:41 +09:00
parent d2a9388bd5
commit ddf7c352eb
10 changed files with 796 additions and 401 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

View File

@@ -54,4 +54,5 @@ export const SpotlightIds = {
// detailPanel // detailPanel
DETAIL_BUYNOW: "detail_buynow", DETAIL_BUYNOW: "detail_buynow",
DETAIL_SHOPBYMOBILE: "detail_shop_by_mobile",
}; };

View File

@@ -6,90 +6,122 @@ import React, {
useMemo, useMemo,
useRef, useRef,
useState, useState,
} from "react"; } from 'react';
import { useDispatch, useSelector } from "react-redux"; import {
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from "@enact/spotlight"; import Spinner from '@enact/sandstone/Spinner';
import { setContainerLastFocusedElement } from "@enact/spotlight/src/container"; import Spotlight from '@enact/spotlight';
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
import { getDeviceAdditionInfo } from "../../actions/deviceActions";
import detailPanelBg
from '../../../assets/images/detailpanel/detailpanel-bg-1.png';
import indicatorDefaultImage
from '../../../assets/images/img-thumb-empty-144@3x.png';
import { getDeviceAdditionInfo } from '../../actions/deviceActions';
import { getThemeCurationDetailInfo } from '../../actions/homeActions';
import { import {
getMainCategoryDetail, getMainCategoryDetail,
getMainYouMayLike, getMainYouMayLike,
} from "../../actions/mainActions"; } from '../../actions/mainActions';
import { popPanel, updatePanel } from "../../actions/panelActions"; import {
import { finishVideoPreview } from "../../actions/playActions"; popPanel,
updatePanel,
} from '../../actions/panelActions';
import { finishVideoPreview } from '../../actions/playActions';
import { import {
clearProductDetail, clearProductDetail,
getProductOptionId, getProductOptionId,
} from "../../actions/productActions"; } from '../../actions/productActions';
import TBody from '../../components/TBody/TBody';
import TBody from "../../components/TBody/TBody"; import TPanel from '../../components/TPanel/TPanel';
import THeaderCustom from "./components/THeaderCustom"; import { panel_names } from '../../utils/Config';
import TPanel from "../../components/TPanel/TPanel"; import fp from '../../utils/fp';
import {
import { panel_names } from "../../utils/Config"; $L,
import { $L, getQRCodeUrl } from "../../utils/helperMethods"; getQRCodeUrl,
import fp from "../../utils/fp"; } from '../../utils/helperMethods';
import css from "./DetailPanel.module.less"; import { SpotlightIds } from '../../utils/SpotlightIds';
import ProductAllSection from "./ProductAllSection/ProductAllSection"; import THeaderCustom from './components/THeaderCustom';
import { getThemeCurationDetailInfo } from "../../actions/homeActions"; import css from './DetailPanel.module.less';
import indicatorDefaultImage from "../../../assets/images/img-thumb-empty-144@3x.png"; import ProductAllSection from './ProductAllSection/ProductAllSection';
import detailPanelBg from "../../../assets/images/detailpanel/detailpanel-bg-1.png"; import ThemeItemListOverlay from './ThemeItemListOverlay/ThemeItemListOverlay';
import ThemeItemListOverlay from "./ThemeItemListOverlay/ThemeItemListOverlay";
import Spinner from "@enact/sandstone/Spinner";
export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const productData = useSelector((state) => state.main.productData); const productData = useSelector((state) => state.main.productData);
const isLoading = useSelector((state) => const isLoading = useSelector((state) =>
fp.pipe(() => state, fp.get('common.appStatus.showLoadingPanel.show'))() fp.pipe(() => state, fp.get("common.appStatus.showLoadingPanel.show"))()
); );
const themeData = useSelector((state) => const themeData = useSelector((state) =>
fp.pipe( fp.pipe(
() => state, () => state,
fp.get('home.productData.themeInfo'), fp.get("home.productData.themeInfo"),
(list) => (list && list[0]) (list) => list && list[0]
)() )()
); );
const webOSVersion = useSelector( const webOSVersion = useSelector(
(state) => state.common.appStatus.webOSVersion, (state) => state.common.appStatus.webOSVersion
); );
const panels = useSelector((state) => state.panels.panels); const panels = useSelector((state) => state.panels.panels);
// FP 방식으로 상태 관리 // FP 방식으로 상태 관리
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
const localRecentItems = useSelector((state) => const localRecentItems = useSelector((state) =>
fp.pipe(() => state, fp.get('localSettings.recentItems'))() fp.pipe(() => state, fp.get("localSettings.recentItems"))()
); );
const { httpHeader } = useSelector((state) => state.common); const { httpHeader } = useSelector((state) => state.common);
const { popupVisible, activePopup } = useSelector( const { popupVisible, activePopup } = useSelector(
(state) => state.common.popup, (state) => state.common.popup
); );
const [lgCatCd, setLgCatCd] = useState(""); const [lgCatCd, setLgCatCd] = useState("");
const [themeProductInfo, setThemeProductInfo] = useState(null); const [themeProductInfo, setThemeProductInfo] = useState(null);
const containerRef = useRef(null); const containerRef = useRef(null);
const panelType = useMemo(
const panelType = useMemo(() => fp.pipe(() => panelInfo, fp.get('type'))(), [panelInfo]); () => fp.pipe(() => panelInfo, fp.get("type"))(),
const panelCurationId = useMemo(() => fp.pipe(() => panelInfo, fp.get('curationId'))(), [panelInfo]); [panelInfo]
const panelPatnrId = useMemo(() => fp.pipe(() => panelInfo, fp.get('patnrId'))(), [panelInfo]); );
const panelPrdtId = useMemo(() => fp.pipe(() => panelInfo, fp.get('prdtId'))(), [panelInfo]); const panelCurationId = useMemo(
const panelLiveReqFlag = useMemo(() => fp.pipe(() => panelInfo, fp.get('liveReqFlag'))(), [panelInfo]); () => fp.pipe(() => panelInfo, fp.get("curationId"))(),
const panelBgImgNo = useMemo(() => fp.pipe(() => panelInfo, fp.get('bgImgNo'))(), [panelInfo]); [panelInfo]
const productPmtSuptYn = useMemo(() => fp.pipe(() => productData, fp.get('pmtSuptYn'))(), [productData]); );
const productGrPrdtProcYn = useMemo(() => fp.pipe(() => productData, fp.get('grPrdtProcYn'))(), [productData]); const panelPatnrId = useMemo(
() => fp.pipe(() => panelInfo, fp.get("patnrId"))(),
[panelInfo]
);
const panelPrdtId = useMemo(
() => fp.pipe(() => panelInfo, fp.get("prdtId"))(),
[panelInfo]
);
const panelLiveReqFlag = useMemo(
() => fp.pipe(() => panelInfo, fp.get("liveReqFlag"))(),
[panelInfo]
);
const panelBgImgNo = useMemo(
() => fp.pipe(() => panelInfo, fp.get("bgImgNo"))(),
[panelInfo]
);
const productPmtSuptYn = useMemo(
() => fp.pipe(() => productData, fp.get("pmtSuptYn"))(),
[productData]
);
const productGrPrdtProcYn = useMemo(
() => fp.pipe(() => productData, fp.get("grPrdtProcYn"))(),
[productData]
);
const productDataSource = useMemo(
const productDataSource = useMemo(() => () =>
fp.pipe( fp.pipe(
() => panelType, () => panelType,
type => type === "theme" ? themeData : productData (type) => (type === "theme" ? themeData : productData)
)(), )(),
[panelType, themeData, productData] [panelType, themeData, productData]
); );
@@ -102,17 +134,16 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
// FP 방식으로 상태 업데이트 함수들 // FP 방식으로 상태 업데이트 함수들
const updateSelectedIndex = useCallback((newIndex) => { const updateSelectedIndex = useCallback((newIndex) => {
setSelectedIndex(fp.pipe( setSelectedIndex(
() => newIndex, fp.pipe(
index => Math.max(0, Math.min(index, 999)) // 범위 제한 () => newIndex,
)()); (index) => Math.max(0, Math.min(index, 999)) // 범위 제한
)()
);
}, []); }, []);
const updateThemeItemOverlay = useCallback((isOpen) => { const updateThemeItemOverlay = useCallback((isOpen) => {
setOpenThemeItemOverlay(fp.pipe( setOpenThemeItemOverlay(fp.pipe(() => isOpen, Boolean)());
() => isOpen,
Boolean
)());
}, []); }, []);
// FP 방식으로 이벤트 핸들러 정의 // FP 방식으로 이벤트 핸들러 정의
@@ -131,27 +162,26 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
}, },
() => { () => {
// 패널 업데이트 조건 체크 // 패널 업데이트 조건 체크
const shouldUpdatePanel = fp.pipe( const shouldUpdatePanel =
() => panels, fp.pipe(
fp.get('length'), () => panels,
length => length === 4 fp.get("length"),
)() && fp.pipe( (length) => length === 4
() => panels, )() &&
fp.get('1.name'), fp.pipe(
name => name === panel_names.PLAYER_PANEL () => panels,
)(); fp.get("1.name"),
(name) => name === panel_names.PLAYER_PANEL
)();
if (shouldUpdatePanel) { if (shouldUpdatePanel) {
dispatch( dispatch(
updatePanel({ updatePanel({
name: panel_names.PLAYER_PANEL, name: panel_names.PLAYER_PANEL,
panelInfo: { panelInfo: {
thumbnail: fp.pipe( thumbnail: fp.pipe(() => panelInfo, fp.get("thumbnailUrl"))(),
() => panelInfo,
fp.get('thumbnailUrl')
)(),
}, },
}), })
); );
} }
} }
@@ -161,7 +191,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
ev.stopPropagation(); ev.stopPropagation();
} }
}, },
[dispatch, panelInfo, panels], [dispatch, panelInfo, panels]
); );
// FP 방식으로 스크롤 함수 핸들러 // FP 방식으로 스크롤 함수 핸들러
@@ -175,56 +205,63 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
() => ({ scrollToSection, sectionId }), () => ({ scrollToSection, sectionId }),
({ scrollToSection, sectionId }) => { ({ scrollToSection, sectionId }) => {
if (fp.isNotNil(scrollToSection)) { if (fp.isNotNil(scrollToSection)) {
return { action: 'execute', scrollFunction: scrollToSection, sectionId }; return {
action: "execute",
scrollFunction: scrollToSection,
sectionId,
};
} else { } else {
return { action: 'store', sectionId }; return { action: "store", sectionId };
} }
} }
)(); )();
// 액션에 따른 처리 // 액션에 따른 처리
if (scrollAction.action === 'execute') { if (scrollAction.action === "execute") {
scrollAction.scrollFunction(scrollAction.sectionId); scrollAction.scrollFunction(scrollAction.sectionId);
} else { } else {
console.log( console.log(
"DetailPanel: scrollToSection function is null, storing pending scroll", "DetailPanel: scrollToSection function is null, storing pending scroll"
); );
setPendingScrollSection(scrollAction.sectionId); setPendingScrollSection(scrollAction.sectionId);
} }
}, },
[scrollToSection], [scrollToSection]
); );
// ===== 고정 배경 이미지 설정 (detailPanelBg만 사용) ===== // ===== 고정 배경 이미지 설정 (detailPanelBg만 사용) =====
// 모든 DetailPanel에서 동일한 배경 이미지(detailpanel-bg-1.png) 사용 // 모든 DetailPanel에서 동일한 배경 이미지(detailpanel-bg-1.png) 사용
useEffect(() => { useEffect(() => {
console.log('[PartnerId] Partner background info:', { console.log("[PartnerId] Partner background info:", {
panelPatnrId: panelPatnrId, panelPatnrId: panelPatnrId,
productDataPatnrId: productData?.patnrId, productDataPatnrId: productData?.patnrId,
productDataPatncNm: productData?.patncNm, productDataPatncNm: productData?.patncNm,
productDataThumbnailUrl960: productData?.thumbnailUrl960, productDataThumbnailUrl960: productData?.thumbnailUrl960,
imageUrl: imageUrl, imageUrl: imageUrl,
detailPanelBg: detailPanelBg detailPanelBg: detailPanelBg,
}); });
// 고정 배경 이미지만 설정 (파트너사별 변경 없이) // 고정 배경 이미지만 설정 (파트너사별 변경 없이)
document.documentElement.style.setProperty('--bg-url', `url(${detailPanelBg})`); document.documentElement.style.setProperty(
"--bg-url",
`url(${detailPanelBg})`
);
}, [panelPatnrId, productData, imageUrl]); }, [panelPatnrId, productData, imageUrl]);
// FP 방식으로 pending scroll 처리 (메모리 누수 방지) // FP 방식으로 pending scroll 처리 (메모리 누수 방지)
useEffect(() => { useEffect(() => {
const shouldExecutePendingScroll = fp.pipe( const shouldExecutePendingScroll = fp.pipe(
() => ({ scrollToSection, pendingScrollSection }), () => ({ scrollToSection, pendingScrollSection }),
({ scrollToSection, pendingScrollSection }) => ({ scrollToSection, pendingScrollSection }) =>
fp.isNotNil(scrollToSection) && fp.isNotNil(pendingScrollSection) fp.isNotNil(scrollToSection) && fp.isNotNil(pendingScrollSection)
)(); )();
if (shouldExecutePendingScroll) { if (shouldExecutePendingScroll) {
console.log( console.log(
"DetailPanel: executing pending scroll to:", "DetailPanel: executing pending scroll to:",
pendingScrollSection, pendingScrollSection
); );
// 메모리 누수 방지를 위한 cleanup 함수 // 메모리 누수 방지를 위한 cleanup 함수
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
if (scrollToSection) { if (scrollToSection) {
@@ -252,14 +289,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
() => { () => {
// 테마 데이터 로딩 // 테마 데이터 로딩
const isThemeType = panelType === "theme"; const isThemeType = panelType === "theme";
if (isThemeType) { if (isThemeType) {
dispatch( dispatch(
getThemeCurationDetailInfo({ getThemeCurationDetailInfo({
patnrId: panelPatnrId, patnrId: panelPatnrId,
curationId: panelCurationId, curationId: panelCurationId,
bgImgNo: panelBgImgNo, bgImgNo: panelBgImgNo,
}), })
); );
} }
}, },
@@ -267,14 +304,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
// 일반 상품 데이터 로딩 // 일반 상품 데이터 로딩
const hasProductId = fp.isNotNil(panelPrdtId); const hasProductId = fp.isNotNil(panelPrdtId);
const hasNoCuration = fp.isNil(panelCurationId); const hasNoCuration = fp.isNil(panelCurationId);
if (hasProductId && hasNoCuration) { if (hasProductId && hasNoCuration) {
dispatch( dispatch(
getMainCategoryDetail({ getMainCategoryDetail({
patnrId: panelPatnrId, patnrId: panelPatnrId,
prdtId: panelPrdtId, prdtId: panelPrdtId,
liveReqFlag: panelLiveReqFlag || "N", liveReqFlag: panelLiveReqFlag || "N",
}), })
); );
} }
} }
@@ -296,10 +333,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
// FP 방식으로 추천 상품 데이터 로딩 (메모리 누수 방지) // FP 방식으로 추천 상품 데이터 로딩 (메모리 누수 방지)
useEffect(() => { useEffect(() => {
const shouldLoadRecommendations = fp.pipe( const shouldLoadRecommendations = fp.pipe(() => lgCatCd, fp.isNotEmpty)();
() => lgCatCd,
fp.isNotEmpty
)();
if (shouldLoadRecommendations) { if (shouldLoadRecommendations) {
dispatch( dispatch(
@@ -308,62 +342,82 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
exclCurationId: panelCurationId, exclCurationId: panelCurationId,
exclPatnrId: panelPatnrId, exclPatnrId: panelPatnrId,
exclPrdtId: panelPrdtId, exclPrdtId: panelPrdtId,
}), })
); );
} }
}, [panelCurationId, panelPatnrId, panelPrdtId, lgCatCd]); }, [panelCurationId, panelPatnrId, panelPrdtId, lgCatCd]);
// FP 방식으로 카테고리 규칙 헬퍼 함수들 (curry 적용) // FP 방식으로 카테고리 규칙 헬퍼 함수들 (curry 적용)
const categoryHelpers = useMemo(() => ({ const categoryHelpers = useMemo(
createCategoryRule: fp.curry((conditionFn, extractFn, data) => () => ({
conditionFn(data) ? extractFn(data) : null createCategoryRule: fp.curry((conditionFn, extractFn, data) =>
), conditionFn(data) ? extractFn(data) : null
hasProductWithoutCuration: fp.curry((panelCurationId, productData) => ),
fp.isNotNil(productData) && fp.isNil(panelCurationId) hasProductWithoutCuration: fp.curry(
), (panelCurationId, productData) =>
hasThemeWithPaymentCondition: fp.curry((panelCurationId, themeProductInfo) => { fp.isNotNil(productData) && fp.isNil(panelCurationId)
const hasThemeProduct = fp.isNotNil(themeProductInfo); ),
const equalToN = fp.curry((expected, actual) => actual === expected)("N"); hasThemeWithPaymentCondition: fp.curry(
const isNoPayment = equalToN(fp.pipe(() => themeProductInfo, fp.get('pmtSuptYn'))()); (panelCurationId, themeProductInfo) => {
const hasCuration = fp.isNotNil(panelCurationId); const hasThemeProduct = fp.isNotNil(themeProductInfo);
return hasThemeProduct && isNoPayment && hasCuration; const equalToN = fp.curry((expected, actual) => actual === expected)(
}) "N"
}), []); );
const isNoPayment = equalToN(
fp.pipe(() => themeProductInfo, fp.get("pmtSuptYn"))()
);
const hasCuration = fp.isNotNil(panelCurationId);
return hasThemeProduct && isNoPayment && hasCuration;
}
),
}),
[]
);
const getlgCatCd = useCallback(() => { const getlgCatCd = useCallback(() => {
// FP 방식으로 카테고리 코드 결정 - curry 적용으로 더 함수형 개선 // FP 방식으로 카테고리 코드 결정 - curry 적용으로 더 함수형 개선
const categoryRules = [ const categoryRules = [
// 일반 상품 규칙 (curry 활용) // 일반 상품 규칙 (curry 활용)
() => categoryHelpers.createCategoryRule( () =>
categoryHelpers.hasProductWithoutCuration(panelCurationId), categoryHelpers.createCategoryRule(
(data) => fp.pipe(() => data, fp.get('catCd'))(), categoryHelpers.hasProductWithoutCuration(panelCurationId),
productData (data) => fp.pipe(() => data, fp.get("catCd"))(),
), productData
),
// 테마 상품 규칙 (curry 활용) // 테마 상품 규칙 (curry 활용)
() => categoryHelpers.createCategoryRule( () =>
categoryHelpers.hasThemeWithPaymentCondition(panelCurationId), categoryHelpers.createCategoryRule(
(data) => fp.pipe(() => data, fp.get('catCd'))(), categoryHelpers.hasThemeWithPaymentCondition(panelCurationId),
themeProductInfo (data) => fp.pipe(() => data, fp.get("catCd"))(),
) themeProductInfo
),
]; ];
// 첫 번째로 매칭되는 규칙의 결과 사용 (curry의 reduce 활용) // 첫 번째로 매칭되는 규칙의 결과 사용 (curry의 reduce 활용)
const categoryCode = fp.reduce( const categoryCode = fp.reduce(
(result, value) => result || value || "", (result, value) => result || value || "",
"", "",
categoryRules.map(rule => rule()) categoryRules.map((rule) => rule())
); );
setLgCatCd(categoryCode); setLgCatCd(categoryCode);
}, [productData, selectedIndex, panelCurationId, themeProductInfo, categoryHelpers]); }, [
productData,
selectedIndex,
panelCurationId,
themeProductInfo,
categoryHelpers,
]);
// FP 방식으로 카테고리 코드 업데이트 (메모리 누수 방지) // FP 방식으로 카테고리 코드 업데이트 (메모리 누수 방지)
useEffect(() => { useEffect(() => {
const shouldUpdateCategory = fp.pipe( const shouldUpdateCategory = fp.pipe(
() => ({ themeProductInfo, productData, panelInfo, selectedIndex }), () => ({ themeProductInfo, productData, panelInfo, selectedIndex }),
({ themeProductInfo, productData, panelInfo, selectedIndex }) => ({ themeProductInfo, productData, panelInfo, selectedIndex }) =>
fp.isNotNil(themeProductInfo) || fp.isNotNil(productData) || fp.isNotNil(panelInfo) fp.isNotNil(themeProductInfo) ||
fp.isNotNil(productData) ||
fp.isNotNil(panelInfo)
)(); )();
if (shouldUpdateCategory) { if (shouldUpdateCategory) {
@@ -400,98 +454,147 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
// 테마/호텔 기반 인덱스 초기화가 필요하면: // 테마/호텔 기반 인덱스 초기화가 필요하면:
// - findIndex 유틸을 만들어 매칭 인덱스를 계산 후 setSelectedIndex에 반영하세요. // - findIndex 유틸을 만들어 매칭 인덱스를 계산 후 setSelectedIndex에 반영하세요.
// FP 방식으로 버전 비교 헬퍼 함수 (curry 적용) // FP 방식으로 버전 비교 헬퍼 함수 (curry 적용)
const versionComparators = useMemo(() => ({ const versionComparators = useMemo(
isVersionGTE: fp.curry((target, version) => version >= target), () => ({
isVersionLT: fp.curry((target, version) => version < target) isVersionGTE: fp.curry((target, version) => version >= target),
}), []); isVersionLT: fp.curry((target, version) => version < target),
}),
[]
);
// FP 방식으로 조건 체크 헬퍼 함수들 (curry 적용) // FP 방식으로 조건 체크 헬퍼 함수들 (curry 적용)
const conditionCheckers = useMemo(() => ({ const conditionCheckers = useMemo(
hasDataAndCondition: fp.curry((conditionFn, data) => fp.isNotNil(data) && conditionFn(data)), () => ({
equalTo: fp.curry((expected, actual) => actual === expected), hasDataAndCondition: fp.curry(
checkAllConditions: fp.curry((conditions, data) => (conditionFn, data) => fp.isNotNil(data) && conditionFn(data)
fp.reduce((acc, condition) => acc && condition, true, conditions.map(fn => fn(data))) ),
) equalTo: fp.curry((expected, actual) => actual === expected),
}), []); checkAllConditions: fp.curry((conditions, data) =>
fp.reduce(
(acc, condition) => acc && condition,
true,
conditions.map((fn) => fn(data))
)
),
}),
[]
);
const getProductType = useCallback(() => { const getProductType = useCallback(() => {
// FP 방식으로 데이터 검증 및 타입 결정 - curry 적용으로 더 함수형 개선 // FP 방식으로 데이터 검증 및 타입 결정 - curry 적용으로 더 함수형 개선
const createTypeChecker = fp.curry((type, conditions, sideEffect) => const createTypeChecker = fp.curry((type, conditions, sideEffect) =>
fp.pipe( fp.pipe(
() => conditions(), () => conditions(),
isValid => isValid ? (() => { (isValid) =>
sideEffect && sideEffect(); isValid
return { matched: true, type }; ? (() => {
})() : { matched: false } sideEffect && sideEffect();
return { matched: true, type };
})()
: { matched: false }
)() )()
); );
const productTypeRules = [ const productTypeRules = [
// 테마 타입 체크 // 테마 타입 체크
() => createTypeChecker( () =>
"theme", createTypeChecker(
() => fp.pipe( "theme",
() => ({ panelCurationId, themeData }), () =>
({ panelCurationId, themeData }) => fp.pipe(
fp.isNotNil(panelCurationId) && fp.isNotNil(themeData) () => ({ panelCurationId, themeData }),
)(), ({ panelCurationId, themeData }) =>
() => { fp.isNotNil(panelCurationId) && fp.isNotNil(themeData)
const themeProduct = fp.pipe( )(),
() => themeData, () => {
fp.get('productInfos'), const themeProduct = fp.pipe(
fp.get(selectedIndex.toString()) () => themeData,
)(); fp.get("productInfos"),
setProductType("theme"); fp.get(selectedIndex.toString())
setThemeProductInfo(themeProduct); )();
} setProductType("theme");
), setThemeProductInfo(themeProduct);
}
),
// Buy Now 타입 체크 (curry 활용) // Buy Now 타입 체크 (curry 활용)
() => createTypeChecker( () =>
"buyNow", createTypeChecker(
() => fp.pipe( "buyNow",
() => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }), () =>
({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => { fp.pipe(
const conditions = [ () => ({
() => fp.isNotNil(productData), productData,
() => conditionCheckers.equalTo("Y")(productPmtSuptYn), panelPrdtId,
() => conditionCheckers.equalTo("N")(productGrPrdtProcYn), productPmtSuptYn,
() => fp.isNotNil(panelPrdtId), productGrPrdtProcYn,
() => versionComparators.isVersionGTE("6.0")(webOSVersion) webOSVersion,
]; }),
return conditionCheckers.checkAllConditions(conditions)({}); ({
} productData,
)(), panelPrdtId,
() => setProductType("buyNow") productPmtSuptYn,
), productGrPrdtProcYn,
webOSVersion,
}) => {
const conditions = [
() => fp.isNotNil(productData),
() => conditionCheckers.equalTo("Y")(productPmtSuptYn),
() => conditionCheckers.equalTo("N")(productGrPrdtProcYn),
() => fp.isNotNil(panelPrdtId),
() => versionComparators.isVersionGTE("6.0")(webOSVersion),
];
return conditionCheckers.checkAllConditions(conditions)({});
}
)(),
() => setProductType("buyNow")
),
// Shop By Mobile 타입 체크 (curry 활용) // Shop By Mobile 타입 체크 (curry 활용)
() => createTypeChecker( () =>
"shopByMobile", createTypeChecker(
() => fp.pipe( "shopByMobile",
() => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }), () =>
({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => { fp.pipe(
if (!productData) return false; () => ({
productData,
const isDirectMobile = conditionCheckers.equalTo("N")(productPmtSuptYn); panelPrdtId,
const conditionalMobileConditions = [ productPmtSuptYn,
() => conditionCheckers.equalTo("Y")(productPmtSuptYn), productGrPrdtProcYn,
() => conditionCheckers.equalTo("N")(productGrPrdtProcYn), webOSVersion,
() => versionComparators.isVersionLT("6.0")(webOSVersion), }),
() => fp.isNotNil(panelPrdtId) ({
]; productData,
const isConditionalMobile = conditionCheckers.checkAllConditions(conditionalMobileConditions)({}); panelPrdtId,
productPmtSuptYn,
return isDirectMobile || isConditionalMobile; productGrPrdtProcYn,
} webOSVersion,
)(), }) => {
() => setProductType("shopByMobile") if (!productData) return false;
)
const isDirectMobile =
conditionCheckers.equalTo("N")(productPmtSuptYn);
const conditionalMobileConditions = [
() => conditionCheckers.equalTo("Y")(productPmtSuptYn),
() => conditionCheckers.equalTo("N")(productGrPrdtProcYn),
() => versionComparators.isVersionLT("6.0")(webOSVersion),
() => fp.isNotNil(panelPrdtId),
];
const isConditionalMobile =
conditionCheckers.checkAllConditions(
conditionalMobileConditions
)({});
return isDirectMobile || isConditionalMobile;
}
)(),
() => setProductType("shopByMobile")
),
]; ];
// FP 방식으로 순차적 타입 체크 // FP 방식으로 순차적 타입 체크
const matchedRule = fp.reduce( const matchedRule = fp.reduce(
(result, rule) => result.matched ? result : rule(), (result, rule) => (result.matched ? result : rule()),
{ matched: false }, { matched: false },
productTypeRules productTypeRules
); );
@@ -499,15 +602,27 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
// 매칭되지 않은 경우 디버깅 정보 출력 // 매칭되지 않은 경우 디버깅 정보 출력
if (!matchedRule.matched) { if (!matchedRule.matched) {
const debugInfo = fp.pipe( const debugInfo = fp.pipe(
() => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }), () => ({
({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => ({ productData,
panelPrdtId,
productPmtSuptYn,
productGrPrdtProcYn,
webOSVersion,
}),
({
productData,
panelPrdtId,
productPmtSuptYn,
productGrPrdtProcYn,
webOSVersion,
}) => ({
pmtSuptYn: productPmtSuptYn, pmtSuptYn: productPmtSuptYn,
grPrdtProcYn: productGrPrdtProcYn, grPrdtProcYn: productGrPrdtProcYn,
prdtId: panelPrdtId, prdtId: panelPrdtId,
webOSVersion, webOSVersion,
}) })
)(); )();
console.warn("Unknown product type:", productData); console.warn("Unknown product type:", productData);
console.warn("Product data properties:", debugInfo); console.warn("Product data properties:", debugInfo);
} }
@@ -530,45 +645,59 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
} }
}, [getProductType, productData, themeData, panelType]); }, [getProductType, productData, themeData, panelType]);
const imageUrl = useMemo(() => fp.pipe(() => productData, fp.get('thumbnailUrl960'))(), [productData]); const imageUrl = useMemo(
() => fp.pipe(() => productData, fp.get("thumbnailUrl960"))(),
[productData]
);
// FP 방식으로 타이틀과 aria-label 메모이제이션 (성능 최적화) // FP 방식으로 타이틀과 aria-label 메모이제이션 (성능 최적화)
const headerTitle = useMemo(() => fp.pipe( const headerTitle = useMemo(
() => ({ panelPrdtId, productData, panelType, themeData }), () =>
({ panelPrdtId, productData, panelType, themeData }) => { fp.pipe(
const productTitle = fp.pipe( () => ({ panelPrdtId, productData, panelType, themeData }),
() => ({ panelPrdtId, productData }), ({ panelPrdtId, productData, panelType, themeData }) => {
({ panelPrdtId, productData }) => const productTitle = fp.pipe(
fp.isNotNil(panelPrdtId) && fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)() () => ({ panelPrdtId, productData }),
? fp.pipe(() => productData, fp.get('prdtNm'))() ({ panelPrdtId, productData }) =>
: null fp.isNotNil(panelPrdtId) &&
)(); fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)()
? fp.pipe(() => productData, fp.get("prdtNm"))()
const themeTitle = fp.pipe( : null
() => ({ panelType, themeData }), )();
({ panelType, themeData }) =>
panelType === "theme" && fp.pipe(() => themeData, fp.get('curationNm'), fp.isNotNil)()
? fp.pipe(() => themeData, fp.get('curationNm'))()
: null
)();
return productTitle || themeTitle || "";
}
)(), [panelPrdtId, productData, panelType, themeData]);
const ariaLabel = useMemo(() => fp.pipe( const themeTitle = fp.pipe(
() => ({ panelPrdtId, productData }), () => ({ panelType, themeData }),
({ panelPrdtId, productData }) => ({ panelType, themeData }) =>
fp.isNotNil(panelPrdtId) && fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)() panelType === "theme" &&
? fp.pipe(() => productData, fp.get('prdtNm'))() fp.pipe(() => themeData, fp.get("curationNm"), fp.isNotNil)()
: "" ? fp.pipe(() => themeData, fp.get("curationNm"))()
)(), [panelPrdtId, productData]); : null
)();
return productTitle || themeTitle || "";
}
)(),
[panelPrdtId, productData, panelType, themeData]
);
const ariaLabel = useMemo(
() =>
fp.pipe(
() => ({ panelPrdtId, productData }),
({ panelPrdtId, productData }) =>
fp.isNotNil(panelPrdtId) &&
fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)()
? fp.pipe(() => productData, fp.get("prdtNm"))()
: ""
)(),
[panelPrdtId, productData]
);
// ===== 파트너사별 배경 이미지 설정 로직 (현재 비활성화) ===== // ===== 파트너사별 배경 이미지 설정 로직 (현재 비활성화) =====
// thumbnailUrl960을 사용하여 파트너사별로 다른 배경 이미지를 설정하는 기능 // thumbnailUrl960을 사용하여 파트너사별로 다른 배경 이미지를 설정하는 기능
// Pink Pong 등 특정 파트너사에서만 thumbnailUrl960 데이터가 있어서 배경이 변경됨 // Pink Pong 등 특정 파트너사에서만 thumbnailUrl960 데이터가 있어서 배경이 변경됨
// 현재는 고정 배경(detailPanelBg)만 사용하기 위해 주석 처리 // 현재는 고정 배경(detailPanelBg)만 사용하기 위해 주석 처리
// FP 방식으로 배경 이미지 설정 (메모리 누수 방지) // FP 방식으로 배경 이미지 설정 (메모리 누수 방지)
/* /*
useLayoutEffect(() => { useLayoutEffect(() => {
@@ -589,6 +718,15 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
// 언마운트 시 인덱스 초기화가 필요하면: // 언마운트 시 인덱스 초기화가 필요하면:
// useEffect(() => () => setSelectedIndex(0), []) // useEffect(() => () => setSelectedIndex(0), [])
const handleProductAllSectionReady = useCallback(() => {
const spotTime = setTimeout(() => {
Spotlight.focus(SpotlightIds.DETAIL_SHOPBYMOBILE);
}, 100);
return () => {
clearTimeout(spotTime);
};
}, []);
return ( return (
<div ref={containerRef}> <div ref={containerRef}>
<TPanel <TPanel
@@ -621,13 +759,18 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
({ isLoading, panelInfo, productDataSource, productType }) => { ({ isLoading, panelInfo, productDataSource, productType }) => {
const hasRequiredData = fp.pipe( const hasRequiredData = fp.pipe(
() => [panelInfo, productDataSource, productType], () => [panelInfo, productDataSource, productType],
data => fp.reduce((acc, item) => acc && fp.isNotNil(item), true, data) (data) =>
fp.reduce(
(acc, item) => acc && fp.isNotNil(item),
true,
data
)
)(); )();
return { return {
canRender: !isLoading && hasRequiredData, canRender: !isLoading && hasRequiredData,
showLoading: !isLoading && !hasRequiredData, showLoading: !isLoading && !hasRequiredData,
showNothing: isLoading showNothing: isLoading,
}; };
} }
)(); )();
@@ -645,10 +788,12 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
openThemeItemOverlay={openThemeItemOverlay} openThemeItemOverlay={openThemeItemOverlay}
setOpenThemeItemOverlay={updateThemeItemOverlay} setOpenThemeItemOverlay={updateThemeItemOverlay}
themeProductInfo={themeProductInfo} themeProductInfo={themeProductInfo}
onReady={handleProductAllSectionReady}
isOnRender={renderStates.canRender}
/> />
); );
} }
if (renderStates.showLoading) { if (renderStates.showLoading) {
return ( return (
<div className={css.loadingContainer}> <div className={css.loadingContainer}>
@@ -656,10 +801,23 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
</div> </div>
); );
} }
return null; return null;
}, [isLoading, panelInfo, productDataSource, productType, selectedIndex, panelPatnrId, panelPrdtId, updateSelectedIndex, openThemeItemOverlay, updateThemeItemOverlay, themeProductInfo])} }, [
isLoading,
panelInfo,
productDataSource,
productType,
selectedIndex,
panelPatnrId,
panelPrdtId,
updateSelectedIndex,
openThemeItemOverlay,
updateThemeItemOverlay,
themeProductInfo,
])}
</TBody> </TBody>
<ThemeItemListOverlay <ThemeItemListOverlay
productInfo={productDataSource} productInfo={productDataSource}
isOpen={openThemeItemOverlay} isOpen={openThemeItemOverlay}

View File

@@ -5,13 +5,29 @@
background-size: cover; background-size: cover;
background-position: center; background-position: center;
background-image: background-image:
// linear-gradient(0deg, #222222, #222222),
// linear-gradient(180deg, rgba(34, 34, 34, 0) 89.93%, #222222 103.61%),
// linear-gradient(0deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)),
// linear-gradient(
// 270deg,
// rgba(0, 0, 0, 0) 43.07%,
// rgba(0, 0, 0, 0.539) 73.73%,
// rgba(0, 0, 0, 0.7) 100%
// ),
linear-gradient(0deg, rgba(0, 0, 0, 0.4)),
linear-gradient(
180deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.77) 70%,
rgba(0, 0, 0, 1) 100%
),
linear-gradient( linear-gradient(
270deg, 270deg,
rgba(0, 0, 0, 0) 43.07%, rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.539) 73.73%, rgba(0, 0, 0, 0.77) 70%,
rgba(0, 0, 0, 0.7) 100% rgba(0, 0, 0, 1) 100%
), ),
linear-gradient(0deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), //1차 변경본 너무 어두움 확인필요
var(--bg-url); var(--bg-url);
} }

View File

@@ -1,41 +1,72 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import { throttle } from 'lodash';
import { PropTypes } from 'prop-types';
import {
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
/* eslint-disable react/jsx-no-bind */ /* eslint-disable react/jsx-no-bind */
// src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx // src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; import SpotlightContainerDecorator
import Spottable from "@enact/spotlight/Spottable"; from '@enact/spotlight/SpotlightContainerDecorator';
import React, { useCallback, useRef, useState, useMemo, useEffect } from "react"; import Spottable from '@enact/spotlight/Spottable';
import { useSelector, useDispatch } from "react-redux";
import Spotlight from "@enact/spotlight";
import { PropTypes } from "prop-types";
//image
import arrowDown
from '../../../../assets/images/icons/ic_arrow_down_3x_new.png';
import { pushPanel } from '../../../actions/panelActions';
import { resetShowAllReviews } from '../../../actions/productActions';
// ProductInfoSection imports // ProductInfoSection imports
import TButton from "../../../components/TButton/TButton"; import TButton from '../../../components/TButton/TButton';
import { $L } from "../../../utils/helperMethods"; import useReviews from '../../../hooks/useReviews/useReviews';
import useScrollTo from '../../../hooks/useScrollTo';
import { panel_names } from '../../../utils/Config';
import { import {
curry, pipe, when, isVal, isNotNil, defaultTo, defaultWith, get, identity, isEmpty, isNil, andThen, tap andThen,
} from "../../../utils/fp"; curry,
import { resetShowAllReviews } from "../../../actions/productActions"; defaultTo,
import useReviews from "../../../hooks/useReviews/useReviews"; defaultWith,
import { pushPanel } from "../../../actions/panelActions"; get,
import { panel_names } from "../../../utils/Config"; identity,
import ViewAllReviewsButton from "../ProductContentSection/UserReviews/ViewAllReviewsButton"; isEmpty,
import FavoriteBtn from "../components/FavoriteBtn"; isNil,
import StarRating from "../components/StarRating"; isNotNil,
import ProductTag from "../components/ProductTag"; isVal,
import DetailMobileSendPopUp from "../components/DetailMobileSendPopUp"; pipe,
import { SpotlightIds } from "../../../utils/SpotlightIds"; tap,
import QRCode from "../ProductInfoSection/QRCode/QRCode"; when,
import ProductOverview from "../ProductOverview/ProductOverview"; } from '../../../utils/fp';
import { $L } from '../../../utils/helperMethods';
import { SpotlightIds } from '../../../utils/SpotlightIds';
import ShowUserReviews from '../../UserReview/ShowUserReviews';
import CustomScrollbar from '../components/CustomScrollbar/CustomScrollbar';
import DetailMobileSendPopUp from '../components/DetailMobileSendPopUp';
import FavoriteBtn from '../components/FavoriteBtn';
import ProductTag from '../components/ProductTag';
import StarRating from '../components/StarRating';
// ProductContentSection imports // ProductContentSection imports
import TScrollerDetail from "../components/TScroller/TScrollerDetail"; import TScrollerDetail from '../components/TScroller/TScrollerDetail';
import CustomScrollbar from "../components/CustomScrollbar/CustomScrollbar"; import ProductDescription
import useScrollTo from "../../../hooks/useScrollTo"; from '../ProductContentSection/ProductDescription/ProductDescription';
import ProductDetail from "../ProductContentSection/ProductDetail/ProductDetail.new"; import ProductDetail
import UserReviews from "../ProductContentSection/UserReviews/UserReviews"; from '../ProductContentSection/ProductDetail/ProductDetail.new';
import YouMayAlsoLike from "../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike"; import UserReviews from '../ProductContentSection/UserReviews/UserReviews';
import ProductDescription from "../ProductContentSection/ProductDescription/ProductDescription"; import ViewAllReviewsButton
import ShowUserReviews from "../../UserReview/ShowUserReviews"; from '../ProductContentSection/UserReviews/ViewAllReviewsButton';
import YouMayAlsoLike
from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
import QRCode from '../ProductInfoSection/QRCode/QRCode';
import ProductOverview from '../ProductOverview/ProductOverview';
// CSS imports // CSS imports
// import infoCSS from "../ProductInfoSection/ProductInfoSection.module.less"; // import infoCSS from "../ProductInfoSection/ProductInfoSection.module.less";
// import contentCSS from "../ProductContentSection/ProductContentSection.module.less"; // import contentCSS from "../ProductContentSection/ProductContentSection.module.less";
@@ -46,9 +77,9 @@ const Container = SpotlightContainerDecorator(
enterTo: "last-focused", enterTo: "last-focused",
preserveld: true, preserveld: true,
leaveFor: { right: "content-scroller-container" }, leaveFor: { right: "content-scroller-container" },
spotlightDirection: "vertical" spotlightDirection: "vertical",
}, },
"div", "div"
); );
const ContentContainer = SpotlightContainerDecorator( const ContentContainer = SpotlightContainerDecorator(
@@ -56,12 +87,12 @@ const ContentContainer = SpotlightContainerDecorator(
enterTo: "default-element", enterTo: "default-element",
preserveld: true, preserveld: true,
leaveFor: { leaveFor: {
left: "spotlight-product-info-section-container" left: "spotlight-product-info-section-container",
}, },
restrict: "none", restrict: "none",
spotlightDirection: "vertical" spotlightDirection: "vertical",
}, },
"div", "div"
); );
const HorizontalContainer = SpotlightContainerDecorator( const HorizontalContainer = SpotlightContainerDecorator(
@@ -69,17 +100,19 @@ const HorizontalContainer = SpotlightContainerDecorator(
enterTo: "last-focused", enterTo: "last-focused",
preserveld: true, preserveld: true,
defaultElement: "spotlight-product-info-section-container", defaultElement: "spotlight-product-info-section-container",
spotlightDirection: "horizontal" spotlightDirection: "horizontal",
}, },
"div", "div"
); );
// FP: Pure function to determine product data based on type // FP: Pure function to determine product data based on type
const getProductData = curry((productType, themeProductInfo, productInfo) => const getProductData = curry((productType, themeProductInfo, productInfo) =>
pipe( pipe(
when( when(
() => isVal(productType) && productType === "theme" && isVal(themeProductInfo), () =>
isVal(productType) &&
productType === "theme" &&
isVal(themeProductInfo),
() => themeProductInfo () => themeProductInfo
), ),
defaultTo(productInfo), defaultTo(productInfo),
@@ -91,19 +124,14 @@ const getProductData = curry((productType, themeProductInfo, productInfo) =>
const deriveFavoriteFlag = curry((favoriteOverride, productData) => const deriveFavoriteFlag = curry((favoriteOverride, productData) =>
pipe( pipe(
when(isNotNil, identity), when(isNotNil, identity),
defaultWith(() => defaultWith(() => pipe(get("favorYn"), defaultTo("N"))(productData))
pipe(
get("favorYn"),
defaultTo("N")
)(productData)
)
)(favoriteOverride) )(favoriteOverride)
); );
// FP: Pure function to extract review grade and order phone // FP: Pure function to extract review grade and order phone
const extractProductMeta = (productInfo) => ({ const extractProductMeta = (productInfo) => ({
revwGrd: get("revwGrd", productInfo), revwGrd: get("revwGrd", productInfo),
orderPhnNo: get("orderPhnNo", productInfo) orderPhnNo: get("orderPhnNo", productInfo),
}); });
// 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경 // 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경
@@ -130,11 +158,22 @@ export default function ProductAllSection({
openThemeItemOverlay, openThemeItemOverlay,
setOpenThemeItemOverlay, setOpenThemeItemOverlay,
themeProductInfo, themeProductInfo,
onReady,
isOnRender,
}) { }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [currentHeight, setCurrentHeight] = useState(0);
//하단부분까지 갔을때 체크용
const [documentHeight, setDocumentHeight] = useState();
const [isBottom, setIsBottom] = useState(false);
//버튼 active 표시용
const [reviewHeight, setReviewHeight] = useState(0);
const [activeProductBtn, setActiveProductBtn] = useState(false);
const [activeReviewBtn, setActiveReviewBtn] = useState(false);
const [activeYouMayLikeBtn, setActiveYouMayLikeBtn] = useState(false);
const productData = useMemo(() => const productData = useMemo(
getProductData(productType, themeProductInfo, productInfo), () => getProductData(productType, themeProductInfo, productInfo),
[productType, themeProductInfo, productInfo] [productType, themeProductInfo, productInfo]
); );
@@ -143,16 +182,21 @@ export default function ProductAllSection({
previewReviews, previewReviews,
stats, stats,
isLoading: reviewsLoading, isLoading: reviewsLoading,
hasReviews // 리뷰 존재 여부 플래그 추가 hasReviews, // 리뷰 존재 여부 플래그 추가
} = useReviews(productData.prdtId); } = useReviews(productData.prdtId);
// YouMayAlsoLike 데이터 확인 // YouMayAlsoLike 데이터 확인
const youmaylikeProductData = useSelector((state) => state.main.youmaylikeData); const youmaylikeProductData = useSelector(
const hasYouMayAlsoLike = youmaylikeProductData && youmaylikeProductData.length > 0; (state) => state.main.youmaylikeData
);
const hasYouMayAlsoLike =
youmaylikeProductData && youmaylikeProductData.length > 0;
// ProductAllSection 마운트 시 showAllReviews 초기화 // ProductAllSection 마운트 시 showAllReviews 초기화
useEffect(() => { useEffect(() => {
console.log("[ProductAllSection] Component mounted - resetting showAllReviews to false"); console.log(
"[ProductAllSection] Component mounted - resetting showAllReviews to false"
);
dispatch(resetShowAllReviews()); dispatch(resetShowAllReviews());
}, []); // 빈 dependency array = 마운트 시에만 실행 }, []); // 빈 dependency array = 마운트 시에만 실행
@@ -163,18 +207,22 @@ export default function ProductAllSection({
hasProductData: !!productData, hasProductData: !!productData,
reviewTotalCount: stats.totalReviews, reviewTotalCount: stats.totalReviews,
averageRating: stats.averageRating, averageRating: stats.averageRating,
productData: productData productData: productData,
}); });
dispatch( dispatch(
pushPanel({ pushPanel({
name: panel_names.USER_REVIEW_PANEL, name: panel_names.USER_REVIEW_PANEL,
panelInfo: { panelInfo: {
prdtId: productData.prdtId, prdtId: productData.prdtId,
productImage: (productData.imgUrls600 && productData.imgUrls600[0]) || (productData.imgUrls && productData.imgUrls[0]) || productData.thumbnailUrl || 'https://placehold.co/150x150', productImage:
brandLogo: productData.patncLogoPath || 'https://placehold.co/50x50', (productData.imgUrls600 && productData.imgUrls600[0]) ||
productName: productData.prdtNm || '상품명 정보가 없습니다', (productData.imgUrls && productData.imgUrls[0]) ||
productData.thumbnailUrl ||
"https://placehold.co/150x150",
brandLogo: productData.patncLogoPath || "https://placehold.co/50x50",
productName: productData.prdtNm || "상품명 정보가 없습니다",
avgRating: stats.averageRating || 5, avgRating: stats.averageRating || 5,
reviewCount: stats.totalReviews || 0 reviewCount: stats.totalReviews || 0,
}, },
}) })
); );
@@ -186,30 +234,38 @@ export default function ProductAllSection({
hasProductData: !!productData, hasProductData: !!productData,
productDataPrdtId: productData && productData.prdtId, productDataPrdtId: productData && productData.prdtId,
imgUrls600: productData && productData.imgUrls600, imgUrls600: productData && productData.imgUrls600,
imgUrls600Length: productData && productData.imgUrls600 && productData.imgUrls600.length, imgUrls600Length:
imgUrls600Type: Array.isArray(productData && productData.imgUrls600) ? 'array' : typeof (productData && productData.imgUrls600), productData && productData.imgUrls600 && productData.imgUrls600.length,
productData: productData imgUrls600Type: Array.isArray(productData && productData.imgUrls600)
? "array"
: typeof (productData && productData.imgUrls600),
productData: productData,
}); });
}, [productData]); }, [productData]);
const { revwGrd, orderPhnNo } = useMemo(() => const { revwGrd, orderPhnNo } = useMemo(
extractProductMeta(productInfo), () => extractProductMeta(productInfo),
[productInfo] [productInfo]
); );
// FP: derive favorite flag from props with local override, avoid non-I/O useEffect // FP: derive favorite flag from props with local override, avoid non-I/O useEffect
const [favoriteOverride, setFavoriteOverride] = useState(null); const [favoriteOverride, setFavoriteOverride] = useState(null);
const favoriteFlag = useMemo(() => const favoriteFlag = useMemo(
deriveFavoriteFlag(favoriteOverride, productData), () => deriveFavoriteFlag(favoriteOverride, productData),
[favoriteOverride, productData] [favoriteOverride, productData]
); );
const [mobileSendPopupOpen, setMobileSendPopupOpen] = useState(false); const [mobileSendPopupOpen, setMobileSendPopupOpen] = useState(false);
// useReviews에서 모든 리뷰 데이터 관리 // useReviews에서 모든 리뷰 데이터 관리
const reviewTotalCount = stats.totalReviews; const reviewTotalCount = stats.totalReviews;
const reviewData = { reviewList: previewReviews, reviewDetail: { totRvwCnt: stats.totalReviews, avgRvwScr: stats.averageRating } }; const reviewData = {
reviewList: previewReviews,
reviewDetail: {
totRvwCnt: stats.totalReviews,
avgRvwScr: stats.averageRating,
},
};
// User Reviews 스크롤 핸들러 추가 // User Reviews 스크롤 핸들러 추가
const handleUserReviewsClick = useCallback( const handleUserReviewsClick = useCallback(
@@ -218,15 +274,16 @@ export default function ProductAllSection({
); );
const scrollContainerRef = useRef(null); const scrollContainerRef = useRef(null);
const { getScrollTo, scrollTop } = useScrollTo(); const productDetailRef = useRef(null); //높이값 변경때문
const descriptionRef = useRef(null);
const reviewRef = useRef(null);
const youMayAlsoLikelRef = useRef(null);
const { getScrollTo, scrollTop } = useScrollTo();
// FP: Pure function for mobile popup state change // FP: Pure function for mobile popup state change
const handleShopByMobileOpen = useCallback( const handleShopByMobileOpen = useCallback(
pipe( pipe(() => true, setMobileSendPopupOpen),
() => true,
setMobileSendPopupOpen
),
[] []
); );
@@ -275,7 +332,86 @@ export default function ProductAllSection({
() => scrollToSection("scroll-marker-you-may-also-like"), () => scrollToSection("scroll-marker-you-may-also-like"),
[scrollToSection] [scrollToSection]
); );
const scrollPositionRef = useRef(0);
const handleArrowClickAlternative = useCallback(() => {
const currentHeight = scrollPositionRef.current;
const scrollAmount = 200;
scrollTop({
y: currentHeight + scrollAmount,
animate: true,
});
if (documentHeight) {
const isAtBottom = scrollPositionRef.current + 944 >= documentHeight;
if (isAtBottom !== isBottom) {
setIsBottom(isAtBottom);
}
}
}, []);
const handleScroll = useCallback(
(e) => {
scrollPositionRef.current = e.scrollTop;
if (documentHeight) {
const isAtBottom =
scrollPositionRef.current + 944 >=
documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0);
if (isAtBottom !== isBottom) {
setIsBottom(isAtBottom);
}
}
},
[documentHeight, isBottom, youMayAlsoLikelRef]
);
const productFocus = useCallback(() => {
setActiveProductBtn(true);
setActiveReviewBtn(false);
setActiveYouMayLikeBtn(false);
}, []);
const reviewFocus = useCallback(() => {
setActiveProductBtn(false);
setActiveReviewBtn(true);
setActiveYouMayLikeBtn(false);
}, []);
const youmaylikeFocus = useCallback(() => {
setActiveProductBtn(false);
setActiveReviewBtn(false);
setActiveYouMayLikeBtn(true);
}, []);
const _onBlur = useCallback(() => {
setActiveProductBtn(false);
setActiveReviewBtn(false);
setActiveYouMayLikeBtn(false);
}, []);
useEffect(() => {
setDocumentHeight(
(productDetailRef.current?.scrollHeight || 0) +
(descriptionRef.current?.scrollHeight || 0) +
(reviewRef.current?.scrollHeight || 0)
);
setReviewHeight(
(productDetailRef.current?.scrollHeight || 0) +
(descriptionRef.current?.scrollHeight || 0) +
(reviewRef.current?.scrollHeight || 0)
);
}, [
productDetailRef.current,
descriptionRef.current,
hasReviews,
hasYouMayAlsoLike,
]);
//spot관련
useEffect(() => {
if (onReady && isOnRender) {
onReady();
}
}, [onReady, isOnRender]);
return ( return (
<HorizontalContainer className={css.detailArea}> <HorizontalContainer className={css.detailArea}>
@@ -342,26 +478,31 @@ export default function ProductAllSection({
)} )}
</Container> </Container>
{orderPhnNo && ( <div className={css.callToOrderSection}>
<div className={css.callToOrderSection}> {orderPhnNo && (
<div className={css.callToOrderText}> <>
{$L("Call to Order")} <div className={css.callToOrderText}>
</div> {$L("Call to Order")}
<div className={css.phoneSection}>
<div className={css.phoneIconContainer}>
<div className={css.phoneIcon} />
</div> </div>
<div className={css.phoneNumber}>{orderPhnNo}</div> <div className={css.phoneSection}>
</div> <div className={css.phoneIconContainer}>
</div> <div className={css.phoneIcon} />
)} </div>
<div className={css.phoneNumber}>{orderPhnNo}</div>
</div>
</>
)}
</div>
<Container <Container
className={css.actionButtonsWrapper} className={css.actionButtonsWrapper}
spotlightId="product-info-button-container" spotlightId="product-info-button-container"
> >
<TButton <TButton
className={css.productDetailsButton} className={classNames(
css.productDetailsButton,
activeProductBtn ? css.active : ""
)}
onClick={handleProductDetailsClick} onClick={handleProductDetailsClick}
spotlightId="product-details-button" spotlightId="product-details-button"
> >
@@ -369,7 +510,10 @@ export default function ProductAllSection({
</TButton> </TButton>
{hasReviews && ( {hasReviews && (
<TButton <TButton
className={css.userReviewsButton} className={classNames(
css.userReviewsButton,
activeReviewBtn ? css.active : ""
)}
onClick={handleUserReviewsClick} onClick={handleUserReviewsClick}
spotlightId="user-reviews-button" spotlightId="user-reviews-button"
> >
@@ -378,7 +522,10 @@ export default function ProductAllSection({
)} )}
{hasYouMayAlsoLike && ( {hasYouMayAlsoLike && (
<TButton <TButton
className={css.youMayLikeButton} className={classNames(
css.youMayLikeButton,
activeYouMayLikeBtn ? css.active : ""
)}
onClick={handleYouMayAlsoLikeClick} onClick={handleYouMayAlsoLikeClick}
> >
{$L("YOU MAY ALSO LIKE")} {$L("YOU MAY ALSO LIKE")}
@@ -387,7 +534,8 @@ export default function ProductAllSection({
</Container> </Container>
{panelInfo && {panelInfo &&
panelInfo && panelInfo.type === "theme" && panelInfo &&
panelInfo.type === "theme" &&
!openThemeItemOverlay && ( !openThemeItemOverlay && (
<TButton <TButton
className={css.themeButton} className={css.themeButton}
@@ -425,15 +573,23 @@ export default function ProductAllSection({
spotlightId="main-content-scroller" spotlightId="main-content-scroller"
spotlightDisabled={false} spotlightDisabled={false}
spotlightRestrict="none" spotlightRestrict="none"
onScroll={handleScroll}
> >
<div className={css.productDetail}> <div className={css.productDetail}>
<div <div
id="scroll-marker-product-details" id="scroll-marker-product-details"
className={css.scrollMarker} className={css.scrollMarker}
></div> ></div>
<LayoutSample onClick={handleLayoutSampleClick} /> {/* <LayoutSample onClick={handleLayoutSampleClick} /> */}
<div id="product-details-section"> <div
{productData && productData.imgUrls600 && productData.imgUrls600.length > 0 ? ( id="product-details-section"
ref={productDetailRef}
onFocus={productFocus}
onBlur={_onBlur}
>
{productData &&
productData.imgUrls600 &&
productData.imgUrls600.length > 0 ? (
productData.imgUrls600.map((image, index) => ( productData.imgUrls600.map((image, index) => (
<ProductDetail <ProductDetail
key={`product-detail-${index}`} key={`product-detail-${index}`}
@@ -441,7 +597,7 @@ export default function ProductAllSection({
...productData, ...productData,
singleImage: image, singleImage: image,
imageIndex: index, imageIndex: index,
totalImages: productData.imgUrls600.length totalImages: productData.imgUrls600.length,
}} }}
/> />
)) ))
@@ -449,21 +605,29 @@ export default function ProductAllSection({
<ProductDetail productInfo={productData} /> <ProductDetail productInfo={productData} />
)} )}
</div> </div>
<div id="product-description-section"> <div id="product-description-section" ref={descriptionRef}>
<ProductDescription productInfo={productData} /> <ProductDescription productInfo={productData} />
</div> </div>
{/* 리뷰가 있을 때만 UserReviews 섹션 표시 */} {/* 리뷰가 있을 때만 UserReviews 섹션 표시 */}
{hasReviews && ( {hasReviews && (
<> <>
<div id="scroll-marker-user-reviews" className={css.scrollMarker}></div> <div
<div id="user-reviews-section"> id="scroll-marker-user-reviews"
className={css.scrollMarker}
></div>
<div
id="user-reviews-section"
ref={reviewRef}
onFocus={reviewFocus}
onBlur={_onBlur}
>
<UserReviews <UserReviews
productInfo={productData} productInfo={productData}
panelInfo={panelInfo} panelInfo={panelInfo}
reviewsData={{ reviewsData={{
previewReviews: previewReviews.slice(0, 5), // 처음 5개만 previewReviews: previewReviews.slice(0, 5), // 처음 5개만
stats: stats, stats: stats,
isLoading: reviewsLoading isLoading: reviewsLoading,
}} }}
/> />
</div> </div>
@@ -473,21 +637,34 @@ export default function ProductAllSection({
)} )}
</div> </div>
{hasYouMayAlsoLike && ( {hasYouMayAlsoLike && (
<> <div ref={youMayAlsoLikelRef}>
<div <div
id="scroll-marker-you-may-also-like" id="scroll-marker-you-may-also-like"
className={css.scrollMarker} className={css.scrollMarker}
></div> ></div>
<div id="you-may-also-like-section"> <div id="you-may-also-like-section">
<YouMayAlsoLike productInfo={productData} panelInfo={panelInfo} /> <YouMayAlsoLike
productInfo={productData}
panelInfo={panelInfo}
onFocus={youmaylikeFocus}
onBlur={_onBlur}
/>
</div> </div>
</> </div>
)} )}
</TScrollerDetail> </TScrollerDetail>
</div> </div>
</ContentContainer> </ContentContainer>
{!isBottom && (
<p
className={classNames(css.arrow, css.arrowBottom)}
onClick={handleArrowClickAlternative}
>
<img src={arrowDown} />
SCROLL DOWN
</p>
)}
</div> </div>
</HorizontalContainer> </HorizontalContainer>
); );
} }

View File

@@ -20,9 +20,6 @@
// 1. Left Margin Section - 60px // 1. Left Margin Section - 60px
.leftMarginSection { .leftMarginSection {
position: absolute;
left: 0;
top: 0;
width: 60px; width: 60px;
height: 100%; height: 100%;
padding: 0; padding: 0;
@@ -32,9 +29,6 @@
// 2. Info Section - 645px // 2. Info Section - 645px
.infoSection { .infoSection {
position: absolute;
left: 60px;
top: 0;
width: 650px; width: 650px;
height: 100%; height: 100%;
padding: 0; padding: 0;
@@ -49,9 +43,6 @@
// 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바) // 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바)
.contentSection { .contentSection {
position: absolute;
left: 705px; // 60px + 645px
top: 0;
width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바 width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바
height: 100%; height: 100%;
padding: 0; padding: 0;
@@ -286,7 +277,8 @@
} }
// 포커스 상태 추가 // 포커스 상태 추가
&:focus { &:focus,
&.active {
background: @PRIMARY_COLOR_RED !important; // 포커스시 빨간색 배경 background: @PRIMARY_COLOR_RED !important; // 포커스시 빨간색 배경
outline: 2px solid @PRIMARY_COLOR_RED !important; outline: 2px solid @PRIMARY_COLOR_RED !important;
@@ -403,7 +395,8 @@
font-weight: 400; // Bold에서 Regular로 변경 font-weight: 400; // Bold에서 Regular로 변경
line-height: 35px; line-height: 35px;
&:focus { &:focus,
&.active {
background: #c72054; // 포커스시만 빨간색 background: #c72054; // 포커스시만 빨간색
} }
} }
@@ -495,9 +488,6 @@
// 1. Left Margin Section - 60px // 1. Left Margin Section - 60px
.leftMarginSection { .leftMarginSection {
position: absolute;
left: 0;
top: 0;
width: 60px; width: 60px;
height: 100%; height: 100%;
padding: 0; padding: 0;
@@ -507,9 +497,6 @@
// 2. Info Section - 645px // 2. Info Section - 645px
.infoSection { .infoSection {
position: absolute;
left: 60px;
top: 0;
width: 650px; width: 650px;
height: 100%; height: 100%;
padding: 0; padding: 0;
@@ -524,9 +511,6 @@
// 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바) // 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바)
.contentSection { .contentSection {
position: absolute;
left: 705px; // 60px + 645px
top: 0;
width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바 width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바
height: 100%; height: 100%;
padding: 0; padding: 0;
@@ -943,3 +927,32 @@
} }
} }
} }
.arrow {
width: 238px;
height: 75px;
background-color: #656b78;
line-height: 75px;
position: absolute;
left: 1315px; //710px + 1210px /2
bottom: 2px;
opacity: 0.7;
transform: translateX(-119px);
color: @COLOR_WHITE;
vertical-align: top;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
text-align: center;
border-radius: 6px;
&:hover {
background-color: @PRIMARY_COLOR_RED;
opacity: 1;
}
> img {
margin-right: 5px;
width: 26px;
height: 15px;
}
}

View File

@@ -59,7 +59,7 @@
width: 1060px; // 절대 크기 지정 width: 1060px; // 절대 크기 지정
height: 100%; height: 100%;
// 좌측 30px, 우측 66px(스크롤바) 패딩을 명시적으로 적용 // 좌측 30px, 우측 66px(스크롤바) 패딩을 명시적으로 적용
padding: 30px 20px 30px 20px; padding: 10px 20px 0px 20px;
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
overflow-y: auto; overflow-y: auto;
@@ -88,12 +88,11 @@
// gap 대신 margin 사용 (TV 호환성) - 화면을 적절히 채우도록 조정 // gap 대신 margin 사용 (TV 호환성) - 화면을 적절히 채우도록 조정
.imageGrid { .imageGrid {
width: 100%; width: 100%;
padding: 0; padding: 0;
margin: 0; margin: 20px 0 0 0;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
justify-content: center; justify-content: flex-start;
align-items: flex-start; align-items: flex-start;
overflow: visible; overflow: visible;
height: 100%; height: 100%;
@@ -101,44 +100,45 @@
align-content: flex-start; align-content: flex-start;
// padding: 30px 40px; // 좌우 패딩 증가 // padding: 30px 40px; // 좌우 패딩 증가
// padding: 30px 0px 30px 15px; // 좌우 패딩 증가 // padding: 30px 0px 30px 15px; // 좌우 패딩 증가
.imageItem { .imageItem {
//스타일 변경 //스타일 변경
// width: 210px; // 크기 약간 증가 // width: 226px; // 크기 약간 증가
// height: 190px; // 비율 맞춤 // height: 218px; // 비율 맞춤
width: 240px;
width: 226px; // 크기 약간 증가 height: 240px;
height: 218px; // 비율 맞춤
border-radius: 12px; border-radius: 12px;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
//스타일 변경 //스타일 변경
// margin-right: 35px; // 마진 증가로 균등 분배 // margin-right: 35px; // 마진 증가로 균등 분배
// margin-bottom: 30px; // 세로 마진도 증가 // margin-bottom: 30px; // 세로 마진도 증가
margin-right: 20px; // 마진 증가로 균등 분배 margin-right: 6px; // 마진 증가로 균등 분배
margin-top: 20px; // 세로 마진도 증가 > div {
> img {
width: 226px;
height: 220px;
}
}
// 4개씩 배치하므로 4번째마다 margin-right 제거 // 4개씩 배치하므로 4번째마다 margin-right 제거
&:nth-child(4n) {
margin-right: 0;
}
&:focus { &:focus {
//확대 이미지 //확대 이미지
width: 240px; > div {
height: 240px; > img {
margin-right: 6px; width: 240px;
margin-top: -2px; height: 240px;
// margin-top: -3px; outline: none;
outline: none; }
}
&::after { &::after {
.focused(@boxShadow: 0px, @borderRadius: 12px); .focused(@boxShadow: 0px, @borderRadius: 12px);
// 프로젝트 표준 포커스 스타일 사용 (4px solid @PRIMARY_COLOR_RED) // 프로젝트 표준 포커스 스타일 사용 (4px solid @PRIMARY_COLOR_RED)
} }
&:nth-child(4n) {
margin-right: 0;
margin-left: -14px;
}
} }
.image { .image {

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef } from "react"; import React, { useState, useCallback, useEffect, useMemo, useRef } from "react";
import css from "./YouMayAlsoLike.module.less"; import css from "./YouMayAlsoLike.module.less";
import { $L } from "../../../../utils/helperMethods"; import { $L } from "../../../../utils/helperMethods";
import TVerticalPagenator from "../../../../components/TVerticalPagenator/TVerticalPagenator"; import TVerticalPagenator from "../../../../components/TVerticalPagenator/TVerticalPagenator";
@@ -31,14 +31,26 @@ const Container = SpotlightContainerDecorator(
"div" "div"
); );
export default function YouMayAlsoLike({ productInfo, panelInfo }) { export default function YouMayAlsoLike({ productInfo, panelInfo, onFocus, onBlur }) {
const { getScrollTo, scrollLeft } = useScrollTo(); const { getScrollTo, scrollLeft } = useScrollTo();
const [newYoumaylikeProductData, setNewYoumaylikeProductData] = useState([]);
const dispatch = useDispatch(); const dispatch = useDispatch();
const focusedContainerIdRef = useRef(null); const focusedContainerIdRef = useRef(null);
const youmaylikeProductData = useSelector( const youmaylikeProductData = useSelector(
(state) => state.main.youmaylikeData (state) => state.main.youmaylikeData
); );
//노출 9개로 변경위한 처리건.
useEffect(() => {
if (youmaylikeProductData && youmaylikeProductData.length > 0) {
setNewYoumaylikeProductData(
youmaylikeProductData.slice(0, youmaylikeProductData.length - 1)
);
} else {
setNewYoumaylikeProductData([]);
}
}, [youmaylikeProductData]);
const panels = useSelector((state) => state.panels.panels); const panels = useSelector((state) => state.panels.panels);
const themeProductInfos = useSelector( const themeProductInfos = useSelector(
(state) => state.home.themeCurationDetailInfoData (state) => state.home.themeCurationDetailInfoData
@@ -63,9 +75,21 @@ export default function YouMayAlsoLike({ productInfo, panelInfo }) {
focusedContainerIdRef.current = containerId; focusedContainerIdRef.current = containerId;
}, []); }, []);
const _onFocus = useCallback(() => {
if (onFocus) {
onFocus();
}
}, [onFocus]);
const _onBlur = useCallback(() => {
if (onBlur) {
onBlur();
}
}, [onBlur]);
return ( return (
<div> <div>
{youmaylikeProductData && youmaylikeProductData.length > 0 && ( {newYoumaylikeProductData && newYoumaylikeProductData.length > 0 && (
<TVerticalPagenator <TVerticalPagenator
spotlightId={"detail_youMayAlsoLike_area"} spotlightId={"detail_youMayAlsoLike_area"}
data-wheel-point={true} data-wheel-point={true}
@@ -74,10 +98,14 @@ export default function YouMayAlsoLike({ productInfo, panelInfo }) {
onFocusedContainerId={onFocusedContainerId} onFocusedContainerId={onFocusedContainerId}
topMargin={36} topMargin={36}
> >
<Container className={css.container}> <Container
className={css.container}
onFocus={_onFocus}
onBlur={_onBlur}
>
<THeader title={$L("YOU MAY ALSO LIKE")} className={css.tHeader} /> <THeader title={$L("YOU MAY ALSO LIKE")} className={css.tHeader} />
<div className={css.renderCardContainer}> <div className={css.renderCardContainer}>
{youmaylikeProductData?.map((product, index) => { {newYoumaylikeProductData?.map((product, index) => {
const { const {
imgUrl, imgUrl,
patnrId, patnrId,
@@ -109,7 +137,6 @@ export default function YouMayAlsoLike({ productInfo, panelInfo }) {
); );
cursorOpen.current.stop(); cursorOpen.current.stop();
}; };
return ( return (
<TItemCard <TItemCard
key={prdtId} key={prdtId}
@@ -130,7 +157,7 @@ export default function YouMayAlsoLike({ productInfo, panelInfo }) {
productName={prdtNm} productName={prdtNm}
onClick={handleItemClick} onClick={handleItemClick}
label={ label={
index * 1 + 1 + " of " + youmaylikeProductData.length index * 1 + 1 + " of " + newYoumaylikeProductData.length
} }
lastLabel=" go to detail, button" lastLabel=" go to detail, button"
/> />

View File

@@ -233,7 +233,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
</div> </div>
<div className={css.reviewsSection__filters__group}> <div className={css.reviewsSection__filters__group}>
<FilterItemButton <FilterItemButton
text={`All stars(${filterCounts?.rating?.all || reviewCount || 0})`} text={`All star(${filterCounts?.rating?.all || reviewCount || 0})`}
onClick={handleAllStarsFilter} onClick={handleAllStarsFilter}
spotlightId="filter-all-stars" spotlightId="filter-all-stars"
ariaLabel="Filter by all star ratings" ariaLabel="Filter by all star ratings"
@@ -241,7 +241,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
isActive={currentFilter.type === 'rating' && currentFilter.value === 'all'} isActive={currentFilter.type === 'rating' && currentFilter.value === 'all'}
/> />
<FilterItemButton <FilterItemButton
text={`5 stars (${filterCounts?.rating?.[5] || 0})`} text={`5 star (${filterCounts?.rating?.[5] || 0})`}
onClick={handle5StarsFilter} onClick={handle5StarsFilter}
spotlightId="filter-5-stars" spotlightId="filter-5-stars"
ariaLabel="Filter by 5 star ratings" ariaLabel="Filter by 5 star ratings"
@@ -250,7 +250,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
isActive={currentFilter.type === 'rating' && currentFilter.value === 5} isActive={currentFilter.type === 'rating' && currentFilter.value === 5}
/> />
<FilterItemButton <FilterItemButton
text={`4 stars (${filterCounts?.rating?.[4] || 0})`} text={`4 star (${filterCounts?.rating?.[4] || 0})`}
onClick={handle4StarsFilter} onClick={handle4StarsFilter}
spotlightId="filter-4-stars" spotlightId="filter-4-stars"
ariaLabel="Filter by 4 star ratings" ariaLabel="Filter by 4 star ratings"
@@ -259,7 +259,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
isActive={currentFilter.type === 'rating' && currentFilter.value === 4} isActive={currentFilter.type === 'rating' && currentFilter.value === 4}
/> />
<FilterItemButton <FilterItemButton
text={`3 stars (${filterCounts?.rating?.[3] || 0})`} text={`3 star (${filterCounts?.rating?.[3] || 0})`}
onClick={handle3StarsFilter} onClick={handle3StarsFilter}
spotlightId="filter-3-stars" spotlightId="filter-3-stars"
ariaLabel="Filter by 3 star ratings" ariaLabel="Filter by 3 star ratings"
@@ -268,7 +268,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
isActive={currentFilter.type === 'rating' && currentFilter.value === 3} isActive={currentFilter.type === 'rating' && currentFilter.value === 3}
/> />
<FilterItemButton <FilterItemButton
text={`2 stars (${filterCounts?.rating?.[2] || 0})`} text={`2 star (${filterCounts?.rating?.[2] || 0})`}
onClick={handle2StarsFilter} onClick={handle2StarsFilter}
spotlightId="filter-2-stars" spotlightId="filter-2-stars"
ariaLabel="Filter by 2 star ratings" ariaLabel="Filter by 2 star ratings"
@@ -277,7 +277,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
isActive={currentFilter.type === 'rating' && currentFilter.value === 2} isActive={currentFilter.type === 'rating' && currentFilter.value === 2}
/> />
<FilterItemButton <FilterItemButton
text={`1 stars (${filterCounts?.rating?.[1] || 0})`} text={`1 star (${filterCounts?.rating?.[1] || 0})`}
onClick={handle1StarsFilter} onClick={handle1StarsFilter}
spotlightId="filter-1-stars" spotlightId="filter-1-stars"
ariaLabel="Filter by 1 star ratings" ariaLabel="Filter by 1 star ratings"
@@ -374,4 +374,4 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
); );
}; };
export default UserReviewPanel; export default UserReviewPanel;

View File

@@ -102,10 +102,10 @@
height: 150px; height: 150px;
border-radius: 14px; border-radius: 14px;
object-fit: cover; object-fit: cover;
margin-right: 15px;
} }
&__content { &__content {
margin-left: 15px;
flex: 1; flex: 1;
align-self: stretch; align-self: stretch;
display: flex; display: flex;
@@ -123,6 +123,9 @@
height: 50px; height: 50px;
object-fit: contain; object-fit: contain;
margin-right: 15px; margin-right: 15px;
border-radius: 50px;
border: 1px solid #808080;
box-sizing: border-box;
} }
&__productId { &__productId {