[251029] fix: searchId null , UserReviewsV2
🕐 커밋 시간: 2025. 10. 29. 15:24:15 📊 변경 통계: • 총 파일: 9개 • 추가: +409줄 • 삭제: -97줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/actionTypes.js ~ com.twin.app.shoptime/src/actions/productActions.js ~ com.twin.app.shoptime/src/actions/searchActions.js ~ com.twin.app.shoptime/src/hooks/useReviews/useReviews.js ~ com.twin.app.shoptime/src/reducers/productReducer.js ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx ~ com.twin.app.shoptime/src/views/UserReview/ShowUserReviews.jsx ~ com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx 🔧 주요 변경 내용: • 타입 시스템 안정성 강화 • 핵심 비즈니스 로직 개선 • 대규모 기능 개발 • 모듈 구조 개선
This commit is contained in:
@@ -168,6 +168,7 @@ export const types = {
|
||||
GET_PRODUCT_OPTION_ID: 'GET_PRODUCT_OPTION_ID',
|
||||
CLEAR_PRODUCT_OPTIONS: 'CLEAR_PRODUCT_OPTIONS',
|
||||
GET_USER_REVIEW: 'GET_USER_REVIEW',
|
||||
GET_USER_REVIEW_LIST: 'GET_USER_REVIEW_LIST',
|
||||
TOGGLE_SHOW_ALL_REVIEWS: 'TOGGLE_SHOW_ALL_REVIEWS',
|
||||
RESET_SHOW_ALL_REVIEWS: 'RESET_SHOW_ALL_REVIEWS',
|
||||
|
||||
|
||||
@@ -150,22 +150,74 @@ const extractReviewApiData = (apiResponse) => {
|
||||
}
|
||||
};
|
||||
|
||||
// IF-LGSP-101용 API 응답에서 reviewDetail 추출
|
||||
// IF-LGSP-101용 API 응답에서 reviewList + reviewDetail 추출
|
||||
const extractReviewListApiData = (apiResponse) => {
|
||||
try {
|
||||
let reviewDetail = null;
|
||||
console.log('[UserReviewList] 📥 extractReviewListApiData 호출 - 원본 응답:', apiResponse);
|
||||
|
||||
if (apiResponse && apiResponse.data && apiResponse.data.reviewDetail) {
|
||||
reviewDetail = apiResponse.data.reviewDetail;
|
||||
} else if (apiResponse && apiResponse.reviewDetail) {
|
||||
reviewDetail = apiResponse.reviewDetail;
|
||||
let data = null;
|
||||
|
||||
// 응답 구조 분석
|
||||
if (apiResponse && apiResponse.data) {
|
||||
// data 경로에서 추출
|
||||
const apiData = apiResponse.data;
|
||||
|
||||
// 중요: v2 API는 reviewList가 reviewDetail 안에 있을 수 있음
|
||||
let reviewList = apiData.reviewList || [];
|
||||
const reviewDetail = apiData.reviewDetail || {};
|
||||
|
||||
// reviewDetail.reviewList에 실제 데이터가 있으면 사용
|
||||
if (reviewDetail.reviewList && Array.isArray(reviewDetail.reviewList) && reviewList.length === 0) {
|
||||
reviewList = reviewDetail.reviewList;
|
||||
console.log('[UserReviewList] 🔄 reviewDetail.reviewList에서 데이터 추출됨');
|
||||
}
|
||||
|
||||
data = {
|
||||
reviewList: reviewList,
|
||||
reviewDetail: reviewDetail
|
||||
};
|
||||
|
||||
console.log('[UserReviewList] 📊 apiResponse.data 경로에서 추출:', {
|
||||
reviewListLength: data.reviewList.length,
|
||||
reviewDetailKeys: Object.keys(data.reviewDetail),
|
||||
reviewDetail: data.reviewDetail,
|
||||
reviewListSample: data.reviewList.length > 0 ? data.reviewList[0] : 'empty'
|
||||
});
|
||||
} else if (apiResponse) {
|
||||
// 직접 경로에서 추출
|
||||
let reviewList = apiResponse.reviewList || [];
|
||||
const reviewDetail = apiResponse.reviewDetail || {};
|
||||
|
||||
// reviewDetail.reviewList에 실제 데이터가 있으면 사용
|
||||
if (reviewDetail.reviewList && Array.isArray(reviewDetail.reviewList) && reviewList.length === 0) {
|
||||
reviewList = reviewDetail.reviewList;
|
||||
console.log('[UserReviewList] 🔄 reviewDetail.reviewList에서 데이터 추출됨');
|
||||
}
|
||||
|
||||
data = {
|
||||
reviewList: reviewList,
|
||||
reviewDetail: reviewDetail
|
||||
};
|
||||
|
||||
console.log('[UserReviewList] 📊 직접 경로에서 추출:', {
|
||||
reviewListLength: data.reviewList.length,
|
||||
reviewDetailKeys: Object.keys(data.reviewDetail),
|
||||
reviewDetail: data.reviewDetail,
|
||||
reviewListSample: data.reviewList.length > 0 ? data.reviewList[0] : 'empty'
|
||||
});
|
||||
}
|
||||
|
||||
if (!reviewDetail) {
|
||||
if (!data || (!data.reviewList && !data.reviewDetail)) {
|
||||
console.warn('[UserReviewList] ⚠️ reviewList와 reviewDetail 모두 없음:', apiResponse);
|
||||
return null;
|
||||
}
|
||||
|
||||
return reviewDetail;
|
||||
console.log('[UserReviewList] ✅ 추출 완료:', {
|
||||
reviewListLength: data.reviewList.length,
|
||||
reviewDetail: data.reviewDetail
|
||||
});
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('[UserReviewList] ❌ extractReviewListApiData 에러:', error);
|
||||
return null;
|
||||
@@ -455,31 +507,43 @@ export const getUserReviewList = (requestParams) => (dispatch, getState) => {
|
||||
fullResponse: response.data,
|
||||
});
|
||||
|
||||
const reviewDetail = extractReviewListApiData(response.data);
|
||||
const reviewData = extractReviewListApiData(response.data);
|
||||
|
||||
console.log('[UserReviewList] 📊 추출된 reviewDetail:', {
|
||||
hasReviewDetail: !!reviewDetail,
|
||||
reviewDetail: reviewDetail,
|
||||
totRvwCnt: reviewDetail && reviewDetail.totRvwCnt,
|
||||
totRvwAvg: reviewDetail && reviewDetail.totRvwAvg,
|
||||
rvwListCnt: reviewDetail && reviewDetail.rvwListCnt,
|
||||
reviewListLength: reviewDetail && reviewDetail.reviewList ? reviewDetail.reviewList.length : 0,
|
||||
console.log('[UserReviewList] 📊 추출된 리뷰 데이터:', {
|
||||
hasData: !!reviewData,
|
||||
reviewListLength: reviewData && reviewData.reviewList ? reviewData.reviewList.length : 0,
|
||||
reviewDetail: reviewData && reviewData.reviewDetail,
|
||||
totRvwCnt: reviewData && reviewData.reviewDetail && reviewData.reviewDetail.totRvwCnt,
|
||||
totRvwAvg: reviewData && reviewData.reviewDetail && reviewData.reviewDetail.totRvwAvg,
|
||||
});
|
||||
|
||||
if (reviewDetail) {
|
||||
dispatch({
|
||||
type: types.GET_USER_REVIEW_LIST,
|
||||
payload: {
|
||||
reviewDetail,
|
||||
prdtId: prdtId
|
||||
},
|
||||
if (reviewData) {
|
||||
console.log('[UserReviewList] 🔴 dispatch 직전 상태:', {
|
||||
actionType: types.GET_USER_REVIEW_LIST,
|
||||
typeValue: 'GET_USER_REVIEW_LIST',
|
||||
reviewListLength: reviewData.reviewList.length,
|
||||
prdtId: prdtId
|
||||
});
|
||||
|
||||
const action = {
|
||||
type: types.GET_USER_REVIEW_LIST,
|
||||
payload: {
|
||||
...reviewData, // reviewList + reviewDetail 모두 포함
|
||||
prdtId: prdtId
|
||||
},
|
||||
};
|
||||
|
||||
console.log('[UserReviewList] 🟡 dispatch할 액션:', JSON.stringify(action, null, 2));
|
||||
|
||||
dispatch(action);
|
||||
|
||||
console.log('[UserReviewList] 📦 데이터 디스패치 완료:', {
|
||||
reviewDetail,
|
||||
reviewListLength: reviewData.reviewList.length,
|
||||
reviewDetail: reviewData.reviewDetail,
|
||||
prdtId
|
||||
});
|
||||
} else {
|
||||
console.warn('[UserReviewList] ⚠️ reviewDetail 추출 실패');
|
||||
console.warn('[UserReviewList] ⚠️ 리뷰 데이터 추출 실패');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ export const getShopperHouseSearch =
|
||||
(dispatch, getState) => {
|
||||
|
||||
// 이전 데이터 초기화 -> shopperHouseData만 초기화
|
||||
dispatch({ type: types.CLEAR_SHOPPERHOUSE_DATA });
|
||||
// dispatch({ type: types.CLEAR_SHOPPERHOUSE_DATA });
|
||||
|
||||
// 새로운 검색 시작 - 고유 키 생성
|
||||
const currentSearchKey = new Date().getTime();
|
||||
|
||||
@@ -1,17 +1,71 @@
|
||||
import { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { getUserReviews } from '../../actions/productActions';
|
||||
import { getUserReviews, getUserReviewList } from '../../actions/productActions';
|
||||
import fp from '../../utils/fp';
|
||||
|
||||
const DISPLAY_SIZE = 3; // 화면에 표시할 리뷰 개수
|
||||
const STEP_SIZE = 1; // 페이징 시 이동할 리뷰 개수
|
||||
|
||||
const useReviews = (prdtId, patnrId) => {
|
||||
// 🔧 REVIEW_VERSION: 1 = 기존 API (getUserReviews), 2 = 신 API (getUserReviewList)
|
||||
// 이 값을 변경하면 전체 앱에서 API 버전이 변경됩니다
|
||||
export const REVIEW_VERSION = 2; // ← 여기서 1 또는 2로 변경
|
||||
|
||||
console.log('[useReviews] 🔑 REVIEW_VERSION 설정:', REVIEW_VERSION);
|
||||
|
||||
// reviewVersion 파라미터는 더 이상 사용하지 않음 (호환성 유지를 위해 파라미터는 남겨둠)
|
||||
const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
|
||||
const reviewVersion = REVIEW_VERSION; // 항상 REVIEW_VERSION 상수 사용
|
||||
|
||||
console.log('[useReviews] 🟢 useReviews Hook 호출됨 (REVIEW_VERSION 사용):', {
|
||||
prdtId,
|
||||
patnrId,
|
||||
reviewVersion,
|
||||
usingGlobalVersion: true
|
||||
});
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Redux 상태에서 리뷰 데이터 가져오기 - CustomerImages와 동일한 방식
|
||||
const reviewData = useSelector((state) => state.product.reviewData);
|
||||
const loadedPrdtId = useSelector((state) => state.product.loadedPrdtId);
|
||||
// Redux 상태에서 리뷰 데이터 가져오기 - reviewVersion에 따라 선택
|
||||
const reviewData = useSelector((state) => {
|
||||
// v2 디버깅: 전체 product state 확인
|
||||
if (reviewVersion === 2) {
|
||||
console.log('[useReviews_useReviewList] 📥 전체 Redux state.product 상태:', {
|
||||
keys: Object.keys(state.product),
|
||||
reviewData: state.product.reviewData,
|
||||
reviewListData: state.product.reviewListData,
|
||||
loadedPrdtId: state.product.loadedPrdtId,
|
||||
loadedListPrdtId: state.product.loadedListPrdtId
|
||||
});
|
||||
}
|
||||
|
||||
const data = reviewVersion === 1
|
||||
? state.product.reviewData
|
||||
: state.product.reviewListData;
|
||||
|
||||
console.log('[useReviews_useReviewList] 📥 Redux reviewData 선택:', {
|
||||
reviewVersion,
|
||||
selectedState: reviewVersion === 1 ? 'reviewData' : 'reviewListData',
|
||||
dataExists: !!data,
|
||||
dataKeys: data ? Object.keys(data) : 'null',
|
||||
reviewListLength: data?.reviewList?.length || 0
|
||||
});
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
const loadedPrdtId = useSelector((state) => {
|
||||
const id = reviewVersion === 1
|
||||
? state.product.loadedPrdtId
|
||||
: state.product.loadedListPrdtId;
|
||||
|
||||
console.log('[useReviews_useReviewList] 🔑 Redux loadedPrdtId 선택:', {
|
||||
reviewVersion,
|
||||
selectedState: reviewVersion === 1 ? 'loadedPrdtId' : 'loadedListPrdtId',
|
||||
loadedPrdtId: id
|
||||
});
|
||||
|
||||
return id;
|
||||
});
|
||||
|
||||
// 빈 내용 리뷰 필터링 - 의미있는 리뷰만 표시
|
||||
const allReviews = (reviewData?.reviewList || []).filter((review) => {
|
||||
@@ -42,12 +96,27 @@ const useReviews = (prdtId, patnrId) => {
|
||||
const [hasLoadedData, setHasLoadedData] = useState(false);
|
||||
|
||||
// 현재 제품이 이미 로드된 적이 있는지 확인 (Redux 기반)
|
||||
// reviewVersion에 따라 다른 loadedPrdtId 비교
|
||||
const isCurrentProductLoaded = prdtId === loadedPrdtId;
|
||||
|
||||
// Redux 상태 선택 로그
|
||||
useEffect(() => {
|
||||
console.log('[useReviews] 🔍 Redux 상태 선택:', {
|
||||
reviewVersion,
|
||||
prdtId,
|
||||
patnrId,
|
||||
loadedPrdtId,
|
||||
isCurrentProductLoaded,
|
||||
selectedReduxState: reviewVersion === 1 ? 'reviewData' : 'reviewListData',
|
||||
hasReviewData: !!reviewData,
|
||||
reviewDataLength: reviewData?.reviewList?.length || 0
|
||||
});
|
||||
}, [reviewVersion, prdtId, patnrId, loadedPrdtId, isCurrentProductLoaded, reviewData]);
|
||||
|
||||
// UserReviewPanel 전용 페이징 상태 (다른 컴포넌트에 영향 없음)
|
||||
const [userReviewPanelPage, setUserReviewPanelPage] = useState(0);
|
||||
|
||||
// 리뷰 데이터 로드 함수 - useReviews가 모든 API 호출을 담당
|
||||
// 리뷰 데이터 로드 함수 - reviewVersion에 따라 API 선택
|
||||
const loadReviews = useCallback(async () => {
|
||||
if (!prdtId) {
|
||||
console.warn('[useReviews] loadReviews 호출되었지만 prdtId가 없음');
|
||||
@@ -59,36 +128,63 @@ const useReviews = (prdtId, patnrId) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log('[useReviews] loadReviews 시작:', { prdtId, patnrId });
|
||||
setIsLoading(true);
|
||||
|
||||
console.log('[useReviews] 📋 loadReviews 호출됨:', {
|
||||
prdtId,
|
||||
patnrId,
|
||||
reviewVersion,
|
||||
willCallApi: reviewVersion === 1 ? 'getUserReviews (v1)' : 'getUserReviewList (v2)'
|
||||
});
|
||||
|
||||
try {
|
||||
// Redux action을 통해 API 호출 - patnrId 추가
|
||||
await dispatch(getUserReviews({ prdtId, patnrId }));
|
||||
if (reviewVersion === 1) {
|
||||
// 기존 API 호출
|
||||
console.log('[useReviews] 🔄 getUserReviews 호출 중... (v1)');
|
||||
await dispatch(getUserReviews({ prdtId, patnrId }));
|
||||
} else {
|
||||
// 신 API 호출 (v2)
|
||||
console.log('[useReviews] 🔄 getUserReviewList 호출 중... (v2)');
|
||||
await dispatch(getUserReviewList({
|
||||
prdtId,
|
||||
patnrId,
|
||||
filterTpCd: 'ALL',
|
||||
pageSize: 100,
|
||||
pageNo: 1
|
||||
}));
|
||||
}
|
||||
setHasLoadedData(true);
|
||||
// console.log('[useReviews] loadReviews 완료');
|
||||
} catch (error) {
|
||||
console.error('[useReviews] loadReviews 실패:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [prdtId, patnrId, dispatch]);
|
||||
}, [prdtId, patnrId, reviewVersion, dispatch]);
|
||||
|
||||
// prdtId가 변경되면 자동으로 리뷰 데이터 로드 (싱글톤 패턴)
|
||||
// 중요: reviewVersion이 다르면 다른 Redux 상태를 사용하므로 reviewVersion도 체크해야 함
|
||||
useEffect(() => {
|
||||
if (prdtId && patnrId && prdtId !== loadedPrdtId) {
|
||||
// console.log('[useReviews] prdtId changed, loading new data:', {
|
||||
// from: loadedPrdtId,
|
||||
// to: prdtId,
|
||||
// patnrId
|
||||
// });
|
||||
// prdtId 변경 시 로딩 상태 즉시 설정으로 UI 깜빡임 방지
|
||||
setIsLoading(true);
|
||||
setHasLoadedData(false);
|
||||
loadReviews();
|
||||
} else if (prdtId === loadedPrdtId) {
|
||||
// console.log('[useReviews] Using cached data for same prdtId:', { prdtId, patnrId });
|
||||
setHasLoadedData(true); // 캐시된 데이터 사용 시 로드 완료 상태로 설정
|
||||
if (prdtId && patnrId) {
|
||||
const needsLoad = prdtId !== loadedPrdtId;
|
||||
|
||||
console.log('[useReviews] 🔄 로드 필요 여부 체크:', {
|
||||
prdtId,
|
||||
patnrId,
|
||||
loadedPrdtId,
|
||||
reviewVersion,
|
||||
needsLoad,
|
||||
reason: needsLoad ? 'prdtId가 변경됨' : '캐시된 데이터 사용'
|
||||
});
|
||||
|
||||
if (needsLoad) {
|
||||
// prdtId 변경 시 로딩 상태 즉시 설정으로 UI 깜빡임 방지
|
||||
setIsLoading(true);
|
||||
setHasLoadedData(false);
|
||||
loadReviews();
|
||||
} else {
|
||||
// 캐시된 데이터 사용
|
||||
setHasLoadedData(true);
|
||||
}
|
||||
}
|
||||
}, [prdtId, patnrId, loadedPrdtId, loadReviews]);
|
||||
|
||||
@@ -538,16 +634,7 @@ const useReviews = (prdtId, patnrId) => {
|
||||
const apiTotalCount = reviewDetail && reviewDetail.totRvwCnt ? reviewDetail.totRvwCnt : 0;
|
||||
const actualReviewCount = allReviews.length;
|
||||
|
||||
// if (apiTotalCount > actualReviewCount && actualReviewCount > 0) {
|
||||
// console.log('[UserReviews]-API Mismatch API 개수와 실제 개수 불일치:', {
|
||||
// apiTotalCount,
|
||||
// actualReviewCount,
|
||||
// difference: apiTotalCount - actualReviewCount,
|
||||
// reason: 'API가 최대 100개만 반환하는 페이징 제한',
|
||||
// });
|
||||
// }
|
||||
|
||||
return {
|
||||
const result = {
|
||||
totalReviews: actualReviewCount, // 실제로 받은 리뷰 개수 사용 (API 제한 반영)
|
||||
filteredCount: filteredReviews.length, // 필터링된 리뷰 개수
|
||||
displayedCount: displayReviews.length, // 현재 표시 중인 리뷰 개수
|
||||
@@ -559,7 +646,21 @@ const useReviews = (prdtId, patnrId) => {
|
||||
: 0,
|
||||
totalRatingCount: actualReviewCount, // 실제로 받은 리뷰 개수 사용
|
||||
};
|
||||
}, [allReviews.length, filteredReviews.length, displayReviews.length, reviewDetail]);
|
||||
|
||||
console.log('[useReviews_useReviewList] 📊 stats 계산:', {
|
||||
actualReviewCount,
|
||||
apiTotalCount,
|
||||
allReviewsLength: allReviews.length,
|
||||
hasLoadedData,
|
||||
isLoading,
|
||||
isCurrentProductLoaded,
|
||||
reviewVersion,
|
||||
loadedPrdtId,
|
||||
prdtId
|
||||
});
|
||||
|
||||
return result;
|
||||
}, [allReviews.length, filteredReviews.length, displayReviews.length, reviewDetail, hasLoadedData, isLoading, isCurrentProductLoaded, reviewVersion, loadedPrdtId, prdtId]);
|
||||
|
||||
// 데이터 새로고침 - 강제로 다시 로드
|
||||
const refreshData = useCallback(() => {
|
||||
@@ -570,6 +671,23 @@ const useReviews = (prdtId, patnrId) => {
|
||||
loadReviews();
|
||||
}, [loadReviews]);
|
||||
|
||||
// hasReviews 계산
|
||||
const hasReviews = allReviews.length > 0 && hasLoadedData && !isLoading && isCurrentProductLoaded;
|
||||
|
||||
// hasReviews 계산 로그
|
||||
useEffect(() => {
|
||||
console.log('[useReviews_useReviewList] 🔴 hasReviews 계산:', {
|
||||
allReviewsLength: allReviews.length,
|
||||
hasLoadedData,
|
||||
isLoading,
|
||||
isCurrentProductLoaded,
|
||||
loadedPrdtId,
|
||||
prdtId,
|
||||
resultHasReviews: hasReviews,
|
||||
reviewVersion
|
||||
});
|
||||
}, [hasReviews, allReviews.length, hasLoadedData, isLoading, isCurrentProductLoaded, loadedPrdtId, prdtId, reviewVersion]);
|
||||
|
||||
return {
|
||||
// 🔥 핵심 API 함수 - useReviews가 모든 API 호출 담당
|
||||
loadReviews, // 리뷰 데이터 로드 (prdtId 기반)
|
||||
@@ -580,7 +698,7 @@ const useReviews = (prdtId, patnrId) => {
|
||||
previewReviews, // DetailPanel용 미리보기 리뷰 (첫 5개)
|
||||
allReviews, // 전체 원본 리뷰 (필터링 안된, 빈 내용 제외)
|
||||
filteredReviews, // 필터링된 전체 리뷰
|
||||
hasReviews: allReviews.length > 0 && hasLoadedData && !isLoading && isCurrentProductLoaded, // 리뷰 존재 여부 (현재 제품이 로드된 경우에만 true)
|
||||
hasReviews, // 리뷰 존재 여부 (현재 제품이 로드된 경우에만 true)
|
||||
|
||||
// 🔄 UserReviewPanel 전용 페이징 데이터
|
||||
userReviewPanelReviews, // UserReviewPanel용 페이징된 리뷰들 (3개 표시, 1개씩 이동)
|
||||
@@ -618,6 +736,7 @@ const useReviews = (prdtId, patnrId) => {
|
||||
_debug: {
|
||||
prdtId,
|
||||
patnrId,
|
||||
reviewVersion,
|
||||
allReviews: allReviews.slice(0, 3), // 처음 3개만
|
||||
currentFilter,
|
||||
filteredCount: filteredReviews.length,
|
||||
|
||||
@@ -5,9 +5,14 @@ const initialState = {
|
||||
bestSellerData: {},
|
||||
productImageLength: 0,
|
||||
prdtOptInfo: {},
|
||||
reviewData: null, // 리뷰 데이터 추가
|
||||
showAllReviews: false, // 전체 리뷰 보기 상태
|
||||
loadedPrdtId: null, // 현재 로드된 상품 ID
|
||||
// ReviewVersion 1 (기존 API)
|
||||
reviewData: null,
|
||||
loadedPrdtId: null,
|
||||
// ReviewVersion 2 (신 API)
|
||||
reviewListData: null,
|
||||
loadedListPrdtId: null,
|
||||
// 기타
|
||||
showAllReviews: false,
|
||||
};
|
||||
|
||||
// FP: handlers map (curried), pure and immutable updates only
|
||||
@@ -37,21 +42,29 @@ const handleProductOptionId = curry((state, action) =>
|
||||
set('prodOptCdCval', get('payload', action), state)
|
||||
);
|
||||
|
||||
// 유저 리뷰 데이터 핸들러 추가
|
||||
// 유저 리뷰 데이터 핸들러 (v1 - 기존 API)
|
||||
const handleUserReview = curry((state, action) => {
|
||||
const reviewData = get('payload', action);
|
||||
const prdtId = get(['payload', 'prdtId'], action);
|
||||
|
||||
// console.log("[UserReviews] Reducer - Storing review data:", {
|
||||
// prdtId: prdtId,
|
||||
// hasData: !!reviewData,
|
||||
// reviewListLength: reviewData && reviewData.reviewList ? reviewData.reviewList.length : 0,
|
||||
// totalCount: reviewData && reviewData.reviewDetail ? reviewData.reviewDetail.totRvwCnt : 0
|
||||
// });
|
||||
|
||||
return set('reviewData', reviewData, set('loadedPrdtId', prdtId, state));
|
||||
});
|
||||
|
||||
// 유저 리뷰 리스트 데이터 핸들러 (v2 - 신 API)
|
||||
const handleUserReviewList = curry((state, action) => {
|
||||
const reviewListData = get('payload', action);
|
||||
const prdtId = get(['payload', 'prdtId'], action);
|
||||
|
||||
console.log('[productReducer_useReviewList] 🟡 handleUserReviewList:', {
|
||||
prdtId,
|
||||
reviewListDataKeys: reviewListData ? Object.keys(reviewListData) : 'null',
|
||||
reviewListLength: reviewListData?.reviewList?.length || 0,
|
||||
reviewDetail: reviewListData?.reviewDetail
|
||||
});
|
||||
|
||||
return set('reviewListData', reviewListData, set('loadedListPrdtId', prdtId, state));
|
||||
});
|
||||
|
||||
// showAllReviews 토글 핸들러
|
||||
const handleToggleShowAllReviews = curry((state, action) => {
|
||||
const currentValue = get('showAllReviews', state);
|
||||
@@ -73,13 +86,38 @@ const handlers = {
|
||||
[types.GET_VIDEO_INDECATOR_FOCUS]: handleVideoIndicatorFocus,
|
||||
[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 토글 핸들러
|
||||
[types.RESET_SHOW_ALL_REVIEWS]: handleResetShowAllReviews, // showAllReviews 초기화 핸들러
|
||||
[types.GET_USER_REVIEW]: handleUserReview,
|
||||
[types.GET_USER_REVIEW_LIST]: handleUserReviewList,
|
||||
[types.TOGGLE_SHOW_ALL_REVIEWS]: handleToggleShowAllReviews,
|
||||
[types.RESET_SHOW_ALL_REVIEWS]: handleResetShowAllReviews,
|
||||
};
|
||||
|
||||
export const productReducer = (state = initialState, action = {}) => {
|
||||
const type = get('type', action);
|
||||
const handler = handlers[type];
|
||||
return handler ? handler(state, action) : state;
|
||||
|
||||
// v2 디버깅: GET_USER_REVIEW_LIST 액션 확인
|
||||
if (type === 'GET_USER_REVIEW_LIST') {
|
||||
console.log('[productReducer_useReviewList] 🟢 GET_USER_REVIEW_LIST 액션 수신:', {
|
||||
actionType: type,
|
||||
payloadKeys: action.payload ? Object.keys(action.payload) : 'null',
|
||||
reviewListLength: action.payload?.reviewList?.length || 0,
|
||||
prdtId: action.payload?.prdtId,
|
||||
handler: !!handler
|
||||
});
|
||||
}
|
||||
|
||||
const newState = handler ? handler(state, action) : state;
|
||||
|
||||
// v2 디버깅: 상태 업데이트 확인
|
||||
if (type === 'GET_USER_REVIEW_LIST') {
|
||||
console.log('[productReducer_useReviewList] 🔵 상태 업데이트 완료:', {
|
||||
reviewListDataExists: !!newState.reviewListData,
|
||||
reviewListDataKeys: newState.reviewListData ? Object.keys(newState.reviewListData) : 'null',
|
||||
loadedListPrdtId: newState.loadedListPrdtId,
|
||||
reviewListLength: newState.reviewListData?.reviewList?.length || 0
|
||||
});
|
||||
}
|
||||
|
||||
return newState;
|
||||
};
|
||||
|
||||
@@ -261,8 +261,17 @@ export default function ProductAllSection({
|
||||
stats,
|
||||
isLoading: reviewsLoading,
|
||||
hasReviews, // 리뷰 존재 여부 플래그 추가
|
||||
_debug,
|
||||
} = useReviews(productData.prdtId, panelInfo && panelInfo.patnrId);
|
||||
|
||||
console.log('[ProductAllSection_useReviewList] 🟡 반환 데이터:', {
|
||||
reviewVersion: _debug?.reviewVersion,
|
||||
previewReviewsLength: previewReviews?.length || 0,
|
||||
statsTotal: stats?.totalReviews,
|
||||
hasReviews,
|
||||
selectedReduxState: _debug?.reviewVersion === 1 ? 'reviewData' : 'reviewListData'
|
||||
});
|
||||
|
||||
// YouMayAlsoLike 데이터 확인
|
||||
const hasYouMayAlsoLike = youmaylikeData && youmaylikeData.length > 0;
|
||||
|
||||
@@ -323,6 +332,7 @@ export default function ProductAllSection({
|
||||
useState(false);
|
||||
|
||||
const reviewTotalCount = stats.totalReviews;
|
||||
|
||||
const { getScrollTo, scrollTop } = useScrollTo();
|
||||
|
||||
const scrollToSection = useCallback(
|
||||
@@ -343,7 +353,9 @@ export default function ProductAllSection({
|
||||
|
||||
// User Reviews 스크롤 핸들러 추가
|
||||
const handleUserReviewsClick = useCallback(
|
||||
() => scrollToSection('scroll-marker-user-reviews'),
|
||||
() => {
|
||||
scrollToSection('scroll-marker-user-reviews');
|
||||
},
|
||||
[scrollToSection]
|
||||
);
|
||||
|
||||
@@ -761,16 +773,23 @@ export default function ProductAllSection({
|
||||
{$L('PRODUCT DETAILS')}
|
||||
</TButton>
|
||||
{hasReviews && (
|
||||
<TButton
|
||||
className={classNames(
|
||||
css.userReviewsButton,
|
||||
activeButton === 'review' ? css.active : ''
|
||||
)}
|
||||
onClick={handleUserReviewsClick}
|
||||
spotlightId="user-reviews-button"
|
||||
>
|
||||
{$L('USER REVIEWS')} ({reviewTotalCount})
|
||||
</TButton>
|
||||
<>
|
||||
{console.log('[ProductAllSection_useReviewList] 🎯 버튼 렌더링:', {
|
||||
hasReviews,
|
||||
reviewTotalCount,
|
||||
reviewVersion: _debug?.reviewVersion
|
||||
})}
|
||||
<TButton
|
||||
className={classNames(
|
||||
css.userReviewsButton,
|
||||
activeButton === 'review' ? css.active : ''
|
||||
)}
|
||||
onClick={handleUserReviewsClick}
|
||||
spotlightId="user-reviews-button"
|
||||
>
|
||||
{$L('USER REVIEWS')} ({reviewTotalCount})
|
||||
</TButton>
|
||||
</>
|
||||
)}
|
||||
{hasYouMayAlsoLike && (
|
||||
<TButton
|
||||
|
||||
@@ -49,6 +49,13 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
isLoading: fallbackReviews.isLoading,
|
||||
};
|
||||
|
||||
console.log('[UserReviews_useReviewList] 🔵 데이터:', {
|
||||
dataSource: reviewsData ? 'ProductAllSection props' : 'fallback useReviews',
|
||||
fallbackVersion: fallbackReviews._debug?.reviewVersion,
|
||||
previewReviewsLength: actualReviewsData.previewReviews?.length || 0,
|
||||
statsTotal: actualReviewsData.stats?.totalReviews
|
||||
});
|
||||
|
||||
// 팝업 상태 관리 - 모드와 선택된 이미지 인덱스 추가
|
||||
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
||||
const [popupMode, setPopupMode] = useState('customer-images'); // "customer-images", "all-images"
|
||||
@@ -307,6 +314,23 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
</SpottableComponent>
|
||||
);
|
||||
})}
|
||||
{/* View All Reviews Button - 선택적으로 표시 */}
|
||||
{reviewTotalCount > 5 && !showAllReviews && (
|
||||
<div className={css.viewAllReviewsSection}>
|
||||
<Spottable
|
||||
className={css.viewAllReviewsButton}
|
||||
spotlightId="view-all-reviews-button"
|
||||
onClick={() => {
|
||||
console.log('[UserReviews] View All Reviews button clicked');
|
||||
dispatch(toggleShowAllReviews());
|
||||
}}
|
||||
>
|
||||
<div className={css.viewAllReviewsContent}>
|
||||
<span className={css.viewAllReviewsText}>{$L('View All Reviews +')}</span>
|
||||
</div>
|
||||
</Spottable>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</UserReviewsScroller>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import Spottable from '@enact/spotlight/Spottable';
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import { pushPanel } from '../../actions/panelActions';
|
||||
import { panel_names } from '../../utils/Config';
|
||||
import useReviews, { REVIEW_VERSION } from '../../hooks/useReviews/useReviews';
|
||||
import css from './ShowUserReviews.module.less';
|
||||
|
||||
const SpottableComponent = Spottable('div');
|
||||
@@ -11,13 +12,24 @@ const SpottableComponent = Spottable('div');
|
||||
const ShowUserReviews = ({ hasVideo, launchedFromPlayer, onFocus, onBlur }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// v2 API의 Redux 상태 사용
|
||||
const reviewListData = useSelector(
|
||||
(state) => state.product.reviewData && state.product.reviewData.reviewList
|
||||
(state) => state.product.reviewListData && state.product.reviewListData.reviewList
|
||||
);
|
||||
const reviewData = useSelector((state) => state.product.reviewData || {});
|
||||
const reviewData = useSelector((state) => state.product.reviewListData || {});
|
||||
const productData = useSelector((state) => state.main.productData || {});
|
||||
|
||||
// useReviews Hook에서 필요한 정보 추가 확인
|
||||
const { stats } = useReviews(productData && productData.prdtId);
|
||||
|
||||
const handleShowUserReviewsClick = useCallback(() => {
|
||||
console.log('[ShowUserReviews] 🎯 Show User Reviews 버튼 클릭:', {
|
||||
reviewVersion: REVIEW_VERSION,
|
||||
reviewListDataLength: reviewListData?.length || 0,
|
||||
statsAvgRating: stats?.averageRating,
|
||||
statsTotalReviews: stats?.totalReviews
|
||||
});
|
||||
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.USER_REVIEW_PANEL,
|
||||
@@ -30,8 +42,8 @@ const ShowUserReviews = ({ hasVideo, launchedFromPlayer, onFocus, onBlur }) => {
|
||||
'https://placehold.co/150x150',
|
||||
brandLogo: productData.patncLogoPath || 'https://placehold.co/50x50',
|
||||
productName: productData.prdtNm || '상품명 정보가 없습니다',
|
||||
avgRating: reviewData.reviewDetail?.avgRvwScr || 5,
|
||||
reviewCount: reviewData.reviewDetail?.totRvwCnt || reviewListData?.length || 0,
|
||||
avgRating: stats?.averageRating || 5,
|
||||
reviewCount: stats?.totalReviews || reviewListData?.length || 0,
|
||||
bgVideoInfo: {
|
||||
hasVideo: hasVideo,
|
||||
launchedFromPlayer: launchedFromPlayer ?? false,
|
||||
@@ -39,7 +51,7 @@ const ShowUserReviews = ({ hasVideo, launchedFromPlayer, onFocus, onBlur }) => {
|
||||
},
|
||||
})
|
||||
);
|
||||
}, [dispatch, productData, reviewData, reviewListData, hasVideo, launchedFromPlayer]);
|
||||
}, [dispatch, productData, reviewData, reviewListData, stats, hasVideo, launchedFromPlayer]);
|
||||
|
||||
const handleKeyDown = useCallback((event) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
@@ -74,19 +86,31 @@ const ShowUserReviews = ({ hasVideo, launchedFromPlayer, onFocus, onBlur }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[ShowUserReviews] Component mounted/updated', {
|
||||
console.log('[ShowUserReviews_v2] 컴포넌트 마운트/업데이트', {
|
||||
hasReviewData: !!reviewListData,
|
||||
reviewCount: reviewListData?.length || 0,
|
||||
statsTotalReviews: stats?.totalReviews,
|
||||
shouldShowButton: reviewListData && reviewListData.length > 5,
|
||||
spotlightUpTarget: 'user-review-at-last',
|
||||
reviewVersion: REVIEW_VERSION
|
||||
});
|
||||
}, [reviewListData]);
|
||||
}, [reviewListData, stats]);
|
||||
|
||||
// v2 API 데이터로 렌더링 조건 수정: 5개 이상일 때만 보이기
|
||||
if (!reviewListData || reviewListData.length <= 5) {
|
||||
console.log('[ShowUserReviews_v2] ❌ 버튼 숨김:', {
|
||||
reason: !reviewListData ? 'reviewListData가 없음' : 'reviewListData.length <= 5',
|
||||
reviewListLength: reviewListData?.length || 0
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={css.showUserReviews__wrapper}>
|
||||
{console.log('[ShowUserReviews_v2] ✅ 버튼 렌더링 시작:', {
|
||||
reviewListLength: reviewListData?.length,
|
||||
version: REVIEW_VERSION
|
||||
})}
|
||||
<SpottableComponent
|
||||
className={css.showUserReviews__button}
|
||||
onClick={handleShowUserReviewsClick}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useDispatch } from 'react-redux';
|
||||
import { popPanel, updatePanel } from '../../actions/panelActions';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
import useReviews from '../../hooks/useReviews/useReviews';
|
||||
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';
|
||||
@@ -15,6 +15,10 @@ import UserReviewsList from './components/UserReviewsList';
|
||||
import UserReviewHeader from './UserReviewHeader';
|
||||
import css from './UserReviewPanel.module.less';
|
||||
|
||||
// 버전에 따른 UI 설정
|
||||
const VERSION_LABEL = REVIEW_VERSION === 1 ? '[v1 - 기존 API]' : '[v2 - 신 API]';
|
||||
console.log('[UserReviewPanel] 🔧 REVIEW_VERSION:', REVIEW_VERSION, VERSION_LABEL);
|
||||
|
||||
const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -36,7 +40,8 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
currentFilter,
|
||||
filterCounts,
|
||||
stats,
|
||||
} = useReviews(prdtId, patnrId);
|
||||
_debug,
|
||||
} = useReviews(prdtId, patnrId); // REVIEW_VERSION 상수에 따라 자동으로 API 선택
|
||||
|
||||
const [isPaging, setIsPaging] = useState(false);
|
||||
|
||||
@@ -129,6 +134,24 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
[handleSentimentFilter]
|
||||
);
|
||||
|
||||
// UserReviewPanel 데이터 상태 로깅
|
||||
useEffect(() => {
|
||||
if (_debug) {
|
||||
console.log(`[UserReviewPanel] 📊 데이터 상태 ${VERSION_LABEL}:`, {
|
||||
reviewVersion: _debug.reviewVersion,
|
||||
versionLabel: VERSION_LABEL,
|
||||
totalReviews: stats.totalReviews,
|
||||
filteredCount: stats.filteredCount,
|
||||
currentFilter: currentFilter,
|
||||
userReviewPanelPage,
|
||||
userReviewPanelReviewsCount: userReviewPanelReviews?.length || 0,
|
||||
hasNext: userReviewPanelHasNext,
|
||||
hasPrev: userReviewPanelHasPrev,
|
||||
totalPages: userReviewPanelTotalPages,
|
||||
});
|
||||
}
|
||||
}, [_debug, stats, currentFilter, userReviewPanelPage, userReviewPanelReviews, userReviewPanelHasNext, userReviewPanelHasPrev, userReviewPanelTotalPages]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearAllFilters();
|
||||
@@ -168,7 +191,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
isTabActivated={false}
|
||||
>
|
||||
<UserReviewHeader
|
||||
title="User Reviews"
|
||||
title={`User Reviews ${VERSION_LABEL}`}
|
||||
onBackButton={handleBackButton}
|
||||
onClick={handleBackButton}
|
||||
className={css.header}
|
||||
|
||||
Reference in New Issue
Block a user