import { useState, useMemo, useEffect, useCallback } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { getUserReviewList, getReviewFilters, clearReviewFilter } from '../../actions/productActions'; import fp from '../../utils/fp'; const DISPLAY_SIZE = 3; // 화면에 표시할 리뷰 개수 const STEP_SIZE = 1; // 페이징 시 이동할 리뷰 개수 // 🔧 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 상태에서 리뷰 데이터 가져오기 - 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; }); // Redux 상태에서 필터 데이터 가져오기 (IF-LGSP-100) const reviewFiltersData = useSelector((state) => { const data = state.product.reviewFiltersData; // console.log('[useReviews_reviewFilters] 📥 Redux reviewFiltersData 선택:', { // dataExists: !!data, // dataKeys: data ? Object.keys(data) : 'null', // filtersLength: data?.filters?.length || 0, // filters: data?.filters // }); return data; }); const loadedFiltersPrdtId = useSelector((state) => state.product.loadedFiltersPrdtId); // Redux 상태에서 필터링된 리뷰 데이터 가져오기 const filteredReviewListData = useSelector((state) => state.product.filteredReviewListData); const currentReviewFilter = useSelector((state) => state.product.currentReviewFilter); // console.log('[useReviews_filteredReviewList] 📥 Redux filteredReviewListData 선택:', { // filteredReviewListDataExists: !!filteredReviewListData, // filteredReviewListDataKeys: filteredReviewListData ? Object.keys(filteredReviewListData) : 'null', // reviewListLength: filteredReviewListData?.reviewList?.length || 0, // currentReviewFilter // }); // 활성 리뷰 데이터 결정 (필터링된 데이터가 있으면 사용, 없으면 ALL 데이터 사용) const activeReviewData = useMemo(() => { if (filteredReviewListData) { // console.log('[useReviews] 🟢 activeReviewData: filteredReviewListData 사용'); return filteredReviewListData; } // console.log('[useReviews] 🟢 activeReviewData: reviewListData (ALL) 사용'); return reviewData; }, [filteredReviewListData, reviewData]); // 빈 내용 리뷰 필터링 - 의미있는 리뷰만 표시 const allReviews = (activeReviewData?.reviewList || []).filter((review) => { const content = review.rvwCtnt?.trim(); return content && content.length > 0; }); const reviewDetail = reviewData?.reviewDetail || {}; // [useReviews] Redux 상태 확인 로그 // console.log('[useReviews] Redux 상태 확인:', { // prdtId, // patnrId, // hasReviewData: !!reviewData, // reviewDataKeys: reviewData ? Object.keys(reviewData) : [], // reviewListLength: (reviewData && reviewData.reviewList) ? reviewData.reviewList.length : 0, // reviewDetail: reviewData ? reviewData.reviewDetail : null, // fullReviewData: reviewData // }); // 로컬 상태 관리 const [currentPage, setCurrentPage] = useState(0); const [currentFilter, setCurrentFilter] = useState({ type: 'rating', // 'rating', 'keyword', 'sentiment' value: 'all', // 'all', 1-5, 'positive', 'negative', 'aroma' 등 }); const [isLoading, setIsLoading] = useState(false); 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); // 리뷰 데이터 로드 함수 - reviewVersion에 따라 API 선택 const loadReviews = useCallback(async () => { if (!prdtId) { // console.warn('[useReviews] loadReviews 호출되었지만 prdtId가 없음'); return; } if (!patnrId) { // console.warn('[useReviews] loadReviews 호출되었지만 patnrId가 없음'); return; } setIsLoading(true); // console.log('[useReviews] 📋 loadReviews 호출됨:', { // prdtId, // patnrId, // reviewVersion, // willCallApi: reviewVersion === 1 ? 'getUserReviews (v1)' : 'getUserReviewList (v2)' // }); try { // 신 API 호출 (v2) // console.log('[useReviews] 🔄 getUserReviewList 호출 중... (v2)'); await dispatch(getUserReviewList({ prdtId, patnrId, filterTpCd: 'ALL', pageSize: 100, pageNo: 1 })); // IF-LGSP-100 필터 데이터 조회 // console.log('[useReviews] 🔄 getReviewFilters 호출 중... (IF-LGSP-100)'); await dispatch(getReviewFilters({ prdtId, patnrId })); setHasLoadedData(true); } catch (error) { console.error('[useReviews] loadReviews 실패:', error); } finally { setIsLoading(false); } }, [prdtId, patnrId, reviewVersion, dispatch]); // prdtId가 변경되면 자동으로 리뷰 데이터 로드 (싱글톤 패턴) // 중요: reviewVersion이 다르면 다른 Redux 상태를 사용하므로 reviewVersion도 체크해야 함 // 우선순위 3: 필터 캐싱 로직 완성 useEffect(() => { if (prdtId && patnrId) { const needsReviewLoad = prdtId !== loadedPrdtId; const needsFiltersLoad = prdtId !== loadedFiltersPrdtId; // console.log('[useReviews] 🔄 로드 필요 여부 체크 (캐싱 로직 포함):', { // prdtId, // patnrId, // loadedPrdtId, // loadedFiltersPrdtId, // reviewVersion, // needsReviewLoad, // needsFiltersLoad, // reason: (needsReviewLoad || needsFiltersLoad) ? 'prdtId가 변경됨' : '캐시된 데이터 사용' // }); if (needsReviewLoad || needsFiltersLoad) { // prdtId 변경 시 로딩 상태 즉시 설정으로 UI 깜빡임 방지 setIsLoading(true); setHasLoadedData(false); loadReviews(); } else { // 캐시된 데이터 사용 setHasLoadedData(true); } } }, [prdtId, patnrId, loadedPrdtId, loadedFiltersPrdtId, loadReviews]); // ✅ CRITICAL FIX: prdtId가 변경되면 Redux의 필터 상태를 초기화 // 이전 상품의 필터가 새로운 상품에 잘못 적용되는 문제를 해결 useEffect(() => { if (prdtId && currentReviewFilter) { const hasPrdtIdChanged = prdtId !== loadedPrdtId; if (hasPrdtIdChanged) { console.log('[useReviews] 🔄 prdtId 변경 감지 - 필터 초기화:', { previousPrdtId: loadedPrdtId, newPrdtId: prdtId, clearedFilter: currentReviewFilter, reason: '새로운 상품으로 전환되었으므로 이전 필터 제거' }); // Redux의 필터 상태 초기화 dispatch(clearReviewFilter()); } } }, [prdtId, loadedPrdtId, currentReviewFilter, dispatch]); // 리뷰 데이터가 로드되면 로딩 상태 업데이트 useEffect(() => { if (allReviews.length > 0 && isLoading) { setIsLoading(false); setHasLoadedData(true); } }, [allReviews.length, isLoading]); // keyword matching function const matchesKeyword = useCallback((review, keyword) => { if (!keyword) return true; const content = review.rvwCtnt ? review.rvwCtnt.toLowerCase() : ''; return content.includes(keyword.toLowerCase()); }, []); // Senntiment matching function const matchesSentiment = useCallback((review, sentiment) => { if (!sentiment) return true; const positiveWords = new Set([ 'good', 'great', 'excellent', 'amazing', 'love', 'perfect', 'best', 'wonderful', 'fantastic', 'awesome', 'outstanding', 'superb', 'brilliant', 'positive', 'happy', 'satisfied', 'pleased', 'delightful', 'enjoyed', 'recommend', 'nice', 'helpful', 'valuable', 'impressive', 'remarkable', 'favorite', 'friendly', 'comfortable', 'smooth', 'reliable', 'incredible', 'lovely', 'beautiful', 'fun', 'worthwhile', 'useful', ]); const negativeWords = new Set([ 'bad', 'terrible', 'awful', 'hate', 'worst', 'horrible', 'poor', 'disappointing', 'useless', 'waste', 'cheap', 'broken', 'defective', 'unhappy', 'unsatisfied', 'frustrating', 'annoying', 'regret', 'slow', 'ugly', 'overpriced', 'boring', 'uncomfortable', 'noisy', 'buggy', 'dirty', 'smelly', 'difficult', 'complicated', 'fake', 'flimsy', ]); const negationWords = new Set([ 'not', 'no', 'never', "isn't", "wasn't", "don't", "doesn't", "didn't", ]); const content = (review.rvwCtnt || '').toLowerCase(); if (!content) return false; const tokens = content .replace(/[^a-z0-9\s'-]/g, ' ') .split(/\s+/) .filter(Boolean); const hasWordWithoutNegation = (wordSet) => { return tokens.some((token, index) => { if (!wordSet.has(token)) return false; const prevTokens = tokens.slice(Math.max(0, index - 3), index); return !prevTokens.some((prevToken) => negationWords.has(prevToken)); }); }; if (sentiment === 'positive') { return hasWordWithoutNegation(positiveWords); } if (sentiment === 'negative') { return hasWordWithoutNegation(negativeWords); } return true; }, []); // 필터 카운트 계산 (전체 리뷰 데이터 기반) const filterCounts = useMemo(() => { if (allReviews.length === 0) { return { rating: { all: 0 }, keyword: {}, sentiment: {}, }; } // 별점이 없는 리뷰들 찾기 const noStarReviews = allReviews.filter((review) => { const rating = review.rvwScr || review.rvwRtng || review.rating; return !rating || rating === 0; }); // 별점이 없는 리뷰들 로그 출력 // if (noStarReviews.length > 0) { // console.log('[UserReviews]-NoStar 별점 없는 리뷰들:', { // totalReviews: allReviews.length, // noStarCount: noStarReviews.length, // noStarReviews: noStarReviews.map((review) => ({ // rvwId: review.rvwId, // rvwScr: review.rvwScr, // rvwRtng: review.rvwRtng, // rating: review.rating, // rvwCtnt: review.rvwCtnt?.substring(0, 50) + '...', // wrtrNknm: review.wrtrNknm, // })), // }); // } // 별점이 있는 리뷰만 카운트 (1~5점) let ratingSum = 0; for (let i = 1; i <= 5; i++) { const count = allReviews.filter((review) => { const rating = review.rvwScr || review.rvwRtng || review.rating || 0; return Math.round(rating) === i; }).length; ratingSum += count; } const counts = { rating: { all: ratingSum }, // 별점이 있는 리뷰만 카운트 keyword: {}, sentiment: {}, }; // 별점별 카운트 - 소수점 별점을 정수로 반올림하여 카운팅 for (let i = 1; i <= 5; i++) { counts.rating[i] = allReviews.filter((review) => { const rating = review.rvwScr || review.rvwRtng || review.rating || 0; return Math.round(rating) === i; }).length; } // 키워드별 카운트 const keywords = ['aroma', 'vanilla', 'cinnamon', 'quality']; keywords.forEach((keyword) => { counts.keyword[keyword] = allReviews.filter((review) => matchesKeyword(review, keyword) ).length; }); // 감정별 카운트 counts.sentiment.positive = allReviews.filter((review) => matchesSentiment(review, 'positive') ).length; counts.sentiment.negative = allReviews.filter((review) => matchesSentiment(review, 'negative') ).length; // 디버깅: filterCounts 계산 결과 확인 // console.log('[useReviews] filterCounts 계산 완료:', { // totalReviews: allReviews.length, // ratingCounts: counts.rating, // keywordCounts: counts.keyword, // sentimentCounts: counts.sentiment, // sampleReviews: allReviews.slice(0, 3).map(review => ({ // rvwId: review.rvwId, // rvwScr: review.rvwScr, // rvwRtng: review.rvwRtng, // rating: review.rating, // roundedRating: Math.round(review.rvwScr || review.rvwRtng || review.rating || 0) // })) // }); return counts; }, [allReviews, matchesKeyword, matchesSentiment]); // 필터링된 리뷰 계산 (Single Filter 구조) const filteredReviews = useMemo(() => { if (allReviews.length === 0) return []; // 이전 결과 명시적 해제를 위한 새로운 배열 생성 let result = null; switch (currentFilter.type) { case 'rating': if (currentFilter.value === 'all' || currentFilter.value === null) { // 'all' 필터: 별점이 있는 리뷰만 표시 (별점 없는 리뷰 제외) result = allReviews.filter((review) => { const rating = review.rvwScr || review.rvwRtng || review.rating; return rating && rating > 0; }); } else { result = allReviews.filter((review) => { const rating = review.rvwScr || review.rvwRtng || review.rating || 0; return Math.round(rating) === currentFilter.value; }); } break; case 'sentiment': result = allReviews.filter((review) => matchesSentiment(review, currentFilter.value)); break; case 'keyword': result = allReviews.filter((review) => matchesKeyword(review, currentFilter.value)); break; default: result = [...allReviews]; } // 불변성 보장 및 메모리 최적화 return Object.freeze(result); }, [allReviews, currentFilter.type, currentFilter.value, matchesKeyword, matchesSentiment]); // 현재 화면에 표시할 리뷰들 (항상 3개로 고정) - 기존 컴포넌트용 const displayReviews = useMemo(() => { return filteredReviews.slice(0, 3); }, [filteredReviews]); // UserReviewPanel 전용 페이징된 리뷰들 (3개 표시, 1개씩 이동) const userReviewPanelReviews = useMemo(() => { const startIndex = userReviewPanelPage * STEP_SIZE; const endIndex = startIndex + DISPLAY_SIZE; return filteredReviews.slice(startIndex, endIndex); }, [filteredReviews, userReviewPanelPage]); // 더 로드할 리뷰가 있는지 확인 (기존 로직) const hasMore = displayReviews.length < filteredReviews.length; // UserReviewPanel 전용 페이징 상태들 const userReviewPanelHasNext = fp.pipe( () => (userReviewPanelPage + 1) * STEP_SIZE + DISPLAY_SIZE - 1, (lastIndex) => lastIndex < filteredReviews.length )(); const userReviewPanelHasPrev = userReviewPanelPage > 0; const userReviewPanelTotalPages = fp.pipe( () => Math.max(0, filteredReviews.length - DISPLAY_SIZE + 1), (maxStartIndex) => Math.ceil(maxStartIndex / STEP_SIZE) )(); // 다음 청크 로드 (클라이언트 사이드에서 페이지만 증가) const loadMore = useCallback(() => { if (hasMore) { // console.log('[useReviews] loadMore: 다음 청크 로드', { // currentPage, // nextPage: currentPage + 1, // currentDisplayCount: displayReviews.length, // totalFilteredCount: filteredReviews.length // }); setCurrentPage((prev) => prev + 1); } }, [hasMore, currentPage, displayReviews.length, filteredReviews.length]); // UserReviewPanel 전용 페이징 함수들 const goToNextUserReviewPage = useCallback(() => { if (userReviewPanelHasNext) { // console.log('[useReviews] UserReviewPanel 다음 페이지:', { // currentPage: userReviewPanelPage, // nextPage: userReviewPanelPage + 1, // totalPages: userReviewPanelTotalPages, // filteredCount: filteredReviews.length // }); setUserReviewPanelPage(fp.pipe((prev) => prev + 1)); } }, [ userReviewPanelHasNext, userReviewPanelPage, userReviewPanelTotalPages, filteredReviews.length, ]); const goToPrevUserReviewPage = useCallback(() => { if (userReviewPanelHasPrev) { // console.log('[useReviews] UserReviewPanel 이전 페이지:', { // currentPage: userReviewPanelPage, // prevPage: userReviewPanelPage - 1, // totalPages: userReviewPanelTotalPages, // filteredCount: filteredReviews.length // }); setUserReviewPanelPage(fp.pipe((prev) => prev - 1)); } }, [ userReviewPanelHasPrev, userReviewPanelPage, userReviewPanelTotalPages, filteredReviews.length, ]); // 필터 변경 시 UserReviewPanel 페이지도 초기화 // ✅ 클라이언트 필터 + API 필터 모두 감시 useEffect(() => { console.log('[useReviews] 필터 변경 감지 - userReviewPanelPage 초기화:', { filterType: currentFilter.type, filterValue: currentFilter.value, apiFilter: currentReviewFilter, }); setUserReviewPanelPage(0); }, [currentFilter.type, currentFilter.value, currentReviewFilter]); // Single Filter 적용 함수 const applyFilter = useCallback((type, value) => { // console.log('[useReviews] 필터 적용:', { type, value }); // 이전 필터 결과 해제 setCurrentFilter({ type, value }); setCurrentPage(0); // 필터 변경 시 첫 페이지로 }, []); // 편의 함수들 (기존 인터페이스 호환성) const applyRatingFilter = useCallback( (rating) => { // console.log('[useReviews] applyRatingFilter called with:', rating); applyFilter('rating', rating); }, [applyFilter] ); const applySentimentFilter = useCallback( (sentiment) => { applyFilter('sentiment', sentiment); }, [applyFilter] ); const applyKeywordFilter = useCallback( (keyword) => { applyFilter('keyword', keyword); }, [applyFilter] ); // 필터 초기화 함수 const clearAllFilters = useCallback(() => { // console.log('[useReviews] 모든 필터 초기화'); applyFilter('rating', 'all'); }, [applyFilter]); // 이미지가 있는 리뷰들만 추려내는 함수 const getReviewsWithImages = useMemo(() => { const reviewsWithImages = allReviews.filter( (review) => review.reviewImageList && Array.isArray(review.reviewImageList) && review.reviewImageList.length > 0 ); // console.log('[useReviews] 이미지가 있는 리뷰 필터링:', { // totalReviews: allReviews.length, // reviewsWithImages: reviewsWithImages.length, // imageReviews: reviewsWithImages.slice(0, 3) // 처음 3개만 로그 // }); return reviewsWithImages; }, [allReviews]); // 이미지 데이터 추출 함수 - CustomerImages에서 사용할 수 있도록 const extractImagesFromReviews = useMemo(() => { const images = []; getReviewsWithImages.forEach((review, reviewIndex) => { if (review.reviewImageList && Array.isArray(review.reviewImageList)) { review.reviewImageList.forEach((imgItem, imgIndex) => { const { imgId, imgUrl, imgSeq } = imgItem; if (imgUrl && imgUrl.trim() !== '') { images.push({ imgId: imgId || `img-${reviewIndex}-${imgIndex}`, imgUrl, imgSeq: imgSeq || imgIndex + 1, reviewId: review.rvwId, reviewData: review, // 전체 리뷰 데이터도 포함 }); } }); } }); // console.log('[useReviews] 이미지 데이터 추출 완료:', { // totalImages: images.length, // sampleImages: images.slice(0, 3) // }); return images; }, [getReviewsWithImages]); // DetailPanel용 - 처음 5개만 가져오기 (필터링 안된 원본에서) const previewReviews = useMemo(() => { // console.log('[useReviews] previewReviews 계산:', { // allReviewsLength: allReviews.length, // previewCount: Math.min(allReviews.length, 5), // prdtId, // hasLoadedData, // isLoading, // reviewData, // allReviews: allReviews.slice(0, 3) // }); return allReviews.slice(0, 5); }, [allReviews, prdtId, hasLoadedData, isLoading, reviewData]); // 통계 정보 // ✅ 핵심: 필터링 여부에 따라 다른 데이터 사용 const stats = useMemo(() => { // 필터링 활성 여부 확인 const isFilterActive = !!filteredReviewListData && !!currentReviewFilter; // 초기 로드된 리뷰 데이터의 reviewDetail (ALL 필터 기반) const initialReviewDetail = reviewData?.reviewDetail || {}; // 필터링된 리뷰 데이터의 reviewDetail (필터 적용 후) const filteredReviewDetail = filteredReviewListData?.reviewDetail || {}; // ⭐ 사용할 reviewDetail 선택 // - 필터 미적용: initialReviewDetail (전체 리뷰 기반) // - 필터 적용: filteredReviewDetail (필터링된 리뷰 기반) const activeReviewDetail = isFilterActive ? filteredReviewDetail : initialReviewDetail; const totalReviewsCount = activeReviewDetail?.totRvwCnt || allReviews.length; const averageRating = activeReviewDetail?.totRvwAvg || 0; const result = { // ✅ 필터 미적용: 전체 리뷰 개수 // ✅ 필터 적용: 필터링된 리뷰 개수 (API의 totRvwCnt) totalReviews: totalReviewsCount, filteredCount: filteredReviews.length, displayedCount: displayReviews.length, // ✅ 필터 미적용: 전체 평균 평점 // ✅ 필터 적용: 필터링된 리뷰의 평균 평점 averageRating: averageRating, totalRatingCount: totalReviewsCount, // 🔍 디버깅용: 필터링 상태 _isFilterActive: isFilterActive, _activeSource: isFilterActive ? 'filteredReviewDetail' : 'initialReviewDetail' }; // console.log('[useReviews_useReviewList] 📊 stats 계산:', { // isFilterActive, // totalReviewsCount, // averageRating, // activeSource: isFilterActive ? 'filteredReviewDetail' : 'initialReviewDetail', // initialReviewDetail: { // totRvwCnt: initialReviewDetail.totRvwCnt, // totRvwAvg: initialReviewDetail.totRvwAvg // }, // filteredReviewDetail: { // totRvwCnt: filteredReviewDetail.totRvwCnt, // totRvwAvg: filteredReviewDetail.totRvwAvg // }, // allReviewsLength: allReviews.length, // prdtId // }); return result; }, [allReviews.length, filteredReviews.length, displayReviews.length, reviewData, filteredReviewListData, currentReviewFilter, reviewDetail, hasLoadedData, isLoading, isCurrentProductLoaded, reviewVersion, loadedPrdtId, prdtId]); // 데이터 새로고침 - 강제로 다시 로드 const refreshData = useCallback(() => { // console.log('[useReviews] 데이터 새로고침 시작'); setHasLoadedData(false); // 강제로 다시 로드하도록 setCurrentPage(0); setCurrentFilter({ type: 'rating', value: 'all' }); // 기본 필터로 초기화 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 기반) refreshData, // 데이터 강제 새로고침 // 📊 리뷰 데이터 displayReviews, // 현재 화면에 표시할 리뷰들 (청킹된) - 기존 컴포넌트용 previewReviews, // DetailPanel용 미리보기 리뷰 (첫 5개) allReviews, // 전체 원본 리뷰 (필터링 안된, 빈 내용 제외) filteredReviews, // 필터링된 전체 리뷰 hasReviews, // 리뷰 존재 여부 (현재 제품이 로드된 경우에만 true) // 🔄 UserReviewPanel 전용 페이징 데이터 userReviewPanelReviews, // UserReviewPanel용 페이징된 리뷰들 (3개 표시, 1개씩 이동) userReviewPanelPage, // 현재 페이지 번호 (0부터 시작) userReviewPanelHasNext, // 다음 페이지 존재 여부 userReviewPanelHasPrev, // 이전 페이지 존재 여부 userReviewPanelTotalPages, // 전체 페이지 수 goToNextUserReviewPage, // 다음 페이지로 이동 goToPrevUserReviewPage, // 이전 페이지로 이동 // 🖼️ 이미지 관련 데이터 - CustomerImages 전용 getReviewsWithImages, // 이미지가 있는 리뷰들만 필터링 extractImagesFromReviews, // 이미지 데이터만 추출 (reviewData 포함) // 📄 클라이언트 사이드 페이지네이션 hasMore, // 더 로드할 리뷰가 있는지 loadMore, // 다음 청크 표시 (클라이언트에서 슬라이싱) currentPage, // 현재 페이지 (0부터 시작) // 🔍 필터링 시스템 currentFilter, // 현재 활성화된 필터 { type, value } filterCounts, // 각 필터별 리뷰 개수 (실시간 계산) applyFilter, // 통합 필터 적용 함수 applyRatingFilter, // 별점 필터 적용 applyKeywordFilter, // 키워드 필터 적용 applySentimentFilter, // 감정 필터 적용 clearAllFilters, // 모든 필터 초기화 // ⚡ 상태 관리 isLoading, // API 로딩 상태 hasLoadedData, // 데이터 로드 완료 여부 stats, // 통계 정보 // 🔤 필터 옵션 데이터 (IF-LGSP-100) reviewFiltersData, // 필터 옵션 전체 데이터 filters: reviewFiltersData?.filters || [], // 필터 배열 (RATING, KEYWORDS, SENTIMENT) // 🎯 필터링된 리뷰 데이터 (API 기반 필터링) filteredReviewListData, // API에서 받은 필터링된 리뷰 데이터 currentReviewFilter, // 현재 활성화된 필터 { filterTpCd, filterTpVal } activeReviewData, // 활성 리뷰 데이터 (filteredReviewListData 또는 reviewListData) // 🐛 디버그 정보 _debug: { prdtId, patnrId, reviewVersion, allReviews: allReviews.slice(0, 3), // 처음 3개만 currentFilter, filteredCount: filteredReviews.length, displayedCount: displayReviews.length, currentPage, hasMore, isLoading, hasLoadedData, reviewFiltersData, filteredReviewListData, currentReviewFilter, activeReviewData, }, }; }; export default useReviews;