diff --git a/com.twin.app.shoptime/src/actions/actionTypes.js b/com.twin.app.shoptime/src/actions/actionTypes.js index 23e0d69d..c2926686 100644 --- a/com.twin.app.shoptime/src/actions/actionTypes.js +++ b/com.twin.app.shoptime/src/actions/actionTypes.js @@ -156,6 +156,7 @@ export const types = { GET_PRODUCT_OPTION_ID: "GET_PRODUCT_OPTION_ID", CLEAR_PRODUCT_OPTIONS: "CLEAR_PRODUCT_OPTIONS", GET_USER_REVIEW: "GET_USER_REVIEW", + TOGGLE_SHOW_ALL_REVIEWS: "TOGGLE_SHOW_ALL_REVIEWS", // search actions GET_SEARCH: "GET_SEARCH", diff --git a/com.twin.app.shoptime/src/actions/productActions.js b/com.twin.app.shoptime/src/actions/productActions.js index 67ab1991..c380c9dd 100644 --- a/com.twin.app.shoptime/src/actions/productActions.js +++ b/com.twin.app.shoptime/src/actions/productActions.js @@ -194,6 +194,11 @@ const createMockReviewData = () => ({ } }); +// showAllReviews 상태 토글 +export const toggleShowAllReviews = () => ({ + type: types.TOGGLE_SHOW_ALL_REVIEWS +}); + // 상품별 유저 리뷰 리스트 조회 : IF-LGSP-0002 export const getUserReviews = (requestParams) => (dispatch, getState) => { const { prdtId } = requestParams; diff --git a/com.twin.app.shoptime/src/reducers/productReducer.js b/com.twin.app.shoptime/src/reducers/productReducer.js index a32edfe7..72bb02f4 100644 --- a/com.twin.app.shoptime/src/reducers/productReducer.js +++ b/com.twin.app.shoptime/src/reducers/productReducer.js @@ -6,6 +6,7 @@ const initialState = { productImageLength: 0, prdtOptInfo: {}, reviewData: null, // 리뷰 데이터 추가 + showAllReviews: false, // 전체 리뷰 보기 상태 }; // FP: handlers map (curried), pure and immutable updates only @@ -50,6 +51,13 @@ const handleUserReview = curry((state, action) => { return set("reviewData", reviewData, state); }); +// showAllReviews 토글 핸들러 +const handleToggleShowAllReviews = curry((state, action) => { + const currentValue = get("showAllReviews", state); + console.log("[UserReviews] Toggle showAllReviews:", !currentValue); + return set("showAllReviews", !currentValue, state); +}); + const handlers = { [types.GET_BEST_SELLER]: handleBestSeller, [types.GET_PRODUCT_OPTION]: handleProductOption, @@ -59,6 +67,7 @@ const handlers = { [types.CLEAR_PRODUCT_DETAIL]: handleClearProductDetail, [types.GET_PRODUCT_OPTION_ID]: handleProductOptionId, [types.GET_USER_REVIEW]: handleUserReview, // GET_USER_REVIEW 핸들러 추가 + [types.TOGGLE_SHOW_ALL_REVIEWS]: handleToggleShowAllReviews, // showAllReviews 토글 핸들러 }; export const productReducer = (state = initialState, action = {}) => { 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 11491d2d..40b03a61 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx @@ -1,8 +1,9 @@ /* eslint-disable react/jsx-no-bind */ // src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; +import Spottable from "@enact/spotlight/Spottable"; import React, { useCallback, useRef, useState, useMemo, useEffect } from "react"; -import { useSelector } from "react-redux"; +import { useSelector, useDispatch } from "react-redux"; import Spotlight from "@enact/spotlight"; import { PropTypes } from "prop-types"; @@ -12,6 +13,7 @@ import { $L } from "../../../utils/helperMethods"; import { curry, pipe, when, isVal, isNotNil, defaultTo, defaultWith, get, identity, isEmpty, isNil, andThen, tap } from "../../../utils/fp"; +import { toggleShowAllReviews } from "../../../actions/productActions"; import FavoriteBtn from "../components/FavoriteBtn"; import StarRating from "../components/StarRating"; import ProductTag from "../components/ProductTag"; @@ -99,22 +101,17 @@ const extractProductMeta = (productInfo) => ({ orderPhnNo: get("orderPhnNo", productInfo) }); -// 레이아웃 확인용 샘플 컴포넌트 -const LayoutSample = () => ( -
- Layout Sample (1124px x 300px) -
+// 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경 +const SpottableComponent = Spottable("div"); + +const LayoutSample = ({ onClick }) => ( + + Layout Sample - Click to Show All Reviews (1124px x 300px) + ); export default function ProductAllSection({ @@ -129,10 +126,18 @@ export default function ProductAllSection({ setOpenThemeItemOverlay, themeProductInfo, }) { + const dispatch = useDispatch(); + const productData = useMemo(() => getProductData(productType, themeProductInfo, productInfo), [productType, themeProductInfo, productInfo] ); + + // [임시 테스트] LayoutSample 클릭 핸들러 + const handleLayoutSampleClick = useCallback(() => { + console.log("[Test] LayoutSample clicked - dispatching toggleShowAllReviews"); + dispatch(toggleShowAllReviews()); + }, [dispatch]); // 디버깅: 실제 이미지 데이터 확인 useEffect(() => { @@ -383,7 +388,7 @@ export default function ProductAllSection({ id="scroll-marker-product-details" className={css.scrollMarker} > - +
{productData?.imgUrls600 && productData.imgUrls600.length > 0 ? ( productData.imgUrls600.map((image, index) => ( diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less index 58a69c8e..7ea63d15 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less @@ -463,3 +463,26 @@ } } } + +// LayoutSample 포커스 테스트용 스타일 +.layoutSample { + width: 1124px; + height: 300px; + background-color: yellow; + margin-bottom: 20px; + display: flex; + justify-content: center; + align-items: center; + color: black; + font-size: 24px; + font-weight: bold; + cursor: pointer; + position: relative; + border-radius: 8px; + + &:focus { + &::after { + .focused(@boxShadow:22px, @borderRadius:8px); + } + } +} \ No newline at end of file diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx index e3eaba9b..770cd2d8 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx @@ -8,7 +8,7 @@ import { useMemo } from "react"; import Spottable from "@enact/spotlight/Spottable"; import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; import { useDispatch, useSelector } from "react-redux"; -import { getUserReviews } from "../../../../actions/productActions"; +import { getUserReviews, toggleShowAllReviews } from "../../../../actions/productActions"; import StarRating from "../../components/StarRating"; import CustomerImages from "./CustomerImages/CustomerImages"; import UserReviewsPopup from "./UserReviewsPopup/UserReviewsPopup"; @@ -17,11 +17,13 @@ const SpottableComponent = Spottable("div"); const Container = SpotlightContainerDecorator( { - enterTo: "last-focused", + enterTo: "default-element", preserveld: true, leaveFor: { left: "spotlight-product-info-section-container" - } + }, + restrict: "none", + spotlightDirection: "vertical" }, "div" ); @@ -30,10 +32,46 @@ export default function UserReviews({ productInfo, panelInfo }) { const { getScrollTo, scrollTop } = useScrollTo(); const dispatch = useDispatch(); const containerRef = useRef(null); + const tScrollerRef = useRef(null); // 팝업 상태 관리 const [isPopupOpen, setIsPopupOpen] = useState(false); const [selectedImageIndex, setSelectedImageIndex] = useState(0); + // Redux에서 showAllReviews 상태 가져오기 + const showAllReviews = useSelector((state) => state.product.showAllReviews); + + // 디버깅: showAllReviews 상태 변경 확인 + useEffect(() => { + console.log("[UserReviews] showAllReviews state changed:", { + showAllReviews, + reviewListLength: reviewListData?.length || 0, + willShowCount: showAllReviews ? (reviewListData?.length || 0) : 5 + }); + }, [showAllReviews, reviewListData]); + + // showAllReviews 상태 변경 시 TScroller 스크롤 영역 강제 재계산 + useEffect(() => { + if (showAllReviews && tScrollerRef.current) { + console.log("[UserReviews] Forcing TScroller to update scroll area for all reviews"); + + // 다음 렌더링 사이클 후 스크롤 영역 재계산 + setTimeout(() => { + if (tScrollerRef.current) { + // TScroller의 스크롤 영역을 강제로 업데이트 + if (typeof tScrollerRef.current.calculateMetrics === 'function') { + tScrollerRef.current.calculateMetrics(); + } + + // 또는 scrollTo를 호출해서 스크롤 영역 업데이트 + if (typeof tScrollerRef.current.scrollTo === 'function') { + tScrollerRef.current.scrollTo({ position: { y: 0 }, animate: false }); + } + + console.log("[UserReviews] TScroller scroll area updated"); + } + }, 100); + } + }, [showAllReviews]); const reviewListData = useSelector( (state) => state.product.reviewData && state.product.reviewData.reviewList ); @@ -51,9 +89,11 @@ export default function UserReviews({ productInfo, panelInfo }) { useEffect(() => { console.log("[UserReviews] Review data received:", { reviewListData, + reviewListLength: reviewListData?.length || 0, reviewTotalCount, reviewDetailData, - hasData: reviewListData && reviewListData.length > 0 + hasData: reviewListData && reviewListData.length > 0, + actualDataLength: Array.isArray(reviewListData) ? reviewListData.length : 'not array' }); }, [reviewListData, reviewTotalCount, reviewDetailData]); @@ -116,6 +156,13 @@ export default function UserReviews({ productInfo, panelInfo }) { setSelectedImageIndex(index); }, []); + + const handleViewAllReviewsClick = useCallback(() => { + console.log("[UserReviews] View All Reviews clicked - dispatching toggleShowAllReviews"); + dispatch(toggleShowAllReviews()); + }, [dispatch]); + + // 이미지 데이터 가공 (CustomerImages와 동일한 로직) const customerImages = useMemo(() => { if (!reviewListData || !Array.isArray(reviewListData)) { @@ -155,9 +202,11 @@ export default function UserReviews({ productInfo, panelInfo }) { spotlightId="user-reviews-container" >
{$L( - `Showing ${reviewListData ? reviewListData.length : 0} out of ${reviewTotalCount} reviews` + `Showing ${reviewListData ? (showAllReviews ? reviewListData.length : Math.min(reviewListData.length, 5)) : 0} out of ${reviewTotalCount} reviews` )}
{reviewListData && - reviewListData.map((review, index) => { + (() => { + const reviewsToShow = showAllReviews ? reviewListData : reviewListData.slice(0, 5); + console.log("[UserReviews] Reviews to render:", { + showAllReviews, + totalReviews: reviewListData.length, + reviewsToShowCount: reviewsToShow.length, + isShowingAll: showAllReviews + }); + return reviewsToShow; + })().map((review, index) => { const { reviewImageList, rvwRtng, rvwRgstDtt, rvwCtnt, rvwId, wrtrNknm, rvwWrtrId } = review; console.log(`[UserReviews] Rendering review ${index}:`, { rvwId, hasImages: reviewImageList && reviewImageList.length > 0 }); @@ -224,7 +282,22 @@ export default function UserReviews({ productInfo, panelInfo }) { ); })} +
+ {/* View All Reviews 버튼 - 일시적으로 코멘트 처리 */} + {/* {!showAllReviews && reviewListData && reviewListData.length > 5 && ( +
+ +
+
View All Reviews +
+
+
+
+ )} */} {/* UserReviewsPopup 추가 */} @@ -237,4 +310,4 @@ export default function UserReviews({ productInfo, panelInfo }) { /> ); -} +} \ No newline at end of file diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.module.less b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.module.less index 9b559131..64e155c4 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.module.less +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.module.less @@ -2,8 +2,10 @@ @import "../../../../style/utils.module.less"; .tScroller { - .size(@w: 1124px, @h: 100%); // 마진 포함 전체 크기 (1054px + 60px) + .size(@w: 1124px, @h: auto); // auto height to accommodate dynamic content max-width: 1124px; + min-height: 500px; // 최소 높이 보장 + max-height: none; // 최대 높이 제한 없음 padding: 0; box-sizing: border-box; } @@ -108,4 +110,52 @@ } } } + + .viewAllReviewsSection { + width: 100%; + height: 105px; // 75px + 30px margin-bottom + display: flex; + justify-content: center; + align-items: flex-start; + margin-top: 20px; + + .viewAllReviewsButton { + width: auto; // "View All Reviews +" 한 줄 표시용으로 확장 + height: 75px; // 20 + 35 + 20 + cursor: pointer; + + &:focus { + &::after { + .focused(@boxShadow:22px, @borderRadius:6px); + } + } + + .viewAllReviewsContent { + width: 100%; + height: 100%; + padding: 20px 30px; + background: rgba(255, 255, 255, 0.05); + border-radius: 6px; + border: 1px solid #EEEEEE; + display: flex; + justify-content: flex-start; + align-items: center; + + .viewAllReviewsText { + color: #EAEAEA; + font-size: 24px; + font-family: @baseFont; + font-weight: 600; + line-height: 35px; + margin-right: 10px; + } + + .viewAllReviewsIcon { + width: 17px; + height: 17px; + // 플러스 아이콘을 위한 스타일 (향후 추가 가능) + } + } + } + } } \ No newline at end of file