// src/views/DetailPanel/DetailPanel.new.jsx import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react'; import { useDispatch, useSelector, } from 'react-redux'; import Spotlight from '@enact/spotlight'; import { setContainerLastFocusedElement } from '@enact/spotlight/src/container'; import { getDeviceAdditionInfo } from '../../actions/deviceActions'; import { getThemeCurationDetailInfo, updateHomeInfo, } from '../../actions/homeActions'; import { getMainCategoryDetail, getMainYouMayLike, } from '../../actions/mainActions'; import { finishModalMediaForce } from '../../actions/mediaActions'; import { popPanel, updatePanel, } from '../../actions/panelActions'; import { finishVideoPreview, pauseFullscreenVideo, pauseModalVideo, resumeFullscreenVideo, resumeModalVideo, } from '../../actions/playActions'; import { clearProductDetail, getProductOptionId, } from '../../actions/productActions'; import { clearAllToasts } from '../../actions/toastActions'; import TBody from '../../components/TBody/TBody'; import TPanel from '../../components/TPanel/TPanel'; import { panel_names } from '../../utils/Config'; import fp from '../../utils/fp'; import { SpotlightIds } from '../../utils/SpotlightIds'; import DetailPanelBackground from './components/DetailPanelBackground'; import THeaderCustom from './components/THeaderCustom'; import css from './DetailPanel.module.less'; import ProductAllSection from './ProductAllSection/ProductAllSection'; import ThemeItemListOverlay from './ThemeItemListOverlay/ThemeItemListOverlay'; // ======= // changeAppStatus, // changeLocalSettings, // setHidePopup, // } from "../../actions/commonActions"; // import { clearCouponInfo } from "../../actions/couponActions"; // import { getDeviceAdditionInfo } from "../../actions/deviceActions"; // import { // clearThemeDetail, // getThemeCurationDetailInfo, // getThemeHotelDetailInfo, // } from "../../actions/homeActions"; // import { // getMainCategoryDetail, // getMainYouMayLike, // } from "../../actions/mainActions"; // import { popPanel, updatePanel } from "../../actions/panelActions"; // import { finishVideoPreview } from "../../actions/playActions"; // import { // clearProductDetail, // getProductGroup, // getProductImageLength, // getProductOptionId, // } from "../../actions/productActions"; // import MobileSendPopUp from "../../components/MobileSend/MobileSendPopUp"; // import TBody from "../../components/TBody/TBody"; // import THeader from "../../components/THeader/THeader"; // import TPanel from "../../components/TPanel/TPanel"; // import * as Config from "../../utils/Config"; // import { panel_names } from "../../utils/Config"; // import { $L, getQRCodeUrl } from "../../utils/helperMethods"; // import css from "./DetailPanel.module.less"; // import GroupProduct from "./GroupProduct/GroupProduct"; // import SingleProduct from "./SingleProduct/SingleProduct"; // import ThemeProduct from "./ThemeProduct/ThemeProduct"; // import UnableProduct from "./UnableProduct/UnableProduct"; // import YouMayLike from "./YouMayLike/YouMayLike"; // import { now } from "lodash"; // >>>>>>> gitlab/develop 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 isLoading = useSelector((state) => fp.pipe(() => state, fp.get('common.appStatus.showLoadingPanel.show'))() ); const themeData = useSelector((state) => fp.pipe( () => state, fp.get('home.productData.themeInfo'), (list) => list && list[0] )() ); const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion); const panels = useSelector((state) => state.panels.panels); const [selectedIndex, setSelectedIndex] = useState(0); const [lgCatCd, setLgCatCd] = useState(''); const [themeProductInfo, setThemeProductInfo] = useState(null); const containerRef = useRef(null); const panelType = useMemo(() => fp.pipe(() => panelInfo, fp.get('type'))(), [panelInfo]); const panelCurationId = useMemo( () => 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 panelLiveReqFlag = useMemo( () => fp.pipe(() => panelInfo, fp.get('liveReqFlag'))(), [panelInfo] ); const panelBgImgNo = useMemo(() => fp.pipe(() => panelInfo, fp.get('bgImgNo'))(), [panelInfo]); const panelLaunchedFromPlayer = useMemo( () => fp.pipe(() => panelInfo, fp.get('launchedFromPlayer'))(), [panelInfo] ); const panelLaunchedFromUserReviewPanel = useMemo( () => fp.pipe(() => panelInfo, fp.get('launchedFromUserReviewPanel'), fp.defaultTo(false))(), [panelInfo] ); const panelBgVideoInfo = useMemo( () => fp.pipe(() => panelInfo, fp.get('bgVideoInfo'), fp.defaultTo(null))(), [panelInfo] ); const hasThemeContents = useMemo( () => fp.pipe( () => ({ panelType, themeData }), ({ panelType, themeData }) => panelType === 'theme' && fp.pipe( () => themeData, fp.get('productInfos'), (list) => Array.isArray(list) && list.length > 0 )() )(), [panelType, themeData] ); const themeProducts = useMemo( () => fp.pipe( () => themeData, fp.get('productInfos'), (list) => (Array.isArray(list) ? list : []) )(), [themeData] ); const panelShouldReload = useMemo( () => fp.pipe(() => panelInfo, fp.get('shouldReload'), fp.defaultTo(false))(), [panelInfo] ); const productPmtSuptYn = useMemo( () => fp.pipe(() => productData, fp.get('pmtSuptYn'))(), [productData] ); const productGrPrdtProcYn = useMemo( () => fp.pipe(() => productData, fp.get('grPrdtProcYn'))(), [productData] ); const productDataSource = useMemo( () => fp.pipe( () => panelType, (type) => (type === 'theme' ? themeData : productData) )(), [panelType, themeData, productData] ); const [productType, setProductType] = useState(null); const [openThemeItemOverlay, setOpenThemeItemOverlay] = useState(false); // <<<<<<< HEAD const [scrollToSection, setScrollToSection] = useState(null); const [pendingScrollSection, setPendingScrollSection] = useState(null); const updateSelectedIndex = useCallback((newIndex) => { setSelectedIndex( fp.pipe( () => newIndex, (index) => Math.max(0, Math.min(index, 999)) // 범위 제한 )() ); }, []); // ======= // useEffect(() => { // if (lgCatCd) { // dispatch( // getMainYouMayLike({ // lgCatCd: lgCatCd, // exclCurationId: panelInfo?.curationId, // exclPatnrId: panelInfo?.patnrId, // exclPrdtId: panelInfo?.prdtId, // catDpTh3: // panelInfo?.type === "theme" // ? themeProductInfos[selectedIndex]?.catDpTh3 // : productData?.catDpTh3, // catDpTh4: // panelInfo?.type === "theme" // ? themeProductInfos[selectedIndex]?.catDpTh4 // : productData?.catDpTh4, // }) // ); // } // }, [panelInfo?.curationId, panelInfo?.patnrId, panelInfo?.prdtId, lgCatCd]); // >>>>>>> gitlab/develop const updateThemeItemOverlay = useCallback((isOpen) => { setOpenThemeItemOverlay(fp.pipe(() => isOpen, Boolean)()); }, []); // <<<<<<< HEAD const onSpotlightUpTButton = useCallback((e) => { // ======= // useEffect(() => { // if ( // themeProductInfos && // themeProductInfos.length > 0 && // panelInfo?.themePrdtId // ) { // for (let i = 0; i < themeProductInfos.length; i++) { // if (themeProductInfos[i].prdtId === panelInfo?.themePrdtId) { // setSelectedIndex(i); // } // } // } // if (hotelInfos && hotelInfos.length > 0 && panelInfo?.themeHotelId) { // for (let i = 0; i < hotelInfos.length; i++) { // if (hotelInfos[i].hotelId === panelInfo?.themeHotelId) { // setSelectedIndex(i); // } // } // } // }, [ // themeProductInfos, // hotelInfos, // panelInfo?.themePrdtId, // panelInfo?.themeHotelId, // ]); // const { detailUrl } = useMemo(() => { // return getQRCodeUrl({ // serverHOST, // serverType, // index: deviceInfo?.dvcIndex, // patnrId: productInfo?.patnrId, // prdtId: productInfo?.prdtId, // entryMenu: entryMenu, // nowMenu: nowMenu, // liveFlag: "Y", // qrType: "billingDetail", // }); // }, [serverHOST, serverType, deviceInfo, entryMenu, productInfo]); // const onSpotlightUpTButton = (e) => { // >>>>>>> gitlab/develop e.stopPropagation(); Spotlight.focus('spotlightId_backBtn'); }, []); useEffect(() => { return () => { // console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] DetailPanel cleanup - calling finishModalMediaForce'); dispatch(finishModalMediaForce()); }; }, [dispatch]); // ✅ DetailPanelBackground 이미지 완전 렌더링 후 그라데이션 배경 숨기기 const handleBackgroundImageReady = useCallback(() => { // console.log('[TRACE-GRADIENT] ✅ DetailPanel - BackgroundImage fully rendered, hiding gradient'); dispatch( updateHomeInfo({ name: panel_names.HOME_PANEL, panelInfo: { showGradientBackground: false, }, }) ); }, [dispatch]); // ✅ [251120] DetailPanel이 사라질 때 처리 - sourcePanel에 따라 switch 문으로 처리 useEffect(() => { return () => { const sourcePanel = panelInfo?.sourcePanel; const sourceMenu = panelInfo?.sourceMenu; // console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel cleanup/unmount triggered', { // sourcePanel, // sourceMenu, // panelsSnapshot: panels.map((p) => p.name), // timestamp: Date.now(), // }); // DetailPanel이 unmount되는 시점 // console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel unmount details:', { // sourcePanel, // sourceMenu, // timestamp: Date.now(), // }); // sourcePanel에 따른 상태 업데이트 switch (sourcePanel) { case panel_names.PLAYER_PANEL: { // PlayerPanel에서 온 경우: PlayerPanel에 detailPanelClosed flag 전달 console.log('[PANEL][DetailPanel] unmount - PlayerPanel에 detailPanelClosed flag 전달'); dispatch( updatePanel({ name: panel_names.PLAYER_PANEL, panelInfo: { detailPanelClosed: true, // ✅ flag detailPanelClosedAt: Date.now(), // ✅ 시점 기록 detailPanelClosedFromSource: sourceMenu, // ✅ 출처 lastFocusedTargetId: panelInfo?.lastFocusedTargetId, // ✅ 포커스 복원 타겟 전달 lockModalFalse: false, // Detail 종료 시 lock 해제 }, }) ); break; } case panel_names.HOME_PANEL: { // HomePanel에서 온 경우: HomePanel에 detailPanelClosed flag 전달 // console.log('[DetailPanel] unmount - HomePanel에 detailPanelClosed flag 전달'); // console.log('[TRACE-GRADIENT] 🔶 DetailPanel unmount - HomePanel 복귀'); dispatch( updateHomeInfo({ name: panel_names.HOME_PANEL, panelInfo: { detailPanelClosed: true, // ✅ flag detailPanelClosedAt: Date.now(), // ✅ 시점 기록 detailPanelClosedFromSource: sourceMenu, // ✅ 출처 showGradientBackground: false, // ✅ 명시적으로 그라데이션 끔기 }, }) ); break; } case panel_names.SEARCH_PANEL: { // SearchPanel에서 온 경우: SearchPanel에 detailPanelClosed flag 전달 console.log('[DetailPanel] unmount - SearchPanel에 detailPanelClosed flag 전달'); dispatch( updatePanel({ name: panel_names.SEARCH_PANEL, panelInfo: { detailPanelClosed: true, // ✅ flag detailPanelClosedAt: Date.now(), // ✅ 시점 기록 detailPanelClosedFromSource: sourceMenu, // ✅ 출처 }, }) ); break; } default: console.warn('[DetailPanel] unmount - 처리되지 않은 sourcePanel:', sourcePanel); break; } }; }, [dispatch, panelInfo?.sourcePanel]); const onBackClick = useCallback( (isCancelClick) => (ev) => { const sourcePanel = panelInfo?.sourcePanel; const sourceMenu = panelInfo?.sourceMenu; // console.log('[🟠UNIQUE_DETAIL_BACK🟠] onBackClick triggered', { // sourcePanel, // sourceMenu, // isCancelClick, // currentPanels: panels.map((p) => p.name), // timestamp: Date.now(), // }); fp.pipe( () => { dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거 // sourcePanel에 따른 사전 처리 switch (sourcePanel) { case panel_names.PLAYER_PANEL: // PlayerPanel에서 온 경우: 플레이어 비디오는 그대로 두고 모달만 정리 console.log('[🟠UNIQUE_DETAIL_BACK🟠] PlayerPanel 출신: 모달 정리만 수행'); dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료 dispatch(finishVideoPreview()); break; case panel_names.HOME_PANEL: case panel_names.SEARCH_PANEL: default: // HomePanel, SearchPanel 등에서 온 경우: 백그라운드 비디오 일시 중지 // console.log( // '[🟠UNIQUE_DETAIL_BACK🟠] source panel:', // sourcePanel, // '백그라운드 비디오 일시 중지' // ); dispatch(pauseFullscreenVideo()); // PLAYER_PANEL 비디오 중지 dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료 dispatch(finishVideoPreview()); break; } // console.log('[🟠UNIQUE_DETAIL_BACK🟠] Calling popPanel(DETAIL_PANEL)'); dispatch(popPanel(panel_names.DETAIL_PANEL)); }, () => { // sourcePanel에 따른 상태 업데이트 switch (sourcePanel) { case panel_names.PLAYER_PANEL: { // PlayerPanel에서 온 경우: PlayerPanel에 detailPanelClosed flag 전달 const shouldUpdatePanel = fp.pipe( () => panels, fp.get('length'), (length) => length === 3 // PlayerPanel이 [1]에 있고 DetailPanel이 [2]에 있는 상태 )() && fp.pipe( () => panels, fp.get('1.name'), (name) => name === panel_names.PLAYER_PANEL )(); if (shouldUpdatePanel) { console.log( '[PANEL][DetailPanel] onBackClick - PlayerPanel에 detailPanelClosed flag 전달' ); dispatch( updatePanel({ name: panel_names.PLAYER_PANEL, panelInfo: { thumbnail: fp.pipe(() => panelInfo, fp.get('thumbnailUrl'))(), detailPanelClosed: true, // ✅ flag detailPanelClosedAt: Date.now(), // ✅ 시점 기록 detailPanelClosedFromSource: sourceMenu, // ✅ 출처 lastFocusedTargetId: panelInfo?.lastFocusedTargetId, // ✅ 포커스 복원 타겟 전달 }, }) ); } break; } case panel_names.HOME_PANEL: { // HomePanel에서 온 경우: HomePanel에 detailPanelClosed flag 전달 console.log('[DetailPanel] onBackClick - HomePanel에 detailPanelClosed flag 전달'); dispatch( updateHomeInfo({ name: panel_names.HOME_PANEL, panelInfo: { detailPanelClosed: true, // ✅ flag detailPanelClosedAt: Date.now(), // ✅ 시점 기록 detailPanelClosedFromSource: sourceMenu, // ✅ 출처 showGradientBackground: false, }, }) ); break; } case panel_names.SEARCH_PANEL: { // SearchPanel에서 온 경우: SearchPanel에 detailPanelClosed flag 전달 console.log('[DetailPanel] onBackClick - SearchPanel에 detailPanelClosed flag 전달'); dispatch( updatePanel({ name: panel_names.SEARCH_PANEL, panelInfo: { detailPanelClosed: true, // ✅ flag detailPanelClosedAt: Date.now(), // ✅ 시점 기록 detailPanelClosedFromSource: sourceMenu, // ✅ 출처 }, }) ); break; } default: console.warn('[DetailPanel] onBackClick - 처리되지 않은 sourcePanel:', sourcePanel); break; } } )(); if (isCancelClick) { ev.stopPropagation(); } }, [dispatch, panelInfo, panels] ); const onBackButtonFocus = useCallback(() => { dispatch(clearAllToasts()); }, [dispatch]); const handleScrollToSection = useCallback( (sectionId) => { 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', scrollFunction: scrollToSection, sectionId, }; } else { return { action: 'store', sectionId }; } } )(); // 액션에 따른 처리 if (scrollAction.action === 'execute') { scrollAction.scrollFunction(scrollAction.sectionId); } else { console.log('DetailPanel: scrollToSection function is null, storing pending scroll'); setPendingScrollSection(scrollAction.sectionId); } }, [scrollToSection] ); // ===== 배경 이미지 설정 (컴포넌트로 구현되어 useEffect 불필요) ===== // DetailPanelBackground 컴포넌트로 배경 렌더링 useEffect(() => { const shouldExecutePendingScroll = fp.pipe( () => ({ scrollToSection, pendingScrollSection }), ({ scrollToSection, pendingScrollSection }) => fp.isNotNil(scrollToSection) && fp.isNotNil(pendingScrollSection) )(); if (shouldExecutePendingScroll) { console.log('DetailPanel: executing pending scroll to:', pendingScrollSection); // 메모리 누수 방지를 위한 cleanup 함수 const timeoutId = setTimeout(() => { if (scrollToSection) { scrollToSection(pendingScrollSection); } setPendingScrollSection(null); }, 100); // cleanup 함수 반환으로 메모리 누수 방지 return () => { clearTimeout(timeoutId); }; } }, [scrollToSection, pendingScrollSection]); useEffect(() => { const loadInitialData = fp.pipe( () => { // 기본 액션 디스패치 dispatch(getProductOptionId(undefined)); dispatch(getDeviceAdditionInfo()); }, () => { // 테마 데이터 로딩 const isThemeType = panelType === 'theme'; if (isThemeType) { dispatch( getThemeCurationDetailInfo({ patnrId: panelPatnrId, curationId: panelCurationId, bgImgNo: panelBgImgNo, }) ); } }, () => { // 일반 상품 데이터 로딩 const hasProductId = fp.isNotNil(panelPrdtId); const hasNoCuration = fp.isNil(panelCurationId); if (hasProductId && hasNoCuration) { dispatch( getMainCategoryDetail({ patnrId: panelPatnrId, prdtId: panelPrdtId, liveReqFlag: panelLiveReqFlag || 'N', }) ); } } )(); // cleanup 함수로 메모리 누수 방지 return () => { // 필요한 경우 cleanup 로직 추가 }; }, [ dispatch, panelLiveReqFlag, panelCurationId, panelPrdtId, panelType, panelPatnrId, panelBgImgNo, ]); useEffect(() => { const shouldLoadRecommendations = fp.pipe(() => lgCatCd, fp.isNotEmpty)(); if (shouldLoadRecommendations) { const youMayLikeParams = { lgCatCd: lgCatCd, exclCurationId: panelInfo?.curationId, exclPatnrId: panelInfo?.patnrId, exclPrdtId: panelInfo?.prdtId, catDpTh3: panelInfo?.type === 'theme' ? themeProductInfos[selectedIndex]?.catDpTh3 : productData?.catDpTh3, catDpTh4: panelInfo?.type === 'theme' ? themeProductInfos[selectedIndex]?.catDpTh4 : productData?.catDpTh4, }; // console.log('[YouMayLike]-youmaylikeData 요청 파라미터:', youMayLikeParams); dispatch(getMainYouMayLike(youMayLikeParams)); } }, [panelInfo?.curationId, panelInfo?.patnrId, panelInfo?.prdtId, lgCatCd]); const getlgCatCd = useCallback(() => { // DetailPanel.backup.jsx와 완전히 동일한 로직 if (productData && !panelInfo?.curationId) { // console.log('[YouMayLike] lgCatCd 설정 (일반상품):', productData.catCd); setLgCatCd(productData.catCd); } else if ( themeProductInfos && themeProductInfos[selectedIndex]?.pmtSuptYn === 'N' && panelInfo?.curationId ) { const themeCatCd = themeProductInfos[selectedIndex]?.catCd; // console.log('[YouMayLike] lgCatCd 설정 (테마상품):', themeCatCd); setLgCatCd(themeCatCd); } else { // console.log('[YouMayLike] lgCatCd 설정 (빈값):', { // hasProductData: !!productData, // panelCurationId: panelInfo?.curationId, // hasThemeProductInfos: !!themeProductInfos, // selectedIndex, // themeProductPmtSuptYn: themeProductInfos?.[selectedIndex]?.pmtSuptYn // }); setLgCatCd(''); } }, [productData, themeProductInfos, selectedIndex, panelInfo?.curationId]); // 카테고리 코드 업데이트 - DetailPanel.backup.jsx와 동일한 의존성 useEffect(() => { getlgCatCd(); }, [themeProductInfos, productData, panelInfo, selectedIndex, getlgCatCd]); // lgCatCd 변경 추적 로그 // useEffect(() => { // console.log('[YouMayLike] lgCatCd 변경됨:', { // lgCatCd, // willTriggerYouMayLike: !!lgCatCd // }); // }, [lgCatCd]); // youmaylikeData 변경 추적 로그 // useEffect(() => { // console.log('[YouMayLike] DetailPanel - youmaylikeData 변경됨:', { // youmaylikeData, // hasData: !!(youmaylikeData && youmaylikeData.length > 0), // dataLength: youmaylikeData?.length || 0 // }); // }, [youmaylikeData]); // 최근 본 상품 저장이 필요하면: // - 순수 유틸로 빌드/업서트 함수 작성 후, 적절한 useEffect에서 호출하세요. // 예) saveRecentItem(panelInfo, selectedIndex) useEffect(() => { return () => { fp.pipe( () => { dispatch(clearProductDetail()); }, () => { setContainerLastFocusedElement(null, ['indicator-GridListContainer']); } )(); }; }, [dispatch]); // CheckOutPanel에서 돌아올 때 DetailPanel 재시작 useEffect(() => { if (panelShouldReload) { console.log('[DetailPanel] Reloading panel data...'); // 기존 데이터 초기화 dispatch(clearProductDetail()); // 데이터 다시 로딩 const isThemeType = panelType === 'theme'; if (isThemeType && panelCurationId) { dispatch( getThemeCurationDetailInfo({ patnrId: panelPatnrId, curationId: panelCurationId, bgImgNo: panelBgImgNo, }) ); } else if (panelPrdtId && !panelCurationId) { dispatch( getMainCategoryDetail({ patnrId: panelPatnrId, prdtId: panelPrdtId, liveReqFlag: panelLiveReqFlag || 'N', }) ); } // 재시작 플래그 제거 dispatch( updatePanel({ name: panel_names.DETAIL_PANEL, panelInfo: { shouldReload: false }, }) ); console.log('[DetailPanel] Reload complete'); } }, [ panelShouldReload, dispatch, panelType, panelPatnrId, panelCurationId, panelBgImgNo, panelPrdtId, panelLiveReqFlag, ]); // 최근 본 상품 트리거 예시: // useEffect(() => { // if (panelInfo && panelInfo.patnrId && panelInfo.prdtId) { // // saveRecentItem(panelInfo, selectedIndex) // } // }, [panelInfo, selectedIndex]) const versionComparators = useMemo( () => ({ isVersionGTE: fp.curry((target, version) => version >= target), isVersionLT: fp.curry((target, version) => version < target), }), [] ); const conditionCheckers = useMemo( () => ({ hasDataAndCondition: fp.curry((conditionFn, data) => fp.isNotNil(data) && conditionFn(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 createTypeChecker = fp.curry((type, conditions, sideEffect) => fp.pipe( () => conditions(), (isValid) => isValid ? (() => { sideEffect && sideEffect(); return { matched: true, type }; })() : { matched: false } )() ); const productTypeRules = [ // 테마 타입 체크 () => createTypeChecker( 'theme', () => fp.pipe( () => ({ panelCurationId, themeData }), ({ panelCurationId, themeData }) => fp.isNotNil(panelCurationId) && fp.isNotNil(themeData) )(), () => { const themeProduct = fp.pipe( () => themeData, fp.get('productInfos'), fp.get(selectedIndex.toString()) )(); setProductType('theme'); setThemeProductInfo(themeProduct); } ), // Buy Now 타입 체크 (curry 활용) () => createTypeChecker( 'buyNow', () => fp.pipe( () => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion, }), ({ productData, panelPrdtId, 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 활용) () => createTypeChecker( 'shopByMobile', () => fp.pipe( () => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion, }), ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion, }) => { 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') ), ]; const matchedRule = fp.reduce( (result, rule) => (result.matched ? result : rule()), { matched: false }, productTypeRules ); // 매칭되지 않은 경우 디버깅 정보 출력 if (!matchedRule.matched) { const debugInfo = fp.pipe( () => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion, }), ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => ({ pmtSuptYn: productPmtSuptYn, grPrdtProcYn: productGrPrdtProcYn, prdtId: panelPrdtId, webOSVersion, }) )(); console.warn('Unknown product type:', productData); console.warn('Product data properties:', debugInfo); } }, [ panelCurationId, themeData, productPmtSuptYn, productGrPrdtProcYn, panelPrdtId, webOSVersion, selectedIndex, versionComparators, conditionCheckers, ]); useEffect(() => { // productData가 로드된 후에만 getProductType 실행 if (productData || (panelType === 'theme' && themeData)) { getProductType(); } }, [getProductType, productData, themeData, panelType]); // Theme 콘텐츠 진입 시 전달된 themePrdtId를 초기 선택으로 반영 useEffect(() => { if (panelType !== 'theme') return; if (Array.isArray(themeProducts) && panelInfo?.themePrdtId) { const matchedIndex = themeProducts.findIndex( (item) => item?.prdtId === panelInfo.themePrdtId ); if (matchedIndex >= 0) { setSelectedIndex(matchedIndex); } } }, [panelType, themeProducts, panelInfo?.themePrdtId, setSelectedIndex]); // themeProductInfo 업데이트 - selectedIndex 변경 시마다 실행 useEffect(() => { if (themeData?.productInfos && selectedIndex !== undefined) { const themeProduct = themeData.productInfos[selectedIndex]; setThemeProductInfo(themeProduct); } }, [themeData, selectedIndex]); // 타이틀과 aria-label 메모이제이션 (성능 최적화 // themeTitle과 haederTitle 분리.) const headerTitle = useMemo( () => fp.pipe( () => ({ panelPrdtId, productData }), ({ panelPrdtId, productData }) => { const productTitle = fp.pipe( () => ({ panelPrdtId, productData }), ({ panelPrdtId, productData }) => fp.isNotNil(panelPrdtId) && fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)() ? fp.pipe(() => productData, fp.get('prdtNm'))() : null )(); return productTitle || ''; } )(), [panelPrdtId, productData] ); const themeHeaderTitle = useMemo( () => fp.pipe( () => ({ panelType, themeData }), ({ panelType, themeData }) => { const themeTitle = fp.pipe( () => ({ panelType, themeData }), ({ panelType, themeData }) => panelType === 'theme' && fp.pipe(() => themeData, fp.get('curationNm'), fp.isNotNil)() ? fp.pipe(() => themeData, fp.get('curationNm'))() : null )(); return themeTitle || ''; } )(), [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] ); const handleProductAllSectionReady = useCallback(() => { console.log('############## ShopByMobile focus'); const spotTime = setTimeout(() => { // Spotlight.focus(SpotlightIds.DETAIL_SHOPBYMOBILE); Spotlight.focus(SpotlightIds.DETAIL_PRODUCTVIDEO); }, 100); return () => { clearTimeout(spotTime); }; }, []); // 백그라운드 전체화면 비디오 제어: DetailPanel 진입/퇴장 시 useEffect(() => { // PlayerPanel이 존재하는지 확인 (Modal 또는 Fullscreen) const playerPanel = panels.find((panel) => panel.name === panel_names.PLAYER_PANEL); const hasPlayerPanel = !!playerPanel; const isModal = playerPanel?.panelInfo?.modal; // ProductAllSection에 비디오가 있는지 확인 const hasProductVideo = fp.pipe(() => productData, fp.get('prdtMediaUrl'), fp.isNotNil)(); console.log('[Detail-BG] 🎬 DetailPanel - Video Control Check (mount/update):', { hasPlayerPanel, playerPanelModalStatus: isModal, hasProductVideo, sourceMenu: panelInfo?.sourceMenu, productDataUrl: productData?.prdtMediaUrl, timestamp: Date.now(), }); // PlayerPanel이 있고, 제품에 비디오가 있을 때만 비디오 멈춤 if (hasPlayerPanel && hasProductVideo) { console.log('[Detail-BG] ⏸️ DetailPanel - Pausing PlayerPanel video (match: playerPanel + productVideo)', { isModalVideo: isModal, action: isModal ? 'pauseModalVideo' : 'pauseFullscreenVideo', timestamp: Date.now(), }); if (isModal) { dispatch(pauseModalVideo()); } else { dispatch(pauseFullscreenVideo()); } } else { console.log('[Detail-BG] ⏭️ DetailPanel - Skipping pause (no playerPanel or no productVideo)', { hasPlayerPanel, hasProductVideo, reason: !hasPlayerPanel ? 'no playerPanel' : 'no productVideo', timestamp: Date.now(), }); } return () => { // DetailPanel 언마운트 시: 비디오가 있었고 멈췄던 경우만 재생 재개 if (hasPlayerPanel && hasProductVideo) { console.log('[Detail-BG] ▶️ DetailPanel - Resuming PlayerPanel video (unmount cleanup)', { isModalVideo: isModal, action: isModal ? 'resumeModalVideo' : 'resumeFullscreenVideo', sourceMenu: panelInfo?.sourceMenu, timestamp: Date.now(), }); if (isModal) { dispatch(resumeModalVideo()); } else { dispatch(resumeFullscreenVideo()); } } else { console.log('[Detail-BG] ⏭️ DetailPanel - Skipping resume on unmount', { hasPlayerPanel, hasProductVideo, timestamp: Date.now(), }); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [panelInfo?.sourceMenu, productData?.prdtMediaUrl]); // MediaPanel modal 상태 변화 감지 -> ProductVideo로 포커스 이동 useEffect(() => { const topPanel = panels[panels.length - 1]; // MediaPanel이 modal=true로 복귀했을 때 포커스를 ProductVideo로 이동 // 하지만 MediaPanel에서 이미 포커스를 시도하므로 여기서는 보조 역할만 함 if ( topPanel && topPanel.name === panel_names.MEDIA_PANEL && topPanel.panelInfo.modal === true ) { console.log('[DetailPanel] MediaPanel modal=true detected - will not interfere with focus'); // MediaPanel의 포커스 이동을 방해하지 않기 위해 아무것도 하지 않음 return; } }, [panels]); // PlayerPanel이 modal=true인 경우 비디오 미리보기 중지 useEffect(() => { const hasPlayerPanel = panels.some( (panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal === true ); const launchedFromPlayer = panelInfo?.fromPlayer || panelInfo?.sourcePanel === panel_names.PLAYER_PANEL; if (hasPlayerPanel && !launchedFromPlayer) { console.log('[DetailPanel] PlayerPanel modal=true detected - stopping video preview (non-player source)'); dispatch(finishVideoPreview()); } else if (hasPlayerPanel && launchedFromPlayer) { console.log('[DetailPanel] PlayerPanel modal=true detected - launched from Player, skip finishVideoPreview'); // Detail 동안 modal=true로 바뀌지 않도록 lockModalFalse 설정 const playerPanelEntry = panels.find( (p) => p.name === panel_names.PLAYER_PANEL || p.name === panel_names.PLAYER_PANEL_NEW ); if (playerPanelEntry?.panelInfo?.modal === true) { dispatch( updatePanel({ name: playerPanelEntry.name, panelInfo: { ...playerPanelEntry.panelInfo, modal: false, modalContainerId: undefined, lockModalFalse: true, }, }) ); } } }, [panels, dispatch, panelInfo?.fromPlayer, panelInfo?.sourcePanel]); return (
{useMemo(() => { const renderStates = fp.pipe( () => ({ isLoading, panelInfo, productDataSource, productType }), ({ isLoading, panelInfo, productDataSource, productType }) => { const hasRequiredData = fp.pipe( () => [panelInfo, productDataSource, productType], (data) => fp.reduce((acc, item) => acc && fp.isNotNil(item), true, data) )(); return { canRender: !isLoading && hasRequiredData, showLoading: !isLoading && !hasRequiredData, showNothing: isLoading, }; } )(); if (renderStates.canRender) { return ( ); } return null; }, [ isLoading, panelInfo, productDataSource, productType, selectedIndex, panelPatnrId, panelPrdtId, updateSelectedIndex, openThemeItemOverlay, updateThemeItemOverlay, themeProductInfo, ])}
// ======= // setSelectedIndex={setSelectedIndex} // p // roductInfo={productData || themeProductInfos[selectedIndex]} // setIsYouMayLikeOpened={setIsYouMayLikeOpened} // /> // )} // {activePopup === Config.ACTIVE_POPUP.smsPopup && ( // // )} // // >>>>>>> gitlab/develop ); }