diff --git a/com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx b/com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx index 734c8c9f..2e19a704 100644 --- a/com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx +++ b/com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx @@ -361,17 +361,15 @@ export default function CheckOutPanel({ panelInfo }) { return () => { console.log('[BuyOption][CheckOutPanel] cleanup useEffect - calling resetCheckoutData'); - // Mock 모드에서도 상태 초기화 필요 (Firefox 홀수/짝수번 패턴 문제 해결) + // API Mode에서만 checkout data 초기화 필요 + // Mock Mode에서는 popup 상태만 정리 (Redux checkout state 유지) if (!BUYNOW_CONFIG.isMockMode()) { dispatch(resetCheckoutData()); } else { - console.log('[BuyOption][CheckOutPanel] Mock Mode - Partial cleanup to prevent state accumulation'); - // Mock Mode에서도 팝업 상태와 관련된 부분 초기화 - dispatch(setHidePopup()); - - // empTermsData 초기화를 위한 액션 디스패치 (empActions에서 reset 액션이 있는지 확인 필요) - // dispatch({ type: 'RESET_EMP_TERMS' }); + console.log('[BuyOption][CheckOutPanel] Mock Mode - Cleaning up popup state only'); } + + dispatch(setHidePopup()); }; }, [dispatch]); 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 cc88bc5f..00fa9fd0 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx @@ -174,7 +174,7 @@ export default function ProductAllSection({ const youmaylikeData = useSelector((state) => state.main.youmaylikeData); // ProductVideo 버전 관리 (1: 기존 modal 방식, 2: 내장 방식 , 3: 비디오 생략) - const [productVideoVersion, setProductVideoVersion] = useState(2); + const [productVideoVersion, setProductVideoVersion] = useState(3); // const [currentHeight, setCurrentHeight] = useState(0); //하단부분까지 갔을때 체크용 diff --git a/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx b/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx index 87b86c54..4231a26f 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx @@ -35,7 +35,7 @@ import { popPanel, pushPanel, } from '../../../actions/panelActions'; -import { finishVideoPreview } from '../../../actions/playActions'; +import { clearAllVideoTimers } from '../../../actions/playActions'; import { getProductOption, getProductOptionId, @@ -374,8 +374,23 @@ const BuyOption = ({ const { mbrId, prdtId, prodSno } = response.data.productList[0]; const cartTpSno = `${mbrId}_${prdtId}_${prodSno}`; // dispatch(popPanel(Config.panel_names.DETAIL_PANEL)); - dispatch(finishVideoPreview()); - dispatch(finishMediaPreview()); + clearAllVideoTimers(); // ProductVideoV2의 타이머 정리 (일반 함수 직접 호출) + dispatch(finishMediaPreview()); // MediaPanel 정리 + + // 🔴 CRITICAL: DetailPanel 뒤에 있을 수 있는 PlayerPanel도 함께 제거 (API Mode) + dispatch((dispatchFn, getState) => { + const panels = getState().panels?.panels || []; + const playerPanelExists = panels.some(p => + p.name === Config.panel_names.PLAYER_PANEL || + p.name === Config.panel_names.PLAYER_PANEL_NEW + ); + + if (playerPanelExists) { + console.log('[BuyOption] ⚠️ API Mode - Found PlayerPanel in stack - removing before checkout'); + dispatchFn(popPanel(Config.panel_names.PLAYER_PANEL)); + } + }); + dispatch( pushPanel({ name: Config.panel_names.CHECKOUT_PANEL, @@ -620,10 +635,26 @@ const BuyOption = ({ console.log('[BuyOption] logInfo:', logInfo); console.log('[BuyOption] Dispatching pushPanel to CHECKOUT_PANEL'); - // CheckOutPanel 이동 전에 PlayerPanel/MediaPanel 상태 정리 - console.log('[BuyOption] Mock Mode - Cleaning up PlayerPanel/MediaPanel before checkout'); - dispatch(finishVideoPreview()); - dispatch(finishMediaPreview()); + // CheckOutPanel 이동 전에 ProductVideoV2 타이머 및 MediaPanel/PlayerPanel 정리 + console.log('[BuyOption] Mock Mode - Cleaning up ProductVideoV2 timers and all media panels before checkout'); + clearAllVideoTimers(); // ProductVideoV2의 타이머 정리 (일반 함수 직접 호출) + dispatch(finishMediaPreview()); // MediaPanel 정리 + + // 🔴 CRITICAL: DetailPanel 뒤에 있을 수 있는 PlayerPanel도 함께 제거 + // (finishMediaPreview는 MediaPanel만 처리하므로 PlayerPanel이 남아있을 수 있음) + // dispatch는 thunk function을 받을 수 있으므로, 이를 활용하여 getState로 panels 접근 + dispatch((dispatchFn, getState) => { + const panels = getState().panels?.panels || []; + const playerPanelExists = panels.some(p => + p.name === Config.panel_names.PLAYER_PANEL || + p.name === Config.panel_names.PLAYER_PANEL_NEW + ); + + if (playerPanelExists) { + console.log('[BuyOption] ⚠️ Found PlayerPanel in stack - removing before checkout'); + dispatchFn(popPanel(Config.panel_names.PLAYER_PANEL)); + } + }); // Mock 모드: 선택 상품의 정보를 panelInfo에 담아서 전달 // CheckOutPanel에서 이 정보로 Mock 상품 데이터 생성 @@ -724,7 +755,7 @@ const BuyOption = ({ name: Config.panel_names.CHECKOUT_PANEL, panelInfo: fallbackPanelInfo, }) - ); + ); } else { // 정상 케이스: checkoutPanelInfo 사용 dispatch( diff --git a/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx b/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx index a78cb35b..158a901d 100644 --- a/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx +++ b/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx @@ -11,6 +11,7 @@ import useReviews, { REVIEW_VERSION } from '../../hooks/useReviews/useReviews'; import fp from '../../utils/fp'; import { panel_names } from '../../utils/Config'; import StarRating from '../DetailPanel/components/StarRating'; +import UserReviewsPopup from '../DetailPanel/ProductContentSection/UserReviews/UserReviewsPopup/UserReviewsPopup'; import FilterItemButton from './components/FilterItemButton'; import UserReviewsList from './components/UserReviewsList'; import UserReviewHeader from './UserReviewHeader'; @@ -46,10 +47,19 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => { filters, filteredReviewListData, currentReviewFilter, + // 전체 리뷰 데이터 (팝업용) + allReviews, + getReviewsWithImages, + extractImagesFromReviews, } = useReviews(prdtId, patnrId); // REVIEW_VERSION 상수에 따라 자동으로 API 선택 const [isPaging, setIsPaging] = useState(false); + // 팝업 상태 관리 + const [isPopupOpen, setIsPopupOpen] = useState(false); + const [popupMode, setPopupMode] = useState('user-reviews'); + const [selectedImageIndex, setSelectedImageIndex] = useState(0); + const productImage = fp.pipe( () => panelInfo, fp.get('productImage'), @@ -372,6 +382,41 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => { dispatch(popPanel()); }, [dispatch, bgVideoInfo]); + // 리뷰 클릭 핸들러 - ProductAllSection의 UserReviews.jsx 패턴 + const handleReviewClick = useCallback( + (review, index) => { + console.log('[UserReviewPanel] Review clicked, opening popup:', { + rvwId: review.rvwId, + index, + }); + + // 전체 리뷰에서 클릭한 리뷰의 실제 인덱스 찾기 + const realIndex = allReviews.findIndex((r) => r.rvwId === review.rvwId); + + setSelectedImageIndex(realIndex >= 0 ? realIndex : index); + setPopupMode('user-reviews'); + setIsPopupOpen(true); + }, + [allReviews] + ); + + // 팝업 닫기 핸들러 + const handleClosePopup = useCallback(() => { + console.log('[UserReviewPanel] Closing popup'); + setIsPopupOpen(false); + setPopupMode('user-reviews'); + setSelectedImageIndex(0); + }, []); + + // 팝업 모드 변경 핸들러 + const handleModeChange = useCallback((newMode, imageIndex = 0) => { + console.log('[UserReviewPanel] Mode change requested:', { newMode, imageIndex }); + setPopupMode(newMode); + if (newMode === 'all-images' || newMode === 'user-reviews') { + setSelectedImageIndex(imageIndex); + } + }, []); + return ( {
{ ) : ( // 폴백: API 데이터 없을 경우 기존 하드코딩된 버튼 표시 <> - - + {(filterCounts?.sentiment?.positive || 0) > 0 && ( + + )} + {(filterCounts?.sentiment?.negative || 0) > 0 && ( + 0 ? "filter-positive" : "filter-quality"} + isActive={ + currentFilter.type === 'sentiment' && currentFilter.value === 'negative' + } + /> + )} )}
@@ -609,10 +658,32 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => { onPrevPage={handlePrevPage} isPaging={isPaging} showAllReviews + panelInfo={{ + prdtId: prdtId, + patnrId: patnrId, + productImage: productImage, + brandLogo: brandLogo, + productName: productName, + avgRating: avgRating, + reviewCount: reviewCount, + }} + onReviewClick={handleReviewClick} /> + + {/* UserReviewsPopup 추가 - ProductAllSection과 동일한 방식 */} +
); }; diff --git a/com.twin.app.shoptime/src/views/UserReview/components/FilterItemButton.module.less b/com.twin.app.shoptime/src/views/UserReview/components/FilterItemButton.module.less index 58b2e9f7..d3f3dfea 100644 --- a/com.twin.app.shoptime/src/views/UserReview/components/FilterItemButton.module.less +++ b/com.twin.app.shoptime/src/views/UserReview/components/FilterItemButton.module.less @@ -39,15 +39,15 @@ } } - // 선택된 상태: 회색 배경 (포커스보다 우선도 높게) + // 선택된 상태: 기본 배경색과 동일하게 (활성화 표시 안 함) &--active { display: flex; padding: 20px; flex-direction: column; align-items: flex-start; - background: #7a808d !important; // 회색 (선택됨) + background: #4a4c50 !important; // 기본 배경색과 동일 border-radius: 100px; - border: 1px solid #7a808d !important; + border: 1px solid #585858 !important; white-space: nowrap; } @@ -61,7 +61,7 @@ &--active { text-align: center; - color: white; + color: #eaeaea; // 기본 텍스트색과 동일 font-size: 24px; font-family: "LG Smart UI"; font-weight: 400; diff --git a/com.twin.app.shoptime/src/views/UserReview/components/UserReviewItem.jsx b/com.twin.app.shoptime/src/views/UserReview/components/UserReviewItem.jsx index 62821242..c9f5f02e 100644 --- a/com.twin.app.shoptime/src/views/UserReview/components/UserReviewItem.jsx +++ b/com.twin.app.shoptime/src/views/UserReview/components/UserReviewItem.jsx @@ -47,6 +47,11 @@ const UserReviewItem = ({ return iso.replace(/-/g, '.'); }; + // review가 없으면 렌더링하지 않음 + if (!review) { + return null; + } + const { reviewImageList, rvwRtng, rvwRgstDtt, rvwCtnt, rvwId, wrtrNknm, rvwWrtrId } = review; return ( diff --git a/com.twin.app.shoptime/src/views/UserReview/components/UserReviewsList.jsx b/com.twin.app.shoptime/src/views/UserReview/components/UserReviewsList.jsx index 9a3e2b51..0b5676d8 100644 --- a/com.twin.app.shoptime/src/views/UserReview/components/UserReviewsList.jsx +++ b/com.twin.app.shoptime/src/views/UserReview/components/UserReviewsList.jsx @@ -30,6 +30,8 @@ const UserReviewsList = ({ onNextPage, onPrevPage, isPaging = false, + panelInfo = null, + onReviewClick, // 상위에서 전달받은 리뷰 클릭 핸들러 }) => { const handleReviewClick = useCallback((review, index) => { console.log('[UserReviewsList] Review item clicked:', { @@ -37,7 +39,12 @@ const UserReviewsList = ({ index: index, review: review, }); - }, []); + + // 상위로 클릭 이벤트 전달 + if (onReviewClick) { + onReviewClick(review, index); + } + }, [onReviewClick]); // ✅ API 필터 활성화 여부 확인 // 필터가 활성화되면 filteredReviewCount (필터링된 리뷰 개수) 사용