[251104] fix: UserReviewsPanel Review Filters-2
🕐 커밋 시간: 2025. 11. 04. 12:27:44 📊 변경 통계: • 총 파일: 7개 • 추가: +155줄 • 삭제: -43줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx ~ com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx ~ com.twin.app.shoptime/src/views/UserReview/components/FilterItemButton.module.less ~ com.twin.app.shoptime/src/views/UserReview/components/UserReviewItem.jsx ~ com.twin.app.shoptime/src/views/UserReview/components/UserReviewsList.jsx 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선 • 중간 규모 기능 개선 • 모듈 구조 개선
This commit is contained in:
@@ -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]);
|
||||
|
||||
|
||||
@@ -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);
|
||||
//하단부분까지 갔을때 체크용
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 (
|
||||
<TPanel
|
||||
className={classNames(css.userReviewPanel, className)}
|
||||
@@ -427,7 +472,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
</div>
|
||||
<div className={css.reviewsSection__filters__group}>
|
||||
<FilterItemButton
|
||||
text={`All star(${ratingFilterData['ALL'] || reviewCount || 0})`}
|
||||
text={`All (${ratingFilterData['ALL'] || reviewCount || 0})`}
|
||||
onClick={handleAllStarsFilter}
|
||||
spotlightId="filter-all-stars"
|
||||
ariaLabel="Filter by all star ratings"
|
||||
@@ -564,27 +609,31 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
) : (
|
||||
// 폴백: API 데이터 없을 경우 기존 하드코딩된 버튼 표시
|
||||
<>
|
||||
<FilterItemButton
|
||||
text={`Positive (${filterCounts?.sentiment?.positive || 0})`}
|
||||
onClick={handlePositiveClick}
|
||||
spotlightId="filter-positive"
|
||||
ariaLabel="Filter by positive sentiment"
|
||||
dataSpotlightUp="filter-quality"
|
||||
dataSpotlightDown="filter-negative"
|
||||
isActive={
|
||||
currentFilter.type === 'sentiment' && currentFilter.value === 'positive'
|
||||
}
|
||||
/>
|
||||
<FilterItemButton
|
||||
text={`Negative (${filterCounts?.sentiment?.negative || 0})`}
|
||||
onClick={handleNegativeClick}
|
||||
spotlightId="filter-negative"
|
||||
ariaLabel="Filter by negative sentiment"
|
||||
dataSpotlightUp="filter-positive"
|
||||
isActive={
|
||||
currentFilter.type === 'sentiment' && currentFilter.value === 'negative'
|
||||
}
|
||||
/>
|
||||
{(filterCounts?.sentiment?.positive || 0) > 0 && (
|
||||
<FilterItemButton
|
||||
text={`Positive (${filterCounts?.sentiment?.positive || 0})`}
|
||||
onClick={handlePositiveClick}
|
||||
spotlightId="filter-positive"
|
||||
ariaLabel="Filter by positive sentiment"
|
||||
dataSpotlightUp="filter-quality"
|
||||
dataSpotlightDown="filter-negative"
|
||||
isActive={
|
||||
currentFilter.type === 'sentiment' && currentFilter.value === 'positive'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{(filterCounts?.sentiment?.negative || 0) > 0 && (
|
||||
<FilterItemButton
|
||||
text={`Negative (${filterCounts?.sentiment?.negative || 0})`}
|
||||
onClick={handleNegativeClick}
|
||||
spotlightId="filter-negative"
|
||||
ariaLabel="Filter by negative sentiment"
|
||||
dataSpotlightUp={filterCounts?.sentiment?.positive > 0 ? "filter-positive" : "filter-quality"}
|
||||
isActive={
|
||||
currentFilter.type === 'sentiment' && currentFilter.value === 'negative'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TBody>
|
||||
|
||||
{/* UserReviewsPopup 추가 - ProductAllSection과 동일한 방식 */}
|
||||
<UserReviewsPopup
|
||||
open={isPopupOpen}
|
||||
onClose={handleClosePopup}
|
||||
mode={popupMode}
|
||||
images={extractImagesFromReviews}
|
||||
selectedImageIndex={selectedImageIndex}
|
||||
reviewsWithImages={getReviewsWithImages}
|
||||
allReviews={allReviews}
|
||||
onModeChange={handleModeChange}
|
||||
/>
|
||||
</TPanel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (필터링된 리뷰 개수) 사용
|
||||
|
||||
Reference in New Issue
Block a user