diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx index 837ffdc2..92008749 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx @@ -274,7 +274,6 @@ export default function ProductAllSection({ // const [currentHeight, setCurrentHeight] = useState(0); //하단부분까지 갔을때 체크용 - const [documentHeight, setDocumentHeight] = useState(0); const [isBottom, setIsBottom] = useState(false); //qr코드 노출용 @@ -283,6 +282,8 @@ export default function ProductAllSection({ // sendLogGNB용 entryMenu const entryMenuRef = useRef(null); + const lastProductDetailLogKeyRef = useRef(null); + const lastGnbLogKeyRef = useRef(null); // 출처 정보 통합 (향후 확장성 대비) // YouMayLike 상품이 아닐 경우 fromPanel을 초기화하여 오기 방지 @@ -298,6 +299,7 @@ export default function ProductAllSection({ // 모든 timeout/timer를 추적하기 위한 ref const timersRef = useRef([]); + const contentHeightRef = useRef(0); // handleScrollToImages의 timeout을 추적하기 위한 ref const scrollToImagesTimeoutRef = useRef(null); @@ -733,6 +735,7 @@ export default function ProductAllSection({ // sendLogGNB 로깅 - Source의 DetailPanel 컴포넌트들과 동일한 패턴 useEffect(() => { + if (!productData?.prdtId) return; if (!entryMenuRef.current) entryMenuRef.current = nowMenu; // BUY NOW 버튼 활성화 상태에 따른 메뉴 결정 (Source SingleProduct vs UnableProduct 패턴) @@ -754,8 +757,22 @@ export default function ProductAllSection({ ? `${baseMenu}/${Config.LOG_MENU.DETAIL_PAGE_YOU_MAY_LIKE}` : baseMenu; + const logKey = `${productData?.patnrId || ''}-${productData?.prdtId || ''}`; + if (lastGnbLogKeyRef.current === logKey) { + return; + } + lastGnbLogKeyRef.current = logKey; + dispatch(sendLogGNB(menu)); - }, [isBillingProductVisible, isGroupProductVisible, isTravelProductVisible, fromPanel?.fromYouMayLike]); + }, [ + isBillingProductVisible, + isGroupProductVisible, + isTravelProductVisible, + fromPanel?.fromYouMayLike, + productData?.patnrId, + productData?.prdtId, + nowMenu, + ]); // sendLogGNB 전송 후 플래그 초기화 (1회 사용 후 비활성화) useEffect(() => { @@ -774,37 +791,54 @@ export default function ProductAllSection({ // sendLogProductDetail 로깅 - Source의 productData 변경 감지와 동일한 패턴 useEffect(() => { - if (productData && Object.keys(productData).length > 0) { - const params = { - befPrice: productData?.priceInfo?.split("|")[0], - curationId: productData?.curationId ?? "", - curationNm: productData?.curationNm ?? "", - entryMenu: entryMenuRef.current, - expsOrd: "1", - inDt: formatGMTString(new Date()), - lastPrice: productData?.priceInfo?.split("|")[1], - lgCatCd: productData?.catCd ?? "", - lgCatNm: productData?.catNm ?? "", - linkTpCd: panelInfo?.linkTpCd ?? "", - logTpNo: isTravelProductVisible - ? Config.LOG_TP_NO.PRODUCT.TRAVEL_DETAIL - : isGroupProductVisible - ? Config.LOG_TP_NO.PRODUCT.GROUP_DETAIL - : isBillingProductVisible - ? Config.LOG_TP_NO.PRODUCT.BILLING_PRODUCT_DETAIL - : Config.LOG_TP_NO.PRODUCT.PRODUCT_DETAIL, - patncNm: productData?.patncNm ?? "", - patnrId: productData?.patnrId ?? "", - prdtId: productData?.prdtId ?? "", - prdtNm: productData?.prdtNm ?? "", - revwGrd: productData?.revwGrd ?? "", - rewdAplyFlag: productData.priceInfo?.split("|")[2], - tsvFlag: productData?.todaySpclFlag ?? "", - }; - - dispatch(sendLogProductDetail(params)); + if (!productData || Object.keys(productData).length === 0) { + return; } - }, [productData, panelInfo?.linkTpCd, isBillingProductVisible, isGroupProductVisible, isTravelProductVisible, dispatch]); // productData 변경 시 재실행 + + const logTpNo = isTravelProductVisible + ? Config.LOG_TP_NO.PRODUCT.TRAVEL_DETAIL + : isGroupProductVisible + ? Config.LOG_TP_NO.PRODUCT.GROUP_DETAIL + : isBillingProductVisible + ? Config.LOG_TP_NO.PRODUCT.BILLING_PRODUCT_DETAIL + : Config.LOG_TP_NO.PRODUCT.PRODUCT_DETAIL; + + const logKey = `${productData?.patnrId || ''}-${productData?.prdtId || ''}-${panelInfo?.linkTpCd || ''}-${logTpNo}`; + if (lastProductDetailLogKeyRef.current === logKey) { + return; + } + lastProductDetailLogKeyRef.current = logKey; + + const params = { + befPrice: productData?.priceInfo?.split("|")[0], + curationId: productData?.curationId ?? "", + curationNm: productData?.curationNm ?? "", + entryMenu: entryMenuRef.current, + expsOrd: "1", + inDt: formatGMTString(new Date()), + lastPrice: productData?.priceInfo?.split("|")[1], + lgCatCd: productData?.catCd ?? "", + lgCatNm: productData?.catNm ?? "", + linkTpCd: panelInfo?.linkTpCd ?? "", + logTpNo, + patncNm: productData?.patncNm ?? "", + patnrId: productData?.patnrId ?? "", + prdtId: productData?.prdtId ?? "", + prdtNm: productData?.prdtNm ?? "", + revwGrd: productData?.revwGrd ?? "", + rewdAplyFlag: productData.priceInfo?.split("|")[2], + tsvFlag: productData?.todaySpclFlag ?? "", + }; + + dispatch(sendLogProductDetail(params)); + }, [ + productData, + panelInfo?.linkTpCd, + isBillingProductVisible, + isGroupProductVisible, + isTravelProductVisible, + dispatch, + ]); // productData 변경 시 재실행 // [251115] 주석 처리: MediaPanel에서 이미 포커스 이동을 처리하므로 // ProductAllSection의 자동 포커스는 포커스 탈취를 일으킬 수 있음 @@ -1246,6 +1280,13 @@ export default function ProductAllSection({ const prevScrollTopRef = useRef(0); // HomePanel 스타일 스크롤 위치 추적 const scrollExpandTimerRef = useRef(null); // 스크롤 확장 타이머 const mediaMinimizedRef = useRef(false); + const getTotalContentHeight = useCallback(() => { + const measuredHeight = + contentHeightRef.current || + scrollContainerRef.current?.scrollHeight || + 0; + return measuredHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0); + }, []); const handleArrowClickAlternative = useCallback(() => { dispatch(minimizeModalMedia()); @@ -1258,14 +1299,14 @@ export default function ProductAllSection({ animate: true, }); - // documentHeight를 활용하여 반복 계산 제거 - const totalHeight = documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0); + // 캐시된 콘텐츠 높이를 활용하여 반복 계산 최소화 + const totalHeight = getTotalContentHeight(); const isAtBottom = scrollPositionRef.current + 1100 >= totalHeight; if (isAtBottom) { setIsBottom(isAtBottom); } - }, [documentHeight, scrollTop, dispatch]); + }, [scrollTop, dispatch, getTotalContentHeight]); const handleScroll = useCallback( (e) => { @@ -1276,12 +1317,12 @@ export default function ProductAllSection({ const prevScrollTop = prevScrollTopRef.current; scrollPositionRef.current = currentScrollTop; + contentHeightRef.current = e?.scrollHeight || contentHeightRef.current || 0; // 기존 bottom 체크 로직 유지 - if (documentHeight) { - const isAtBottom = - scrollPositionRef.current + 944 >= - documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0); + const totalHeight = getTotalContentHeight(); + if (totalHeight) { + const isAtBottom = scrollPositionRef.current + 944 >= totalHeight; if (isAtBottom !== isBottom) { setIsBottom(isAtBottom); } @@ -1334,7 +1375,7 @@ export default function ProductAllSection({ } // v2: onScrollStop에서 처리 (기존 로직 유지) }, - [documentHeight, isBottom, productVideoVersion, isVideoPlaying, dispatch] + [isBottom, productVideoVersion, isVideoPlaying, dispatch, getTotalContentHeight] ); // 스크롤 멈추었을 때만 호출 (성능 최적화) @@ -1342,10 +1383,10 @@ export default function ProductAllSection({ (e) => { const currentScrollTop = e.scrollTop; scrollPositionRef.current = currentScrollTop; - if (documentHeight) { - const isAtBottom = - currentScrollTop + 944 >= - documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0); + contentHeightRef.current = e?.scrollHeight || contentHeightRef.current || 0; + const totalHeight = getTotalContentHeight(); + if (totalHeight) { + const isAtBottom = currentScrollTop + 944 >= totalHeight; if (isAtBottom !== isBottom) { setIsBottom(isAtBottom); } @@ -1360,7 +1401,7 @@ export default function ProductAllSection({ return shouldMinimize; }); }, - [documentHeight] + [getTotalContentHeight] ); useEffect(() => { @@ -1385,14 +1426,6 @@ export default function ProductAllSection({ setActiveButton(null); }, []); - useEffect(() => { - setDocumentHeight( - (productDetailRef.current?.scrollHeight || 0) + - (descriptionRef.current?.scrollHeight || 0) + - (reviewRef.current?.scrollHeight || 0) - ); - }, [productDetailRef.current, descriptionRef.current, reviewRef.current]); - // 스크롤 위치에 따른 MediaPanel 제어 (비디오 재생 중에는 자동 제어 안함 - unmount 시에만 정리) // useEffect(() => { // console.log('📍 [ProductAllSection] useEffect 실행 - shouldMinimizeMedia:', shouldMinimizeMedia);