diff --git a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx index 852b794f..d9b1e470 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx @@ -1,85 +1,116 @@ // src/views/DetailPanel/DetailPanel.new.jsx -import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import React, { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch, useSelector } from "react-redux"; -import Spinner from '@enact/sandstone/Spinner'; -import Spotlight from '@enact/spotlight'; -import { setContainerLastFocusedElement } from '@enact/spotlight/src/container'; +import Spinner from "@enact/sandstone/Spinner"; +import Spotlight from "@enact/spotlight"; +import { setContainerLastFocusedElement } from "@enact/spotlight/src/container"; -import indicatorDefaultImage from '../../../assets/images/img-thumb-empty-144@3x.png'; -import { getDeviceAdditionInfo } from '../../actions/deviceActions'; -import { getThemeCurationDetailInfo } from '../../actions/homeActions'; -import { getMainCategoryDetail, getMainYouMayLike } from '../../actions/mainActions'; -import { popPanel, updatePanel } from '../../actions/panelActions'; -import { finishVideoPreview } from '../../actions/playActions'; -import { clearProductDetail, getProductOptionId } from '../../actions/productActions'; -import TBody from '../../components/TBody/TBody'; -import TPanel from '../../components/TPanel/TPanel'; -import { panel_names } from '../../utils/Config'; -import fp from '../../utils/fp'; -import { $L, getQRCodeUrl } from '../../utils/helperMethods'; -import { SpotlightIds } from '../../utils/SpotlightIds'; -import DetailPanelBackground from './components/DetailPanelBackground'; -import THeaderCustom from './components/THeaderCustom'; -import css from './DetailPanel.module.less'; -import DetailPanelSkeleton from './DetailPanelSkeleton/DetailPanelSkeleton'; -import ProductAllSection from './ProductAllSection/ProductAllSection'; -import ThemeItemListOverlay from './ThemeItemListOverlay/ThemeItemListOverlay'; +import indicatorDefaultImage from "../../../assets/images/img-thumb-empty-144@3x.png"; +import { getDeviceAdditionInfo } from "../../actions/deviceActions"; +import { getThemeCurationDetailInfo } from "../../actions/homeActions"; +import { + getMainCategoryDetail, + getMainYouMayLike, +} from "../../actions/mainActions"; +import { popPanel, updatePanel } from "../../actions/panelActions"; +import { finishVideoPreview } from "../../actions/playActions"; +import { + clearProductDetail, + getProductOptionId, +} from "../../actions/productActions"; +import TBody from "../../components/TBody/TBody"; +import TPanel from "../../components/TPanel/TPanel"; +import { panel_names } from "../../utils/Config"; +import fp from "../../utils/fp"; +import { $L, getQRCodeUrl } from "../../utils/helperMethods"; +import { SpotlightIds } from "../../utils/SpotlightIds"; +import DetailPanelBackground from "./components/DetailPanelBackground"; +import THeaderCustom from "./components/THeaderCustom"; +import css from "./DetailPanel.module.less"; +import DetailPanelSkeleton from "./DetailPanelSkeleton/DetailPanelSkeleton"; +import ProductAllSection from "./ProductAllSection/ProductAllSection"; +import ThemeItemListOverlay from "./ThemeItemListOverlay/ThemeItemListOverlay"; export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const dispatch = useDispatch(); const productData = useSelector((state) => state.main.productData); const youmaylikeData = useSelector((state) => state.main.youmaylikeData); - const themeProductInfos = useSelector((state) => state.home.themeCurationDetailInfoData); + const themeProductInfos = useSelector( + (state) => state.home.themeCurationDetailInfoData + ); 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) => fp.pipe( () => state, - fp.get('home.productData.themeInfo'), + fp.get("home.productData.themeInfo"), (list) => list && list[0] )() ); - const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion); + const webOSVersion = useSelector( + (state) => state.common.appStatus.webOSVersion + ); const panels = useSelector((state) => state.panels.panels); const [selectedIndex, setSelectedIndex] = useState(0); 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 { popupVisible, activePopup } = useSelector((state) => state.common.popup); - const [lgCatCd, setLgCatCd] = useState(''); + const { popupVisible, activePopup } = useSelector( + (state) => state.common.popup + ); + const [lgCatCd, setLgCatCd] = useState(""); const [themeProductInfo, setThemeProductInfo] = useState(null); const containerRef = useRef(null); - const panelType = useMemo(() => fp.pipe(() => panelInfo, fp.get('type'))(), [panelInfo]); + const panelType = useMemo( + () => fp.pipe(() => panelInfo, fp.get("type"))(), + [panelInfo] + ); const panelCurationId = useMemo( - () => fp.pipe(() => panelInfo, fp.get('curationId'))(), + () => fp.pipe(() => panelInfo, fp.get("curationId"))(), + [panelInfo] + ); + const panelPatnrId = useMemo( + () => fp.pipe(() => panelInfo, fp.get("patnrId"))(), + [panelInfo] + ); + const panelPrdtId = useMemo( + () => fp.pipe(() => panelInfo, fp.get("prdtId"))(), [panelInfo] ); - 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'))(), + () => fp.pipe(() => panelInfo, fp.get("liveReqFlag"))(), + [panelInfo] + ); + const panelBgImgNo = useMemo( + () => fp.pipe(() => panelInfo, fp.get("bgImgNo"))(), [panelInfo] ); - const panelBgImgNo = useMemo(() => fp.pipe(() => panelInfo, fp.get('bgImgNo'))(), [panelInfo]); // PlayerPanel에서 진입했는지 여부를 panelInfo에서 추출 const panelLaunchedFromPlayer = useMemo( - () => fp.pipe(() => panelInfo, fp.get('launchedFromPlayer'))(), + () => fp.pipe(() => panelInfo, fp.get("launchedFromPlayer"))(), [panelInfo] ); const productPmtSuptYn = useMemo( - () => fp.pipe(() => productData, fp.get('pmtSuptYn'))(), + () => fp.pipe(() => productData, fp.get("pmtSuptYn"))(), [productData] ); const productGrPrdtProcYn = useMemo( - () => fp.pipe(() => productData, fp.get('grPrdtProcYn'))(), + () => fp.pipe(() => productData, fp.get("grPrdtProcYn"))(), [productData] ); @@ -87,7 +118,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { () => fp.pipe( () => panelType, - (type) => (type === 'theme' ? themeData : productData) + (type) => (type === "theme" ? themeData : productData) )(), [panelType, themeData, productData] ); @@ -113,7 +144,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const onSpotlightUpTButton = useCallback((e) => { e.stopPropagation(); - Spotlight.focus('spotlightId_backBtn'); + Spotlight.focus("spotlightId_backBtn"); }, []); const onClick = useCallback( @@ -128,12 +159,12 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const shouldUpdatePanel = fp.pipe( () => panels, - fp.get('length'), + fp.get("length"), (length) => length === 4 )() && fp.pipe( () => panels, - fp.get('1.name'), + fp.get("1.name"), (name) => name === panel_names.PLAYER_PANEL )(); @@ -142,7 +173,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { updatePanel({ name: panel_names.PLAYER_PANEL, panelInfo: { - thumbnail: fp.pipe(() => panelInfo, fp.get('thumbnailUrl'))(), + thumbnail: fp.pipe(() => panelInfo, fp.get("thumbnailUrl"))(), }, }) ); @@ -160,29 +191,31 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const handleScrollToSection = useCallback( (sectionId) => { - console.log('DetailPanel: handleScrollToSection called with:', sectionId); - console.log('DetailPanel: scrollToSection function:', scrollToSection); + console.log("DetailPanel: handleScrollToSection called with:", sectionId); + console.log("DetailPanel: scrollToSection function:", scrollToSection); const scrollAction = fp.pipe( () => ({ scrollToSection, sectionId }), ({ scrollToSection, sectionId }) => { if (fp.isNotNil(scrollToSection)) { return { - action: 'execute', + action: "execute", scrollFunction: scrollToSection, sectionId, }; } else { - return { action: 'store', sectionId }; + return { action: "store", sectionId }; } } )(); // 액션에 따른 처리 - if (scrollAction.action === 'execute') { + if (scrollAction.action === "execute") { scrollAction.scrollFunction(scrollAction.sectionId); } else { - console.log('DetailPanel: scrollToSection function is null, storing pending scroll'); + console.log( + "DetailPanel: scrollToSection function is null, storing pending scroll" + ); setPendingScrollSection(scrollAction.sectionId); } }, @@ -200,7 +233,10 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { )(); if (shouldExecutePendingScroll) { - console.log('DetailPanel: executing pending scroll to:', pendingScrollSection); + console.log( + "DetailPanel: executing pending scroll to:", + pendingScrollSection + ); // 메모리 누수 방지를 위한 cleanup 함수 const timeoutId = setTimeout(() => { @@ -226,7 +262,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }, () => { // 테마 데이터 로딩 - const isThemeType = panelType === 'theme'; + const isThemeType = panelType === "theme"; if (isThemeType) { dispatch( @@ -248,7 +284,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { getMainCategoryDetail({ patnrId: panelPatnrId, prdtId: panelPrdtId, - liveReqFlag: panelLiveReqFlag || 'N', + liveReqFlag: panelLiveReqFlag || "N", }) ); } @@ -278,8 +314,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { exclCurationId: panelInfo?.curationId, exclPatnrId: panelInfo?.patnrId, exclPrdtId: panelInfo?.prdtId, - catDpTh3: productData?.catDpTh3, - catDpTh4: productData?.catDpTh4, + catDpTh3: + panelInfo?.type === "theme" + ? themeProductInfos[selectedIndex]?.catDpTh3 + : productData?.catDpTh3, + catDpTh4: + panelInfo?.type === "theme" + ? themeProductInfos[selectedIndex]?.catDpTh4 + : productData?.catDpTh4, }; // console.log('[YouMayLike]-youmaylikeData 요청 파라미터:', youMayLikeParams); @@ -294,7 +336,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { setLgCatCd(productData.catCd); } else if ( themeProductInfos && - themeProductInfos[selectedIndex]?.pmtSuptYn === 'N' && + themeProductInfos[selectedIndex]?.pmtSuptYn === "N" && panelInfo?.curationId ) { const themeCatCd = themeProductInfos[selectedIndex]?.catCd; @@ -308,7 +350,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // selectedIndex, // themeProductPmtSuptYn: themeProductInfos?.[selectedIndex]?.pmtSuptYn // }); - setLgCatCd(''); + setLgCatCd(""); } }, [productData, themeProductInfos, selectedIndex, panelInfo?.curationId]); @@ -345,7 +387,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { dispatch(clearProductDetail()); }, () => { - setContainerLastFocusedElement(null, ['indicator-GridListContainer']); + setContainerLastFocusedElement(null, ["indicator-GridListContainer"]); } )(); }; @@ -368,7 +410,9 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const conditionCheckers = useMemo( () => ({ - hasDataAndCondition: fp.curry((conditionFn, data) => fp.isNotNil(data) && conditionFn(data)), + hasDataAndCondition: fp.curry( + (conditionFn, data) => fp.isNotNil(data) && conditionFn(data) + ), equalTo: fp.curry((expected, actual) => actual === expected), checkAllConditions: fp.curry((conditions, data) => fp.reduce( @@ -399,7 +443,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // 테마 타입 체크 () => createTypeChecker( - 'theme', + "theme", () => fp.pipe( () => ({ panelCurationId, themeData }), @@ -409,10 +453,10 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { () => { const themeProduct = fp.pipe( () => themeData, - fp.get('productInfos'), + fp.get("productInfos"), fp.get(selectedIndex.toString()) )(); - setProductType('theme'); + setProductType("theme"); setThemeProductInfo(themeProduct); } ), @@ -420,7 +464,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // Buy Now 타입 체크 (curry 활용) () => createTypeChecker( - 'buyNow', + "buyNow", () => fp.pipe( () => ({ @@ -439,21 +483,21 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }) => { const conditions = [ () => fp.isNotNil(productData), - () => conditionCheckers.equalTo('Y')(productPmtSuptYn), - () => conditionCheckers.equalTo('N')(productGrPrdtProcYn), + () => conditionCheckers.equalTo("Y")(productPmtSuptYn), + () => conditionCheckers.equalTo("N")(productGrPrdtProcYn), () => fp.isNotNil(panelPrdtId), - () => versionComparators.isVersionGTE('6.0')(webOSVersion), + () => versionComparators.isVersionGTE("6.0")(webOSVersion), ]; return conditionCheckers.checkAllConditions(conditions)({}); } )(), - () => setProductType('buyNow') + () => setProductType("buyNow") ), // Shop By Mobile 타입 체크 (curry 활용) () => createTypeChecker( - 'shopByMobile', + "shopByMobile", () => fp.pipe( () => ({ @@ -472,21 +516,23 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }) => { if (!productData) return false; - const isDirectMobile = conditionCheckers.equalTo('N')(productPmtSuptYn); + const isDirectMobile = + conditionCheckers.equalTo("N")(productPmtSuptYn); const conditionalMobileConditions = [ - () => conditionCheckers.equalTo('Y')(productPmtSuptYn), - () => conditionCheckers.equalTo('N')(productGrPrdtProcYn), - () => versionComparators.isVersionLT('6.0')(webOSVersion), + () => conditionCheckers.equalTo("Y")(productPmtSuptYn), + () => conditionCheckers.equalTo("N")(productGrPrdtProcYn), + () => versionComparators.isVersionLT("6.0")(webOSVersion), () => fp.isNotNil(panelPrdtId), ]; - const isConditionalMobile = conditionCheckers.checkAllConditions( - conditionalMobileConditions - )({}); + const isConditionalMobile = + conditionCheckers.checkAllConditions( + conditionalMobileConditions + )({}); return isDirectMobile || isConditionalMobile; } )(), - () => setProductType('shopByMobile') + () => setProductType("shopByMobile") ), ]; @@ -506,7 +552,13 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { productGrPrdtProcYn, webOSVersion, }), - ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => ({ + ({ + productData, + panelPrdtId, + productPmtSuptYn, + productGrPrdtProcYn, + webOSVersion, + }) => ({ pmtSuptYn: productPmtSuptYn, grPrdtProcYn: productGrPrdtProcYn, prdtId: panelPrdtId, @@ -514,8 +566,8 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }) )(); - console.warn('Unknown product type:', productData); - console.warn('Product data properties:', debugInfo); + console.warn("Unknown product type:", productData); + console.warn("Product data properties:", debugInfo); } }, [ panelCurationId, @@ -531,7 +583,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { useEffect(() => { // productData가 로드된 후에만 getProductType 실행 - if (productData || (panelType === 'theme' && themeData)) { + if (productData || (panelType === "theme" && themeData)) { getProductType(); } }, [getProductType, productData, themeData, panelType]); @@ -545,7 +597,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }, [themeData, selectedIndex]); const imageUrl = useMemo( - () => fp.pipe(() => productData, fp.get('thumbnailUrl960'))(), + () => fp.pipe(() => productData, fp.get("thumbnailUrl960"))(), [productData] ); @@ -559,20 +611,21 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { () => ({ panelPrdtId, productData }), ({ panelPrdtId, productData }) => fp.isNotNil(panelPrdtId) && - fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)() - ? fp.pipe(() => productData, fp.get('prdtNm'))() + fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)() + ? fp.pipe(() => productData, fp.get("prdtNm"))() : null )(); const themeTitle = fp.pipe( () => ({ panelType, themeData }), ({ panelType, themeData }) => - panelType === 'theme' && fp.pipe(() => themeData, fp.get('curationNm'), fp.isNotNil)() - ? fp.pipe(() => themeData, fp.get('curationNm'))() + panelType === "theme" && + fp.pipe(() => themeData, fp.get("curationNm"), fp.isNotNil)() + ? fp.pipe(() => themeData, fp.get("curationNm"))() : null )(); - return productTitle || themeTitle || ''; + return productTitle || themeTitle || ""; } )(), [panelPrdtId, productData, panelType, themeData] @@ -583,9 +636,10 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { fp.pipe( () => ({ panelPrdtId, productData }), ({ panelPrdtId, productData }) => - fp.isNotNil(panelPrdtId) && fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)() - ? fp.pipe(() => productData, fp.get('prdtNm'))() - : '' + fp.isNotNil(panelPrdtId) && + fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)() + ? fp.pipe(() => productData, fp.get("prdtNm"))() + : "" )(), [panelPrdtId, productData] ); @@ -633,7 +687,12 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { ({ isLoading, panelInfo, productDataSource, productType }) => { const hasRequiredData = fp.pipe( () => [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 {