[251011] fix: MediaPlayer작업
🕐 커밋 시간: 2025. 10. 11. 22:30:02 📊 변경 통계: • 총 파일: 5개 • 추가: +169줄 • 삭제: -161줄 📁 추가된 파일: + com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.backup.jsx 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/productActions.js ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx (javascript): 🔄 Modified: Spottable() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선
This commit is contained in:
@@ -1,37 +1,37 @@
|
||||
import { URLS } from "../api/apiConfig";
|
||||
import { TAxios } from "../api/TAxios";
|
||||
import { types } from "./actionTypes";
|
||||
import { changeAppStatus } from "./commonActions";
|
||||
import { reduce, set, get } from "../utils/fp";
|
||||
import { URLS } from '../api/apiConfig';
|
||||
import { TAxios } from '../api/TAxios';
|
||||
import { types } from './actionTypes';
|
||||
import { changeAppStatus } from './commonActions';
|
||||
import { reduce, set, get } from '../utils/fp';
|
||||
|
||||
// CustomerImages용 리뷰 이미지 import
|
||||
import reviewSampleImage from "../../assets/images/image-review-sample-1.png";
|
||||
import reviewSampleImage from '../../assets/images/image-review-sample-1.png';
|
||||
|
||||
// Best Seller 상품 목록 조회 IF-LGSP-303
|
||||
// FP helpers
|
||||
const pickParams = (keys) => (src) =>
|
||||
reduce(
|
||||
(acc, key) =>
|
||||
src && src[key] !== null && src[key] !== undefined
|
||||
? set(key, src[key], acc)
|
||||
: acc,
|
||||
src && src[key] !== null && src[key] !== undefined ? set(key, src[key], acc) : acc,
|
||||
{},
|
||||
keys
|
||||
);
|
||||
|
||||
// Generic request thunk factory
|
||||
const createRequestThunk = ({
|
||||
method,
|
||||
url,
|
||||
type,
|
||||
params = () => ({}),
|
||||
data = () => ({}),
|
||||
tag,
|
||||
onSuccessExtra = () => (dispatch, getState, response) => {},
|
||||
onFailExtra = () => (dispatch, getState, error) => {},
|
||||
selectPayload = (response) => get("data.data", response),
|
||||
}) =>
|
||||
(props) => (dispatch, getState) => {
|
||||
const createRequestThunk =
|
||||
({
|
||||
method,
|
||||
url,
|
||||
type,
|
||||
params = () => ({}),
|
||||
data = () => ({}),
|
||||
tag,
|
||||
onSuccessExtra = () => (dispatch, getState, response) => {},
|
||||
onFailExtra = () => (dispatch, getState, error) => {},
|
||||
selectPayload = (response) => get('data.data', response),
|
||||
}) =>
|
||||
(props) =>
|
||||
(dispatch, getState) => {
|
||||
const query = params(props);
|
||||
const body = data(props);
|
||||
|
||||
@@ -46,75 +46,57 @@ const createRequestThunk = ({
|
||||
onFailExtra(props, dispatch, getState, error);
|
||||
};
|
||||
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
method,
|
||||
url,
|
||||
query,
|
||||
body,
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
TAxios(dispatch, getState, method, url, query, body, onSuccess, onFail);
|
||||
};
|
||||
|
||||
// Specialization for GET
|
||||
const createGetThunk = ({ url, type, params = () => ({}), tag }) =>
|
||||
createRequestThunk({ method: "get", url, type, params, tag });
|
||||
createRequestThunk({ method: 'get', url, type, params, tag });
|
||||
|
||||
export const getBestSeller = (callback) => (dispatch, getState) => {
|
||||
const onSuccess = (response) => {
|
||||
console.log("getBestSeller onSuccess", response.data);
|
||||
dispatch({ type: types.GET_BEST_SELLER, payload: get("data.data", response) });
|
||||
console.log('getBestSeller onSuccess', response.data);
|
||||
dispatch({ type: types.GET_BEST_SELLER, payload: get('data.data', response) });
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
callback && callback();
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error("getBestSeller onFail", error);
|
||||
console.error('getBestSeller onFail', error);
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
callback && callback();
|
||||
};
|
||||
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
"get",
|
||||
URLS.GET_PRODUCT_BESTSELLER,
|
||||
{},
|
||||
{},
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
TAxios(dispatch, getState, 'get', URLS.GET_PRODUCT_BESTSELLER, {}, {}, onSuccess, onFail);
|
||||
};
|
||||
|
||||
// Detail 옵션상품 정보 조회 IF-LGSP-319
|
||||
export const getProductGroup = createGetThunk({
|
||||
url: URLS.GET_PRODUCT_GROUP,
|
||||
type: types.GET_PRODUCT_GROUP,
|
||||
params: pickParams(["patnrId", "prdtId"]),
|
||||
tag: "getProductGroup",
|
||||
params: pickParams(['patnrId', 'prdtId']),
|
||||
tag: 'getProductGroup',
|
||||
});
|
||||
|
||||
// Detail 옵션상품 정보 조회 IF-LGSP-320
|
||||
export const getProductOption = createGetThunk({
|
||||
url: URLS.GET_PRODUCT_OPTION,
|
||||
type: types.GET_PRODUCT_OPTION,
|
||||
params: pickParams(["patnrId", "prdtId"]),
|
||||
tag: "getProductOption",
|
||||
params: pickParams(['patnrId', 'prdtId']),
|
||||
tag: 'getProductOption',
|
||||
});
|
||||
|
||||
// FP: 실제 API 응답에서 data 부분만 추출 (간소화)
|
||||
const extractReviewApiData = (apiResponse) => {
|
||||
try {
|
||||
console.log("[UserReviews] 🔍 extractReviewApiData - 전체 API 응답 분석:", {
|
||||
fullApiResponse: apiResponse,
|
||||
hasData: !!(apiResponse && apiResponse.data),
|
||||
retCode: apiResponse && apiResponse.retCode,
|
||||
retMsg: apiResponse && apiResponse.retMsg,
|
||||
responseKeys: apiResponse ? Object.keys(apiResponse) : [],
|
||||
dataKeys: apiResponse && apiResponse.data ? Object.keys(apiResponse.data) : []
|
||||
});
|
||||
// console.log("[UserReviews] 🔍 extractReviewApiData - 전체 API 응답 분석:", {
|
||||
// fullApiResponse: apiResponse,
|
||||
// hasData: !!(apiResponse && apiResponse.data),
|
||||
// retCode: apiResponse && apiResponse.retCode,
|
||||
// retMsg: apiResponse && apiResponse.retMsg,
|
||||
// responseKeys: apiResponse ? Object.keys(apiResponse) : [],
|
||||
// dataKeys: apiResponse && apiResponse.data ? Object.keys(apiResponse.data) : []
|
||||
// });
|
||||
|
||||
// 여러 가능한 데이터 경로 시도
|
||||
let apiData = null;
|
||||
@@ -122,27 +104,31 @@ const extractReviewApiData = (apiResponse) => {
|
||||
// 1. response.data.data (중첩 구조)
|
||||
if (apiResponse && apiResponse.data && apiResponse.data.data) {
|
||||
apiData = apiResponse.data.data;
|
||||
console.log("[UserReviews] ✅ 데이터 경로 1: response.data.data 사용");
|
||||
// console.log("[UserReviews] ✅ 데이터 경로 1: response.data.data 사용");
|
||||
}
|
||||
// 2. response.data (단일 구조)
|
||||
else if (apiResponse && apiResponse.data && (apiResponse.data.reviewList || apiResponse.data.reviewDetail)) {
|
||||
else if (
|
||||
apiResponse &&
|
||||
apiResponse.data &&
|
||||
(apiResponse.data.reviewList || apiResponse.data.reviewDetail)
|
||||
) {
|
||||
apiData = apiResponse.data;
|
||||
console.log("[UserReviews] ✅ 데이터 경로 2: response.data 사용");
|
||||
// console.log("[UserReviews] ✅ 데이터 경로 2: response.data 사용");
|
||||
}
|
||||
// 3. response 직접 (최상위)
|
||||
else if (apiResponse && (apiResponse.reviewList || apiResponse.reviewDetail)) {
|
||||
apiData = apiResponse;
|
||||
console.log("[UserReviews] ✅ 데이터 경로 3: response 직접 사용");
|
||||
// console.log("[UserReviews] ✅ 데이터 경로 3: response 직접 사용");
|
||||
}
|
||||
|
||||
if (!apiData) {
|
||||
console.warn("[UserReviews] ❌ 모든 데이터 경로에서 추출 실패:", {
|
||||
hasResponseData: !!(apiResponse && apiResponse.data),
|
||||
hasReviewList: !!(apiResponse && apiResponse.data && apiResponse.data.reviewList),
|
||||
hasReviewDetail: !!(apiResponse && apiResponse.data && apiResponse.data.reviewDetail),
|
||||
hasNestedData: !!(apiResponse && apiResponse.data && apiResponse.data.data),
|
||||
fullResponse: apiResponse
|
||||
});
|
||||
// console.warn("[UserReviews] ❌ 모든 데이터 경로에서 추출 실패:", {
|
||||
// hasResponseData: !!(apiResponse && apiResponse.data),
|
||||
// hasReviewList: !!(apiResponse && apiResponse.data && apiResponse.data.reviewList),
|
||||
// hasReviewDetail: !!(apiResponse && apiResponse.data && apiResponse.data.reviewDetail),
|
||||
// hasNestedData: !!(apiResponse && apiResponse.data && apiResponse.data.data),
|
||||
// fullResponse: apiResponse
|
||||
// });
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -159,7 +145,7 @@ const extractReviewApiData = (apiResponse) => {
|
||||
|
||||
return apiData;
|
||||
} catch (error) {
|
||||
console.error("[UserReviews] ❌ extractReviewApiData 에러:", error);
|
||||
console.error('[UserReviews] ❌ extractReviewApiData 에러:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -167,16 +153,16 @@ const extractReviewApiData = (apiResponse) => {
|
||||
// Mock 데이터 생성 함수 (재사용성 위해 분리) - 100개 리뷰와 많은 이미지 포함
|
||||
const createMockReviewData = () => {
|
||||
const reviewTexts = [
|
||||
"The shoes are really stylish and comfortable for daily wear. I love the design and how lightweight they feel.",
|
||||
"Great value for the price! The quality is better than I expected. Shipping was fast and the product arrived in perfect condition.",
|
||||
"Amazing product! Really happy with this purchase. The color is exactly as shown in the pictures.",
|
||||
"Good quality overall but took a while to arrive. Packaging was excellent though.",
|
||||
"Perfect fit and very comfortable. Would definitely buy again from this seller.",
|
||||
"Beautiful design and great materials. My family loves it!",
|
||||
"Exceeded my expectations. Really impressed with the craftsmanship.",
|
||||
"Good product but could be better. Price is reasonable for what you get.",
|
||||
"Love the style and functionality. Works exactly as described.",
|
||||
"Fantastic quality! Highly recommend this to anyone looking for this type of product."
|
||||
'The shoes are really stylish and comfortable for daily wear. I love the design and how lightweight they feel.',
|
||||
'Great value for the price! The quality is better than I expected. Shipping was fast and the product arrived in perfect condition.',
|
||||
'Amazing product! Really happy with this purchase. The color is exactly as shown in the pictures.',
|
||||
'Good quality overall but took a while to arrive. Packaging was excellent though.',
|
||||
'Perfect fit and very comfortable. Would definitely buy again from this seller.',
|
||||
'Beautiful design and great materials. My family loves it!',
|
||||
'Exceeded my expectations. Really impressed with the craftsmanship.',
|
||||
'Good product but could be better. Price is reasonable for what you get.',
|
||||
'Love the style and functionality. Works exactly as described.',
|
||||
'Fantastic quality! Highly recommend this to anyone looking for this type of product.',
|
||||
];
|
||||
|
||||
const reviewList = [];
|
||||
@@ -191,7 +177,7 @@ const createMockReviewData = () => {
|
||||
reviewImageList.push({
|
||||
imgId: `mock-img-${i}-${j + 1}`,
|
||||
imgUrl: reviewSampleImage,
|
||||
imgSeq: j + 1
|
||||
imgSeq: j + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -204,7 +190,7 @@ const createMockReviewData = () => {
|
||||
rvwRgstDtt: `2024-01-${String((i % 30) + 1).padStart(2, '0')}`,
|
||||
wrtrNknm: `User${i}`,
|
||||
rvwWrtrId: `user${i}`,
|
||||
reviewImageList
|
||||
reviewImageList,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -213,95 +199,95 @@ const createMockReviewData = () => {
|
||||
reviewDetail: {
|
||||
totRvwCnt: 100,
|
||||
totRvwAvg: 4.2,
|
||||
avgRvwScr: 4.2
|
||||
}
|
||||
avgRvwScr: 4.2,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// showAllReviews 상태 토글
|
||||
export const toggleShowAllReviews = () => ({
|
||||
type: types.TOGGLE_SHOW_ALL_REVIEWS
|
||||
type: types.TOGGLE_SHOW_ALL_REVIEWS,
|
||||
});
|
||||
|
||||
// showAllReviews 상태 초기화 (ProductAllSection 마운트 시 사용)
|
||||
export const resetShowAllReviews = () => ({
|
||||
type: types.RESET_SHOW_ALL_REVIEWS
|
||||
type: types.RESET_SHOW_ALL_REVIEWS,
|
||||
});
|
||||
|
||||
// 상품별 유저 리뷰 리스트 조회 : IF-LGSP-0002
|
||||
export const getUserReviews = (requestParams) => (dispatch, getState) => {
|
||||
const { prdtId, patnrId } = requestParams;
|
||||
|
||||
console.log("[UserReviews] 🚀 getUserReviews 액션 시작:", {
|
||||
requestParams,
|
||||
prdtId,
|
||||
patnrId,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
// console.log("[UserReviews] 🚀 getUserReviews 액션 시작:", {
|
||||
// requestParams,
|
||||
// prdtId,
|
||||
// patnrId,
|
||||
// timestamp: new Date().toISOString()
|
||||
// });
|
||||
|
||||
// ==================== [임시 테스트] 시작 - 랜덤 prdtId 사용 ====================
|
||||
// 테스트용 prdtId 목록 - 실제 리뷰 데이터가 있는 상품들
|
||||
const testProductIds = [
|
||||
"LCE3010SB",
|
||||
"100QNED85AU",
|
||||
"14Z90Q-K.ARW3U1",
|
||||
"16Z90Q-K.AAC7U1",
|
||||
"24GN600-B",
|
||||
"50UT8000AUA",
|
||||
"A949KTMS",
|
||||
"AGF76631064",
|
||||
"C5323B0",
|
||||
"DLE3600V"
|
||||
'LCE3010SB',
|
||||
'100QNED85AU',
|
||||
'14Z90Q-K.ARW3U1',
|
||||
'16Z90Q-K.AAC7U1',
|
||||
'24GN600-B',
|
||||
'50UT8000AUA',
|
||||
'A949KTMS',
|
||||
'AGF76631064',
|
||||
'C5323B0',
|
||||
'DLE3600V',
|
||||
];
|
||||
const randomIndex = Math.floor(Math.random() * testProductIds.length);
|
||||
const randomPrdtId = testProductIds[randomIndex];
|
||||
|
||||
console.log("[UserReviews]-API 🎲 랜덤 prdtId 선택:", {
|
||||
originalPrdtId: prdtId,
|
||||
originalPatnrId: patnrId,
|
||||
randomPrdtId: randomPrdtId,
|
||||
fixedPatnrId: fixedPatnrId,
|
||||
randomIndex: randomIndex,
|
||||
testProductIds: testProductIds
|
||||
});
|
||||
// console.log("[UserReviews]-API 🎲 랜덤 prdtId 선택:", {
|
||||
// originalPrdtId: prdtId,
|
||||
// originalPatnrId: patnrId,
|
||||
// randomPrdtId: randomPrdtId,
|
||||
// fixedPatnrId: fixedPatnrId,
|
||||
// randomIndex: randomIndex,
|
||||
// testProductIds: testProductIds
|
||||
// });
|
||||
// ==================== [임시 테스트] 끝 ====================
|
||||
|
||||
// TAxios 파라미터 준비 - 랜덤 prdtId와 고정 patnrId 사용
|
||||
const fixedPatnrId = "9"; // patnrId 고정값
|
||||
const fixedPatnrId = '9'; // patnrId 고정값
|
||||
const params = { prdtId: randomPrdtId, patnrId: fixedPatnrId };
|
||||
const body = {}; // GET이므로 빈 객체
|
||||
|
||||
// plat_cd 값 확인을 위한 httpHeader 로깅
|
||||
const currentState = getState();
|
||||
const httpHeader = currentState.common.httpHeader || {};
|
||||
console.log("[UserReviews] <20> plat_cd 값 확인:", {
|
||||
platCd: httpHeader.plat_cd,
|
||||
fullHttpHeader: httpHeader,
|
||||
hasHttpHeader: !!httpHeader,
|
||||
httpHeaderKeys: Object.keys(httpHeader)
|
||||
});
|
||||
// console.log("[UserReviews] <20> plat_cd 값 확인:", {
|
||||
// platCd: httpHeader.plat_cd,
|
||||
// fullHttpHeader: httpHeader,
|
||||
// hasHttpHeader: !!httpHeader,
|
||||
// httpHeaderKeys: Object.keys(httpHeader)
|
||||
// });
|
||||
|
||||
console.log("[UserReviews]-API <20>📡 TAxios 호출 준비:", {
|
||||
method: "get",
|
||||
url: URLS.GET_USER_REVEIW,
|
||||
params,
|
||||
body,
|
||||
originalPrdtId: prdtId,
|
||||
randomPrdtId: randomPrdtId,
|
||||
originalPatnrId: patnrId,
|
||||
fixedPatnrId: fixedPatnrId,
|
||||
platCd: httpHeader.plat_cd
|
||||
});
|
||||
// console.log("[UserReviews]-API <20>📡 TAxios 호출 준비:", {
|
||||
// method: "get",
|
||||
// url: URLS.GET_USER_REVEIW,
|
||||
// params,
|
||||
// body,
|
||||
// originalPrdtId: prdtId,
|
||||
// randomPrdtId: randomPrdtId,
|
||||
// originalPatnrId: patnrId,
|
||||
// fixedPatnrId: fixedPatnrId,
|
||||
// platCd: httpHeader.plat_cd
|
||||
// });
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log("[UserReviews]-API ✅ API 성공 응답:", {
|
||||
console.log('[UserReviews]-API ✅ API 성공 응답:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
retCode: response.data && response.data.retCode,
|
||||
retMsg: response.data && response.data.retMsg,
|
||||
hasData: !!(response.data && response.data.data),
|
||||
fullResponse: response.data
|
||||
fullResponse: response.data,
|
||||
});
|
||||
|
||||
// ==================== [임시 테스트] 빈 리뷰 응답 시뮬레이션 - 코멘트 처리 ====================
|
||||
@@ -326,12 +312,13 @@ export const getUserReviews = (requestParams) => (dispatch, getState) => {
|
||||
// ==================== [임시 테스트] 끝 - 코멘트 처리 ====================
|
||||
|
||||
if (response.data && response.data.data) {
|
||||
console.log("[UserReviews] 📊 API 데이터 상세:", {
|
||||
console.log('[UserReviews] 📊 API 데이터 상세:', {
|
||||
reviewListLength: response.data.data.reviewList ? response.data.data.reviewList.length : 0,
|
||||
reviewDetail: response.data.data.reviewDetail,
|
||||
reviewList_sample: response.data.data.reviewList && response.data.data.reviewList[0] || "empty",
|
||||
reviewList_sample:
|
||||
(response.data.data.reviewList && response.data.data.reviewList[0]) || 'empty',
|
||||
totRvwCnt: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwCnt,
|
||||
totRvwAvg: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwAvg
|
||||
totRvwAvg: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwAvg,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -339,14 +326,14 @@ export const getUserReviews = (requestParams) => (dispatch, getState) => {
|
||||
const apiData = extractReviewApiData(response.data);
|
||||
|
||||
if (apiData) {
|
||||
console.log("[UserReviews] ✅ 실제 API 데이터 사용");
|
||||
console.log('[UserReviews] ✅ 실제 API 데이터 사용');
|
||||
dispatch({
|
||||
type: types.GET_USER_REVIEW,
|
||||
payload: { ...apiData, prdtId: prdtId } // 원래 prdtId로 저장 (UI에서 사용)
|
||||
payload: { ...apiData, prdtId: prdtId }, // 원래 prdtId로 저장 (UI에서 사용)
|
||||
});
|
||||
console.log("[UserReviews] 📦 실제 API 데이터 디스패치 완료:", apiData);
|
||||
console.log('[UserReviews] 📦 실제 API 데이터 디스패치 완료:', apiData);
|
||||
} else {
|
||||
console.log("[UserReviews] ⚠️ API 데이터 추출 실패");
|
||||
console.log('[UserReviews] ⚠️ API 데이터 추출 실패');
|
||||
// Mock 데이터 사용 비활성화
|
||||
// const mockData = createMockReviewData();
|
||||
// dispatch({
|
||||
@@ -358,17 +345,17 @@ export const getUserReviews = (requestParams) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error("[UserReviews] ❌ API 실패:", {
|
||||
console.error('[UserReviews] ❌ API 실패:', {
|
||||
message: error.message,
|
||||
status: error.response && error.response.status,
|
||||
statusText: error.response && error.response.statusText,
|
||||
responseData: error.response && error.response.data,
|
||||
requestParams: requestParams,
|
||||
url: URLS.GET_USER_REVEIW,
|
||||
fullError: error
|
||||
fullError: error,
|
||||
});
|
||||
|
||||
console.log("[UserReviews] 🔄 API 실패 - Mock 데이터 사용 비활성화");
|
||||
console.log('[UserReviews] 🔄 API 실패 - Mock 데이터 사용 비활성화');
|
||||
// Mock 데이터 사용 비활성화
|
||||
// const mockData = createMockReviewData();
|
||||
// dispatch({
|
||||
@@ -378,17 +365,8 @@ export const getUserReviews = (requestParams) => (dispatch, getState) => {
|
||||
// console.log("[UserReviews] 📦 Mock 데이터 디스패치 완료 (API 실패):", mockData);
|
||||
};
|
||||
|
||||
console.log("[UserReviews]-API 🔗 TAxios 호출 실행 중...");
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
"get",
|
||||
URLS.GET_USER_REVEIW,
|
||||
params,
|
||||
body,
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
// console.log("[UserReviews]-API 🔗 TAxios 호출 실행 중...");
|
||||
TAxios(dispatch, getState, 'get', URLS.GET_USER_REVEIW, params, body, onSuccess, onFail);
|
||||
};
|
||||
|
||||
export const getProductOptionId = (id) => (dispatch) => {
|
||||
@@ -409,7 +387,7 @@ export const getProductImageLength =
|
||||
|
||||
export const getVideoIndicatorFocus = (focused) => (dispatch) => {
|
||||
dispatch({
|
||||
type: types.GET_VIDEO_INDECATOR_FOCUS,
|
||||
type: types.GET_VIDEO_INDECATOR_FOCUS,
|
||||
payload: focused,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import React, { useCallback, useMemo, useState, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
import { startVideoPlayer, finishVideoPreview } from '../../../../actions/playActions';
|
||||
import { startMediaPlayer, finishMediaPreview } from '../../../../actions/mediaActions';
|
||||
import CustomImage from '../../../../components/CustomImage/CustomImage';
|
||||
import { panel_names } from '../../../../utils/Config';
|
||||
import playImg from '../../../../../assets/images/btn/btn-play-thumb-nor.png';
|
||||
import css from './ProductVideo.module.less';
|
||||
|
||||
const SpottableComponent = Spottable('div');
|
||||
|
||||
export default function ProductVideo({ productInfo, videoUrl, thumbnailUrl }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// PlayerPanel 상태 체크를 위한 selectors 추가 (Indicator.jsx와 동일)
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
const [isLaunchedFromPlayer, setIsLaunchedFromPlayer] = useState(false);
|
||||
const [focused, setFocused] = useState(false);
|
||||
const [modalState, setModalState] = useState(true); // 모달 상태 관리 추가
|
||||
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
// Indicator.jsx의 PlayerPanel 상태 체크 로직 추가 + 모달 상태 복원
|
||||
useEffect(() => {
|
||||
if (
|
||||
topPanel &&
|
||||
topPanel.name === panel_names.PLAYER_PANEL &&
|
||||
topPanel.panelInfo.modal === false
|
||||
) {
|
||||
return; // PlayerPanel이 전체화면 모드일 때는 처리하지 않음
|
||||
}
|
||||
|
||||
// PlayerPanel이 닫혔을 때 modalState를 true로 복원
|
||||
if (!topPanel || topPanel.name !== panel_names.PLAYER_PANEL) {
|
||||
setModalState(true);
|
||||
}
|
||||
}, [topPanel]);
|
||||
|
||||
// Indicator.jsx의 canPlayVideo 로직 완전 이식 (selectedIndex === 0 조건 포함)
|
||||
const canPlayVideo = useMemo(() => {
|
||||
return Boolean(productInfo?.prdtMediaUrl);
|
||||
}, [productInfo]);
|
||||
|
||||
// Indicator.jsx의 modalClassNameChange 로직 추가
|
||||
const modalClassNameChange = useCallback(() => {
|
||||
if (focused) {
|
||||
return css.videoModal;
|
||||
}
|
||||
return '';
|
||||
}, [focused]);
|
||||
|
||||
// focus 이벤트 핸들러 추가 (Indicator.jsx와 동일)
|
||||
const videoContainerOnFocus = useCallback(() => {
|
||||
if (canPlayVideo) {
|
||||
setFocused(true);
|
||||
}
|
||||
}, [canPlayVideo]);
|
||||
|
||||
const videoContainerOnBlur = useCallback(() => {
|
||||
if (canPlayVideo) {
|
||||
setFocused(false);
|
||||
// ProductVideo에서 포커스가 벗어나면 비디오 재생 종료
|
||||
dispatch(finishVideoPreview());
|
||||
}
|
||||
}, [canPlayVideo, dispatch]);
|
||||
|
||||
// Indicator.jsx의 handleVideoOnClick 로직 완전히 이식 + 모달 토글 기능 추가
|
||||
const handleVideoClick = useCallback(() => {
|
||||
if (canPlayVideo) {
|
||||
const currentTopPanel = panels[panels.length - 1];
|
||||
|
||||
// 현재 PlayerPanel이 modal=true로 재생 중인지 확인
|
||||
const isCurrentlyPlayingModal =
|
||||
currentTopPanel &&
|
||||
currentTopPanel.name === panel_names.PLAYER_PANEL &&
|
||||
currentTopPanel.panelInfo.modal === true;
|
||||
|
||||
// modal=true로 재생 중이면 modal=false(전체화면)로 변경
|
||||
const newModalState = isCurrentlyPlayingModal ? false : modalState;
|
||||
|
||||
// dispatch(
|
||||
// startVideoPlayer({
|
||||
// qrCurrentItem: productInfo,
|
||||
// showUrl: productInfo?.prdtMediaUrl,
|
||||
// showNm: productInfo?.prdtNm,
|
||||
// patnrNm: productInfo?.patncNm,
|
||||
// patncLogoPath: productInfo?.patncLogoPath,
|
||||
// orderPhnNo: productInfo?.orderPhnNo,
|
||||
// disclaimer: productInfo?.disclaimer,
|
||||
// subtitle: productInfo?.prdtMediaSubtitlUrl,
|
||||
// lgCatCd: productInfo?.catCd,
|
||||
// patnrId: productInfo?.patnrId,
|
||||
// lgCatNm: productInfo?.catNm,
|
||||
// prdtId: productInfo?.prdtId,
|
||||
// patncNm: productInfo?.patncNm,
|
||||
// prdtNm: productInfo?.prdtNm,
|
||||
// thumbnailUrl: productInfo?.thumbnailUrl960,
|
||||
// shptmBanrTpNm: 'MEDIA',
|
||||
// modal: newModalState,
|
||||
// modalContainerId: 'product-video-player',
|
||||
// modalClassName: modalClassNameChange(),
|
||||
// spotlightDisable: true,
|
||||
// })
|
||||
// );
|
||||
|
||||
dispatch(
|
||||
startMediaPlayer({
|
||||
qrCurrentItem: productInfo,
|
||||
showUrl: productInfo?.prdtMediaUrl,
|
||||
showNm: productInfo?.prdtNm,
|
||||
patnrNm: productInfo?.patncNm,
|
||||
patncLogoPath: productInfo?.patncLogoPath,
|
||||
orderPhnNo: productInfo?.orderPhnNo,
|
||||
disclaimer: productInfo?.disclaimer,
|
||||
subtitle: productInfo?.prdtMediaSubtitlUrl,
|
||||
lgCatCd: productInfo?.catCd,
|
||||
patnrId: productInfo?.patnrId,
|
||||
lgCatNm: productInfo?.catNm,
|
||||
prdtId: productInfo?.prdtId,
|
||||
patncNm: productInfo?.patncNm,
|
||||
prdtNm: productInfo?.prdtNm,
|
||||
thumbnailUrl: productInfo?.thumbnailUrl960,
|
||||
shptmBanrTpNm: 'MEDIA',
|
||||
modal: newModalState,
|
||||
modalContainerId: 'product-video-player',
|
||||
modalClassName: modalClassNameChange(),
|
||||
spotlightDisable: true,
|
||||
})
|
||||
);
|
||||
|
||||
// 모달 상태가 변경된 경우 상태 업데이트
|
||||
if (isCurrentlyPlayingModal) {
|
||||
setModalState(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (isLaunchedFromPlayer) {
|
||||
setIsLaunchedFromPlayer(false);
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
productInfo,
|
||||
canPlayVideo,
|
||||
isLaunchedFromPlayer,
|
||||
modalClassNameChange,
|
||||
panels,
|
||||
modalState,
|
||||
]);
|
||||
|
||||
if (!canPlayVideo) return null;
|
||||
|
||||
return (
|
||||
<SpottableComponent
|
||||
className={css.videoContainer}
|
||||
onClick={handleVideoClick}
|
||||
onFocus={videoContainerOnFocus}
|
||||
onBlur={videoContainerOnBlur}
|
||||
spotlightId="product-video-player"
|
||||
aria-label={`${productInfo?.prdtNm} 동영상 재생`}
|
||||
>
|
||||
<div className={css.videoThumbnailWrapper}>
|
||||
<CustomImage
|
||||
src={thumbnailUrl}
|
||||
alt={`${productInfo?.prdtNm} 동영상 썸네일`}
|
||||
className={css.videoThumbnail}
|
||||
/>
|
||||
<div className={css.playButtonOverlay}>
|
||||
<img src={playImg} alt="재생" />
|
||||
</div>
|
||||
</div>
|
||||
</SpottableComponent>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useCallback, useMemo, useState, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
import { startVideoPlayer, finishVideoPreview } from '../../../../actions/playActions';
|
||||
import { startMediaPlayer, finishMediaPreview } from '../../../../actions/mediaActions';
|
||||
import CustomImage from '../../../../components/CustomImage/CustomImage';
|
||||
import { panel_names } from '../../../../utils/Config';
|
||||
@@ -13,7 +12,7 @@ const SpottableComponent = Spottable('div');
|
||||
export default function ProductVideo({ productInfo, videoUrl, thumbnailUrl }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// PlayerPanel 상태 체크를 위한 selectors 추가 (Indicator.jsx와 동일)
|
||||
// MediaPanel 상태 체크를 위한 selectors 추가
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
const [isLaunchedFromPlayer, setIsLaunchedFromPlayer] = useState(false);
|
||||
const [focused, setFocused] = useState(false);
|
||||
@@ -21,28 +20,28 @@ export default function ProductVideo({ productInfo, videoUrl, thumbnailUrl }) {
|
||||
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
// Indicator.jsx의 PlayerPanel 상태 체크 로직 추가 + 모달 상태 복원
|
||||
// MediaPanel 상태 체크 로직 + 모달 상태 복원
|
||||
useEffect(() => {
|
||||
if (
|
||||
topPanel &&
|
||||
topPanel.name === panel_names.PLAYER_PANEL &&
|
||||
topPanel.name === panel_names.MEDIA_PANEL &&
|
||||
topPanel.panelInfo.modal === false
|
||||
) {
|
||||
return; // PlayerPanel이 전체화면 모드일 때는 처리하지 않음
|
||||
return; // MediaPanel이 전체화면 모드일 때는 처리하지 않음
|
||||
}
|
||||
|
||||
// PlayerPanel이 닫혔을 때 modalState를 true로 복원
|
||||
if (!topPanel || topPanel.name !== panel_names.PLAYER_PANEL) {
|
||||
// MediaPanel이 닫혔을 때 modalState를 true로 복원
|
||||
if (!topPanel || topPanel.name !== panel_names.MEDIA_PANEL) {
|
||||
setModalState(true);
|
||||
}
|
||||
}, [topPanel]);
|
||||
|
||||
// Indicator.jsx의 canPlayVideo 로직 완전 이식 (selectedIndex === 0 조건 포함)
|
||||
// 비디오 재생 가능 여부 체크
|
||||
const canPlayVideo = useMemo(() => {
|
||||
return Boolean(productInfo?.prdtMediaUrl);
|
||||
}, [productInfo]);
|
||||
|
||||
// Indicator.jsx의 modalClassNameChange 로직 추가
|
||||
// 모달 CSS 클래스 변경 로직
|
||||
const modalClassNameChange = useCallback(() => {
|
||||
if (focused) {
|
||||
return css.videoModal;
|
||||
@@ -50,7 +49,7 @@ export default function ProductVideo({ productInfo, videoUrl, thumbnailUrl }) {
|
||||
return '';
|
||||
}, [focused]);
|
||||
|
||||
// focus 이벤트 핸들러 추가 (Indicator.jsx와 동일)
|
||||
// 포커스 이벤트 핸들러
|
||||
const videoContainerOnFocus = useCallback(() => {
|
||||
if (canPlayVideo) {
|
||||
setFocused(true);
|
||||
@@ -60,50 +59,44 @@ export default function ProductVideo({ productInfo, videoUrl, thumbnailUrl }) {
|
||||
const videoContainerOnBlur = useCallback(() => {
|
||||
if (canPlayVideo) {
|
||||
setFocused(false);
|
||||
// ProductVideo에서 포커스가 벗어나면 비디오 재생 종료
|
||||
dispatch(finishVideoPreview());
|
||||
}
|
||||
}, [canPlayVideo, dispatch]);
|
||||
|
||||
// Indicator.jsx의 handleVideoOnClick 로직 완전히 이식 + 모달 토글 기능 추가
|
||||
// MediaPanel이 modal로 열려있지 않을 때만 종료
|
||||
// (열려있을 때는 MediaPanel 자체에서 생명주기 관리)
|
||||
const currentTopPanel = panels[panels.length - 1];
|
||||
const isMediaPanelModalOpen =
|
||||
currentTopPanel &&
|
||||
currentTopPanel.name === panel_names.MEDIA_PANEL &&
|
||||
currentTopPanel.panelInfo.modal === true;
|
||||
|
||||
console.log('[ProductVideo] onBlur:', {
|
||||
isMediaPanelModalOpen,
|
||||
currentTopPanelName: currentTopPanel?.name,
|
||||
willClosePanel: !isMediaPanelModalOpen,
|
||||
});
|
||||
|
||||
if (!isMediaPanelModalOpen) {
|
||||
console.log('[ProductVideo] Closing MediaPanel from onBlur');
|
||||
dispatch(finishMediaPreview());
|
||||
} else {
|
||||
console.log('[ProductVideo] MediaPanel is open, skipping finishMediaPreview');
|
||||
}
|
||||
}
|
||||
}, [canPlayVideo, dispatch, panels]);
|
||||
|
||||
// MediaPanel 비디오 클릭 핸들러 + 모달 토글 기능
|
||||
const handleVideoClick = useCallback(() => {
|
||||
if (canPlayVideo) {
|
||||
const currentTopPanel = panels[panels.length - 1];
|
||||
|
||||
// 현재 PlayerPanel이 modal=true로 재생 중인지 확인
|
||||
// 현재 MediaPanel이 modal=true로 재생 중인지 확인
|
||||
const isCurrentlyPlayingModal =
|
||||
currentTopPanel &&
|
||||
currentTopPanel.name === panel_names.PLAYER_PANEL &&
|
||||
currentTopPanel.name === panel_names.MEDIA_PANEL &&
|
||||
currentTopPanel.panelInfo.modal === true;
|
||||
|
||||
// modal=true로 재생 중이면 modal=false(전체화면)로 변경
|
||||
const newModalState = isCurrentlyPlayingModal ? false : modalState;
|
||||
|
||||
// dispatch(
|
||||
// startVideoPlayer({
|
||||
// qrCurrentItem: productInfo,
|
||||
// showUrl: productInfo?.prdtMediaUrl,
|
||||
// showNm: productInfo?.prdtNm,
|
||||
// patnrNm: productInfo?.patncNm,
|
||||
// patncLogoPath: productInfo?.patncLogoPath,
|
||||
// orderPhnNo: productInfo?.orderPhnNo,
|
||||
// disclaimer: productInfo?.disclaimer,
|
||||
// subtitle: productInfo?.prdtMediaSubtitlUrl,
|
||||
// lgCatCd: productInfo?.catCd,
|
||||
// patnrId: productInfo?.patnrId,
|
||||
// lgCatNm: productInfo?.catNm,
|
||||
// prdtId: productInfo?.prdtId,
|
||||
// patncNm: productInfo?.patncNm,
|
||||
// prdtNm: productInfo?.prdtNm,
|
||||
// thumbnailUrl: productInfo?.thumbnailUrl960,
|
||||
// shptmBanrTpNm: 'MEDIA',
|
||||
// modal: newModalState,
|
||||
// modalContainerId: 'product-video-player',
|
||||
// modalClassName: modalClassNameChange(),
|
||||
// spotlightDisable: true,
|
||||
// })
|
||||
// );
|
||||
|
||||
dispatch(
|
||||
startMediaPlayer({
|
||||
qrCurrentItem: productInfo,
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import { toggleShowAllReviews } from '../../../../actions/productActions';
|
||||
@@ -22,26 +12,25 @@ import useScrollTo from '../../../../hooks/useScrollTo';
|
||||
import { $L } from '../../../../utils/helperMethods';
|
||||
import StarRating from '../../components/StarRating';
|
||||
import THeaderDetail from '../../components/THeaderDetail';
|
||||
import UserReviewsScroller
|
||||
from '../../components/UserReviewsScroller/UserReviewsScroller';
|
||||
import UserReviewsScroller from '../../components/UserReviewsScroller/UserReviewsScroller';
|
||||
import CustomerImages from './CustomerImages/CustomerImages';
|
||||
import css from './UserReviews.module.less';
|
||||
import UserReviewsPopup from './UserReviewsPopup/UserReviewsPopup';
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
const SpottableComponent = Spottable('div');
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{
|
||||
enterTo: "default-element",
|
||||
enterTo: 'default-element',
|
||||
preserveld: true,
|
||||
leaveFor: {
|
||||
left: "spotlight-product-info-section-container",
|
||||
up: "view-all-reviews-button",
|
||||
left: 'spotlight-product-info-section-container',
|
||||
up: 'view-all-reviews-button',
|
||||
},
|
||||
restrict: "none",
|
||||
spotlightDirection: "vertical",
|
||||
restrict: 'none',
|
||||
spotlightDirection: 'vertical',
|
||||
},
|
||||
"div"
|
||||
'div'
|
||||
);
|
||||
|
||||
export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
@@ -62,28 +51,28 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
|
||||
// 팝업 상태 관리 - 모드와 선택된 이미지 인덱스 추가
|
||||
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
||||
const [popupMode, setPopupMode] = useState("customer-images"); // "customer-images", "all-images"
|
||||
const [popupMode, setPopupMode] = useState('customer-images'); // "customer-images", "all-images"
|
||||
const [selectedImageIndex, setSelectedImageIndex] = useState(0);
|
||||
// Redux에서 showAllReviews 상태 가져오기
|
||||
const showAllReviews = useSelector((state) => state.product.showAllReviews);
|
||||
|
||||
// 디버깅: showAllReviews 상태 변경 확인
|
||||
useEffect(() => {
|
||||
console.log("[UserReviews] showAllReviews state changed:", {
|
||||
showAllReviews,
|
||||
reviewListLength:
|
||||
(actualReviewsData.previewReviews &&
|
||||
actualReviewsData.previewReviews.length) ||
|
||||
0,
|
||||
willShowCount: showAllReviews
|
||||
? (actualReviewsData.previewReviews &&
|
||||
actualReviewsData.previewReviews.length) ||
|
||||
0
|
||||
: 5,
|
||||
hasReviewsData: !!reviewsData,
|
||||
isFromProductAllSection: !!reviewsData,
|
||||
});
|
||||
}, [showAllReviews, actualReviewsData.previewReviews, reviewsData]);
|
||||
// useEffect(() => {
|
||||
// console.log("[UserReviews] showAllReviews state changed:", {
|
||||
// showAllReviews,
|
||||
// reviewListLength:
|
||||
// (actualReviewsData.previewReviews &&
|
||||
// actualReviewsData.previewReviews.length) ||
|
||||
// 0,
|
||||
// willShowCount: showAllReviews
|
||||
// ? (actualReviewsData.previewReviews &&
|
||||
// actualReviewsData.previewReviews.length) ||
|
||||
// 0
|
||||
// : 5,
|
||||
// hasReviewsData: !!reviewsData,
|
||||
// isFromProductAllSection: !!reviewsData,
|
||||
// });
|
||||
// }, [showAllReviews, actualReviewsData.previewReviews, reviewsData]);
|
||||
|
||||
// showAllReviews 상태 변경 시 TScroller 스크롤 영역 강제 재계산
|
||||
useEffect(() => {
|
||||
@@ -94,12 +83,12 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
setTimeout(() => {
|
||||
if (tScrollerRef.current) {
|
||||
// TScroller의 스크롤 영역을 강제로 업데이트
|
||||
if (typeof tScrollerRef.current.calculateMetrics === "function") {
|
||||
if (typeof tScrollerRef.current.calculateMetrics === 'function') {
|
||||
tScrollerRef.current.calculateMetrics();
|
||||
}
|
||||
|
||||
// 또는 scrollTo를 호출해서 스크롤 영역 업데이트
|
||||
if (typeof tScrollerRef.current.scrollTo === "function") {
|
||||
if (typeof tScrollerRef.current.scrollTo === 'function') {
|
||||
tScrollerRef.current.scrollTo({
|
||||
position: { y: 0 },
|
||||
animate: false,
|
||||
@@ -122,25 +111,25 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
};
|
||||
|
||||
// [UserReviews] 데이터 수신 확인 로그
|
||||
useEffect(() => {
|
||||
console.log("[UserReviews] 실제 데이터 확인:", {
|
||||
reviewListLength: (reviewListData && reviewListData.length) || 0,
|
||||
totalReviews: actualReviewsData.stats.totalReviews,
|
||||
averageRating: actualReviewsData.stats.averageRating,
|
||||
isLoading: actualReviewsData.isLoading,
|
||||
hasData: reviewListData && reviewListData.length > 0,
|
||||
dataSource: reviewsData
|
||||
? "ProductAllSection props"
|
||||
: "useReviews fallback",
|
||||
});
|
||||
}, [reviewListData, actualReviewsData, reviewsData]);
|
||||
// useEffect(() => {
|
||||
// console.log("[UserReviews] 실제 데이터 확인:", {
|
||||
// reviewListLength: (reviewListData && reviewListData.length) || 0,
|
||||
// totalReviews: actualReviewsData.stats.totalReviews,
|
||||
// averageRating: actualReviewsData.stats.averageRating,
|
||||
// isLoading: actualReviewsData.isLoading,
|
||||
// hasData: reviewListData && reviewListData.length > 0,
|
||||
// dataSource: reviewsData
|
||||
// ? "ProductAllSection props"
|
||||
// : "useReviews fallback",
|
||||
// });
|
||||
// }, [reviewListData, actualReviewsData, reviewsData]);
|
||||
|
||||
// ✅ useReviews Hook이 모든 API 호출을 담당하므로 별도 API 호출 불필요
|
||||
|
||||
const formatToYYMMDD = (dateStr) => {
|
||||
const date = new Date(dateStr);
|
||||
const iso = date.toISOString().slice(2, 10);
|
||||
return iso.replace(/-/g, ".");
|
||||
return iso.replace(/-/g, '.');
|
||||
};
|
||||
|
||||
// 리뷰 클릭으로 User Reviews 모드 팝업 열기
|
||||
@@ -154,18 +143,18 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
(review) => review.rvwId === clickedReview.rvwId
|
||||
);
|
||||
|
||||
console.log(
|
||||
"[UserReviews] Review clicked, opening popup in User Reviews mode:",
|
||||
{
|
||||
previewIndex: reviewIndex,
|
||||
realIndex,
|
||||
clickedReviewId: clickedReview.rvwId,
|
||||
totalReviews: fallbackReviews.allReviews.length,
|
||||
}
|
||||
);
|
||||
// console.log(
|
||||
// "[UserReviews] Review clicked, opening popup in User Reviews mode:",
|
||||
// {
|
||||
// previewIndex: reviewIndex,
|
||||
// realIndex,
|
||||
// clickedReviewId: clickedReview.rvwId,
|
||||
// totalReviews: fallbackReviews.allReviews.length,
|
||||
// }
|
||||
// );
|
||||
|
||||
setSelectedImageIndex(realIndex >= 0 ? realIndex : reviewIndex);
|
||||
setPopupMode("user-reviews");
|
||||
setPopupMode('user-reviews');
|
||||
setIsPopupOpen(true);
|
||||
},
|
||||
[reviewListData, fallbackReviews.allReviews]
|
||||
@@ -174,37 +163,34 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
// 팝업 관련 핸들러들
|
||||
// +View More 버튼으로 Customer Images 모드 팝업 열기
|
||||
const handleOpenPopup = useCallback(() => {
|
||||
console.log("[UserReviews] Opening popup in Customer Images mode");
|
||||
setPopupMode("customer-images");
|
||||
// console.log("[UserReviews] Opening popup in Customer Images mode");
|
||||
setPopupMode('customer-images');
|
||||
setIsPopupOpen(true);
|
||||
}, []);
|
||||
|
||||
// 이미지 클릭으로 All-Images 모드 팝업 열기
|
||||
const handleOpenAllImagesPopup = useCallback((imageIndex = 0) => {
|
||||
console.log(
|
||||
"[UserReviews] Opening popup in All-Images mode, image index:",
|
||||
imageIndex
|
||||
);
|
||||
console.log('[UserReviews] Opening popup in All-Images mode, image index:', imageIndex);
|
||||
setSelectedImageIndex(imageIndex);
|
||||
setPopupMode("all-images");
|
||||
setPopupMode('all-images');
|
||||
setIsPopupOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleClosePopup = useCallback(() => {
|
||||
console.log("[UserReviews] Closing popup and resetting mode");
|
||||
// console.log("[UserReviews] Closing popup and resetting mode");
|
||||
setIsPopupOpen(false);
|
||||
setPopupMode("customer-images"); // 모드를 초기값으로 리셋
|
||||
setPopupMode('customer-images'); // 모드를 초기값으로 리셋
|
||||
setSelectedImageIndex(0); // 이미지 인덱스도 초기값으로 리셋
|
||||
}, []);
|
||||
|
||||
// 팝업 모드 변경 핸들러
|
||||
const handleModeChange = useCallback((newMode, imageIndex = 0) => {
|
||||
console.log("[UserReviews] Mode change requested:", {
|
||||
newMode,
|
||||
imageIndex,
|
||||
});
|
||||
// console.log("[UserReviews] Mode change requested:", {
|
||||
// newMode,
|
||||
// imageIndex,
|
||||
// });
|
||||
setPopupMode(newMode);
|
||||
if (newMode === "all-images" || newMode === "user-reviews") {
|
||||
if (newMode === 'all-images' || newMode === 'user-reviews') {
|
||||
setSelectedImageIndex(imageIndex);
|
||||
}
|
||||
}, []);
|
||||
@@ -225,21 +211,14 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
verticalScrollbar="auto"
|
||||
cbScrollTo={getScrollTo}
|
||||
forceUpdate={showAllReviews}
|
||||
key={
|
||||
showAllReviews
|
||||
? `all-${(reviewListData && reviewListData.length) || 0}`
|
||||
: "limited-5"
|
||||
}
|
||||
key={showAllReviews ? `all-${(reviewListData && reviewListData.length) || 0}` : 'limited-5'}
|
||||
>
|
||||
<THeaderDetail
|
||||
title={$L(`USER REVIEWS (${reviewTotalCount})`)}
|
||||
className={classNames(css.tHeader, css.tHeaderDetail)}
|
||||
>
|
||||
{reviewDetailData && reviewDetailData.totRvwAvg && (
|
||||
<StarRating
|
||||
rating={reviewDetailData.totRvwAvg}
|
||||
className={css.averageOverallRating}
|
||||
/>
|
||||
<StarRating rating={reviewDetailData.totRvwAvg} className={css.averageOverallRating} />
|
||||
)}
|
||||
</THeaderDetail>
|
||||
<CustomerImages
|
||||
@@ -256,48 +235,39 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
</div>
|
||||
{reviewListData &&
|
||||
(() => {
|
||||
const reviewsToShow = showAllReviews
|
||||
? reviewListData
|
||||
: reviewListData.slice(0, 5);
|
||||
console.log("[UserReviews] Reviews to render:", {
|
||||
showAllReviews,
|
||||
totalReviews: reviewListData.length,
|
||||
reviewsToShowCount: reviewsToShow.length,
|
||||
isShowingAll: showAllReviews,
|
||||
reviewListData: reviewListData,
|
||||
});
|
||||
const reviewsToShow = showAllReviews ? reviewListData : reviewListData.slice(0, 5);
|
||||
// console.log("[UserReviews] Reviews to render:", {
|
||||
// showAllReviews,
|
||||
// totalReviews: reviewListData.length,
|
||||
// reviewsToShowCount: reviewsToShow.length,
|
||||
// isShowingAll: showAllReviews,
|
||||
// reviewListData: reviewListData,
|
||||
// });
|
||||
|
||||
// 실제 렌더링될 각 리뷰 로그
|
||||
reviewsToShow.forEach((review, index) => {
|
||||
console.log(
|
||||
`[UserReviews] Review ${index + 1}/${reviewsToShow.length}:`,
|
||||
{
|
||||
rvwId: review.rvwId,
|
||||
rvwCtnt:
|
||||
(review.rvwCtnt && review.rvwCtnt.substring(0, 50)) +
|
||||
"...",
|
||||
rvwRtng: review.rvwRtng,
|
||||
hasImages:
|
||||
(review.reviewImageList &&
|
||||
review.reviewImageList.length) ||
|
||||
0,
|
||||
rvwRgstDtt: review.rvwRgstDtt,
|
||||
fullReview: review,
|
||||
}
|
||||
);
|
||||
});
|
||||
// reviewsToShow.forEach((review, index) => {
|
||||
// console.log(
|
||||
// `[UserReviews] Review ${index + 1}/${reviewsToShow.length}:`,
|
||||
// {
|
||||
// rvwId: review.rvwId,
|
||||
// rvwCtnt:
|
||||
// (review.rvwCtnt && review.rvwCtnt.substring(0, 50)) +
|
||||
// "...",
|
||||
// rvwRtng: review.rvwRtng,
|
||||
// hasImages:
|
||||
// (review.reviewImageList &&
|
||||
// review.reviewImageList.length) ||
|
||||
// 0,
|
||||
// rvwRgstDtt: review.rvwRgstDtt,
|
||||
// fullReview: review,
|
||||
// }
|
||||
// );
|
||||
// });
|
||||
|
||||
return reviewsToShow;
|
||||
})().map((review, index, array) => {
|
||||
const {
|
||||
reviewImageList,
|
||||
rvwRtng,
|
||||
rvwRgstDtt,
|
||||
rvwCtnt,
|
||||
rvwId,
|
||||
wrtrNknm,
|
||||
rvwWrtrId,
|
||||
} = review;
|
||||
const { reviewImageList, rvwRtng, rvwRgstDtt, rvwCtnt, rvwId, wrtrNknm, rvwWrtrId } =
|
||||
review;
|
||||
const isLastReview = index === array.length - 1;
|
||||
/* console.log(`[UserReviews] Rendering review ${index}:`, {
|
||||
rvwId,
|
||||
@@ -312,35 +282,24 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
aria-label={`user-reviews-:${rvwId}`}
|
||||
className={css.reviewContentContainer}
|
||||
onClick={() => handleReviewClick(index)}
|
||||
spotlightId={
|
||||
isLastReview
|
||||
? "user-review-at-last"
|
||||
: `user-review-${index}`
|
||||
}
|
||||
spotlightId={isLastReview ? 'user-review-at-last' : `user-review-${index}`}
|
||||
>
|
||||
{reviewImageList && reviewImageList.length > 0 && (
|
||||
<img
|
||||
className={css.reviewThumbnail}
|
||||
src={reviewImageList[0].imgUrl}
|
||||
/>
|
||||
<img className={css.reviewThumbnail} src={reviewImageList[0].imgUrl} />
|
||||
)}
|
||||
<div className={css.reviewContent}>
|
||||
<div className={css.reviewMeta}>
|
||||
{rvwRtng && (
|
||||
<StarRating
|
||||
rating={rvwRtng}
|
||||
aria-label={"star rating " + rvwRtng + " out of 5"}
|
||||
aria-label={'star rating ' + rvwRtng + ' out of 5'}
|
||||
/>
|
||||
)}
|
||||
{(wrtrNknm || rvwWrtrId) && (
|
||||
<span className={css.reviewAuthor}>
|
||||
{wrtrNknm || rvwWrtrId}
|
||||
</span>
|
||||
<span className={css.reviewAuthor}>{wrtrNknm || rvwWrtrId}</span>
|
||||
)}
|
||||
{rvwRgstDtt && (
|
||||
<span className={css.reviewDate}>
|
||||
{formatToYYMMDD(rvwRgstDtt)}
|
||||
</span>
|
||||
<span className={css.reviewDate}>{formatToYYMMDD(rvwRgstDtt)}</span>
|
||||
)}
|
||||
</div>
|
||||
{rvwCtnt && <div className={css.reviewText}>{rvwCtnt}</div>}
|
||||
|
||||
@@ -7,7 +7,11 @@ import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
|
||||
import { sendBroadCast } from '../../actions/commonActions';
|
||||
import { pauseModalMedia, resumeModalMedia } from '../../actions/mediaActions';
|
||||
import {
|
||||
pauseModalMedia,
|
||||
resumeModalMedia,
|
||||
switchMediaToFullscreen,
|
||||
} from '../../actions/mediaActions';
|
||||
import * as PanelActions from '../../actions/panelActions';
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
import Media from '../../components/VideoPlayer/Media';
|
||||
@@ -89,6 +93,11 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
videoPlayer.current = ref;
|
||||
}, []);
|
||||
|
||||
// VideoPlayer가 MEDIA 타입에서 setIsVODPaused를 호출하므로 더미 함수 제공
|
||||
const setIsVODPaused = useCallback(() => {
|
||||
// MediaPanel에서는 VOD pause 상태 관리 불필요 (단순 재생만)
|
||||
}, []);
|
||||
|
||||
// modal 스타일 설정
|
||||
useEffect(() => {
|
||||
if (panelInfo.modal && panelInfo.modalContainerId) {
|
||||
@@ -124,6 +133,14 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
}
|
||||
}, [panelInfo, isOnTop]);
|
||||
|
||||
// 비디오 클릭 시 modal → fullscreen 전환
|
||||
const onVideoClick = useCallback(() => {
|
||||
if (panelInfo.modal) {
|
||||
console.log('[MediaPanel] Video clicked - switching to fullscreen');
|
||||
dispatch(switchMediaToFullscreen());
|
||||
}
|
||||
}, [dispatch, panelInfo.modal]);
|
||||
|
||||
const onClickBack = useCallback(
|
||||
(ev) => {
|
||||
// modal에서 full로 전환된 경우 다시 modal로 돌아감
|
||||
@@ -280,6 +297,7 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
noAutoPlay={false}
|
||||
autoCloseTimeout={3000}
|
||||
onBackButton={onClickBack}
|
||||
onClick={onVideoClick}
|
||||
spotlightDisabled={panelInfo.modal}
|
||||
isYoutube={isYoutube}
|
||||
src={currentPlayingUrl}
|
||||
@@ -302,6 +320,7 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
captionEnable={false}
|
||||
setIsSubtitleActive={setIsSubtitleActive}
|
||||
setCurrentTime={setCurrentTime}
|
||||
setIsVODPaused={setIsVODPaused}
|
||||
>
|
||||
{typeof window === 'object' && window.PalmSystem && (
|
||||
<source src={currentPlayingUrl} type={videoType} />
|
||||
|
||||
Reference in New Issue
Block a user