[251106] fix: BuyOption getProductOption
🕐 커밋 시간: 2025. 11. 06. 18:02:13 📊 변경 통계: • 총 파일: 6개 • 추가: +163줄 • 삭제: -6줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/productActions.js ~ com.twin.app.shoptime/src/actions/toastActions.js ~ com.twin.app.shoptime/src/components/TToast/TToastEnhanced.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/MainView/MainView.jsx 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • UI 컴포넌트 아키텍처 개선 • 중간 규모 기능 개선 • 모듈 구조 개선
This commit is contained in:
@@ -35,14 +35,59 @@ const createRequestThunk =
|
|||||||
const query = params(props);
|
const query = params(props);
|
||||||
const body = data(props);
|
const body = data(props);
|
||||||
|
|
||||||
|
// 📡 REQUEST 로그: API 호출 전 (tag별로 다르게 표시)
|
||||||
|
console.log(
|
||||||
|
`%c[${tag}] 📤 REQUEST - ${method.toUpperCase()} ${url}`,
|
||||||
|
'background: #4CAF50; color: white; font-weight: bold; padding: 3px;',
|
||||||
|
{
|
||||||
|
method: method.toUpperCase(),
|
||||||
|
url: url,
|
||||||
|
params: query,
|
||||||
|
body: body,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const onSuccess = (response) => {
|
const onSuccess = (response) => {
|
||||||
console.log(`${tag} onSuccess`, response.data);
|
// ✅ RESPONSE 로그: API 호출 성공 (tag별로 다르게 표시)
|
||||||
|
console.log(
|
||||||
|
`%c[${tag}] ✅ RESPONSE SUCCESS - ${method.toUpperCase()} ${url}`,
|
||||||
|
'background: #2196F3; color: white; font-weight: bold; padding: 3px;',
|
||||||
|
{
|
||||||
|
method: method.toUpperCase(),
|
||||||
|
url: url,
|
||||||
|
httpStatus: response?.status,
|
||||||
|
httpStatusText: response?.statusText,
|
||||||
|
retCode: response?.data?.retCode,
|
||||||
|
retMsg: response?.data?.retMsg,
|
||||||
|
responseData: response?.data,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({ type, payload: selectPayload(response) });
|
dispatch({ type, payload: selectPayload(response) });
|
||||||
onSuccessExtra(props, dispatch, getState, response);
|
onSuccessExtra(props, dispatch, getState, response);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFail = (error) => {
|
const onFail = (error) => {
|
||||||
console.error(`${tag} onFail`, error);
|
// ❌ ERROR 로그: API 호출 실패 (tag별로 다르게 표시)
|
||||||
|
console.error(
|
||||||
|
`%c[${tag}] ❌ RESPONSE ERROR - ${method.toUpperCase()} ${url}`,
|
||||||
|
'background: #F44336; color: white; font-weight: bold; padding: 3px;',
|
||||||
|
{
|
||||||
|
method: method.toUpperCase(),
|
||||||
|
url: url,
|
||||||
|
errorMessage: error?.message,
|
||||||
|
errorType: error?.type,
|
||||||
|
httpStatus: error?.response?.status,
|
||||||
|
httpStatusText: error?.response?.statusText,
|
||||||
|
responseRetCode: error?.response?.data?.retCode,
|
||||||
|
responseRetMsg: error?.response?.data?.retMsg,
|
||||||
|
responseData: error?.response?.data,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
onFailExtra(props, dispatch, getState, error);
|
onFailExtra(props, dispatch, getState, error);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ export const showToast = curry(
|
|||||||
position = 'bottom-center',
|
position = 'bottom-center',
|
||||||
id,
|
id,
|
||||||
onToastClose, // Toast가 닫힐 때 호출할 콜백
|
onToastClose, // Toast가 닫힐 때 호출할 콜백
|
||||||
|
productInfo, // 🚀 BuyOption용 상품 정보
|
||||||
|
selectedPatnrId, // 🚀 BuyOption용 파트너 ID
|
||||||
|
selectedPrdtId, // 🚀 BuyOption용 상품 ID
|
||||||
|
...otherProps // 🚀 추가 props 전달 지원
|
||||||
}) => ({
|
}) => ({
|
||||||
type: TOAST_ACTIONS.ADD_TOAST,
|
type: TOAST_ACTIONS.ADD_TOAST,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -27,6 +31,10 @@ export const showToast = curry(
|
|||||||
duration,
|
duration,
|
||||||
position,
|
position,
|
||||||
onToastClose, // 콜백 전달
|
onToastClose, // 콜백 전달
|
||||||
|
productInfo, // 🚀 BuyOption에 전달할 상품 정보
|
||||||
|
selectedPatnrId, // 🚀 BuyOption에 전달할 파트너 ID
|
||||||
|
selectedPrdtId, // 🚀 BuyOption에 전달할 상품 ID
|
||||||
|
...otherProps, // 🚀 추가 props 지원
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ export default function TToastEnhanced({
|
|||||||
id,
|
id,
|
||||||
onClose, // ToastContainer에서 전달하는 toast 제거 콜백
|
onClose, // ToastContainer에서 전달하는 toast 제거 콜백
|
||||||
onToastClose, // payload의 onClose - Toast 닫힐 때 호출할 추가 콜백
|
onToastClose, // payload의 onClose - Toast 닫힐 때 호출할 추가 콜백
|
||||||
|
productInfo, // 🚀 BuyOption에 전달할 상품 정보
|
||||||
|
selectedPatnrId, // 🚀 BuyOption에 전달할 파트너 ID
|
||||||
|
selectedPrdtId, // 🚀 BuyOption에 전달할 상품 ID
|
||||||
...rest
|
...rest
|
||||||
}) {
|
}) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -186,7 +189,11 @@ export default function TToastEnhanced({
|
|||||||
>
|
>
|
||||||
{type === 'buyOption' ? (
|
{type === 'buyOption' ? (
|
||||||
<div ref={buyOptionRef} onBlur={handleBuyOptionBlur}>
|
<div ref={buyOptionRef} onBlur={handleBuyOptionBlur}>
|
||||||
<BuyOption />
|
<BuyOption
|
||||||
|
productInfo={productInfo}
|
||||||
|
selectedPatnrId={selectedPatnrId}
|
||||||
|
selectedPrdtId={selectedPrdtId}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={css.content}>
|
<div className={css.content}>
|
||||||
|
|||||||
@@ -371,6 +371,14 @@ export default function ProductAllSection({
|
|||||||
const handleBuyNowClick = useCallback((e) => {
|
const handleBuyNowClick = useCallback((e) => {
|
||||||
// console.log('[BuyNow] Buy Now button clicked');
|
// console.log('[BuyNow] Buy Now button clicked');
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
console.log('[ProductAllSection] 🛒 BUY NOW clicked - productData:', {
|
||||||
|
prdtId: productData?.prdtId,
|
||||||
|
patnrId: productData?.patnrId,
|
||||||
|
prdtNm: productData?.prdtNm,
|
||||||
|
hasProductData: !!productData,
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
showToast({
|
showToast({
|
||||||
id: productData.prdtId,
|
id: productData.prdtId,
|
||||||
@@ -378,6 +386,10 @@ export default function ProductAllSection({
|
|||||||
type: 'buyOption',
|
type: 'buyOption',
|
||||||
duration: 0,
|
duration: 0,
|
||||||
position: 'bottom-center',
|
position: 'bottom-center',
|
||||||
|
// 🚀 BuyOption에 전달할 props 데이터
|
||||||
|
productInfo: productData,
|
||||||
|
selectedPatnrId: productData?.patnrId,
|
||||||
|
selectedPrdtId: productData?.prdtId,
|
||||||
// BuyOption Toast가 닫힐 때 BUY NOW 버튼으로 포커스 복구
|
// BuyOption Toast가 닫힐 때 BUY NOW 버튼으로 포커스 복구
|
||||||
onToastClose: () => {
|
onToastClose: () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -386,7 +398,7 @@ export default function ProductAllSection({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, [dispatch]);
|
}, [dispatch, productData]);
|
||||||
|
|
||||||
//닫히도록
|
//닫히도록
|
||||||
const handleCloseToast = useCallback(() => {
|
const handleCloseToast = useCallback(() => {
|
||||||
|
|||||||
@@ -74,7 +74,18 @@ const BuyOption = ({
|
|||||||
type,
|
type,
|
||||||
}) => {
|
}) => {
|
||||||
// DEBUG_LOG 설정 - 이 값이 true일 때만 console.log가 실행됨
|
// DEBUG_LOG 설정 - 이 값이 true일 때만 console.log가 실행됨
|
||||||
const DEBUG_LOG = false;
|
const DEBUG_LOG = true;
|
||||||
|
|
||||||
|
// 🚀 컴포넌트 마운트 시 props 확인
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('[BuyOption] 🚀 COMPONENT MOUNTED - Props received:', {
|
||||||
|
propsProductInfo: !!propsProductInfo,
|
||||||
|
propsSelectedPatnrId,
|
||||||
|
propsSelectedPrdtId,
|
||||||
|
patncNm,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
// console.log 오버라이드를 위한 ref
|
// console.log 오버라이드를 위한 ref
|
||||||
const originalConsoleLog = useRef(console.log);
|
const originalConsoleLog = useRef(console.log);
|
||||||
@@ -107,6 +118,16 @@ const BuyOption = ({
|
|||||||
const productInfo = propsProductInfo || reduxProductInfo;
|
const productInfo = propsProductInfo || reduxProductInfo;
|
||||||
const reduxProductOptionInfos = useSelector((state) => state.product.prdtOptInfo);
|
const reduxProductOptionInfos = useSelector((state) => state.product.prdtOptInfo);
|
||||||
const productData = useSelector((state) => state.main.productData);
|
const productData = useSelector((state) => state.main.productData);
|
||||||
|
|
||||||
|
// 🔴 Redux productData 상태 확인
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('[BuyOption] 🔴 REDUX PRODUCT DATA STATUS:', {
|
||||||
|
hasReduxProductInfo: !!reduxProductInfo,
|
||||||
|
hasProductData: !!productData,
|
||||||
|
reduxProductInfoFull: reduxProductInfo,
|
||||||
|
productDataFull: productData,
|
||||||
|
});
|
||||||
|
}, [reduxProductInfo, productData]);
|
||||||
const { partnerCoupon } = useSelector(
|
const { partnerCoupon } = useSelector(
|
||||||
(state) => state.coupon.productCouponSearchData || {}
|
(state) => state.coupon.productCouponSearchData || {}
|
||||||
);
|
);
|
||||||
@@ -137,6 +158,34 @@ const BuyOption = ({
|
|||||||
const selectedPrdtId = propsSelectedPrdtId || productInfo?.prdtId;
|
const selectedPrdtId = propsSelectedPrdtId || productInfo?.prdtId;
|
||||||
const isMockMode = BUYNOW_CONFIG.isMockMode();
|
const isMockMode = BUYNOW_CONFIG.isMockMode();
|
||||||
|
|
||||||
|
// 🔴 selectedPatnrId, selectedPrdtId 변경 감지
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('[BuyOption] 🔴 SELECTED IDS CHANGED:', {
|
||||||
|
selectedPatnrId,
|
||||||
|
selectedPrdtId,
|
||||||
|
propsSelectedPatnrId,
|
||||||
|
propsSelectedPrdtId,
|
||||||
|
});
|
||||||
|
}, [selectedPatnrId, selectedPrdtId, propsSelectedPatnrId, propsSelectedPrdtId]);
|
||||||
|
|
||||||
|
// 🔍 Redux 상태와 계산된 값 확인
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('[BuyOption] 🔍 REDUX STATE & CALCULATED VALUES:', {
|
||||||
|
isMockMode,
|
||||||
|
propsSelectedPatnrId,
|
||||||
|
propsSelectedPrdtId,
|
||||||
|
propsProductInfo: !!propsProductInfo,
|
||||||
|
reduxProductInfo: !!reduxProductInfo,
|
||||||
|
productInfo: !!productInfo,
|
||||||
|
reduxProductInfoPatnrId: productInfo?.patnrId,
|
||||||
|
reduxProductInfoPrdtId: productInfo?.prdtId,
|
||||||
|
finalSelectedPatnrId: selectedPatnrId,
|
||||||
|
finalSelectedPrdtId: selectedPrdtId,
|
||||||
|
reduxProductOptionInfos: reduxProductOptionInfos,
|
||||||
|
fullProductInfo: productInfo,
|
||||||
|
});
|
||||||
|
}, [selectedPatnrId, selectedPrdtId, isMockMode, productInfo, reduxProductOptionInfos, reduxProductInfo, propsProductInfo]);
|
||||||
|
|
||||||
// Mock Mode에서 옵션 데이터 처리
|
// Mock Mode에서 옵션 데이터 처리
|
||||||
const productOptionInfos = useMemo(() => {
|
const productOptionInfos = useMemo(() => {
|
||||||
console.log('[BuyOption] productOptionInfos useMemo - isMockMode:', isMockMode);
|
console.log('[BuyOption] productOptionInfos useMemo - isMockMode:', isMockMode);
|
||||||
@@ -402,8 +451,33 @@ const BuyOption = ({
|
|||||||
// 필수 데이터 로드 (SingleOption과 동일)
|
// 필수 데이터 로드 (SingleOption과 동일)
|
||||||
// Mock Mode: API 호출 스킵 (Mock 데이터만 사용)
|
// Mock Mode: API 호출 스킵 (Mock 데이터만 사용)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 디버그 로그: useEffect 진입
|
||||||
|
console.log('[BuyOption] 📍 DATA LOADING useEffect - Dependencies changed', {
|
||||||
|
isMockMode,
|
||||||
|
selectedPatnrId,
|
||||||
|
selectedPrdtId,
|
||||||
|
userNumber,
|
||||||
|
hasSelectedPatnrId: !!selectedPatnrId,
|
||||||
|
hasSelectedPrdtId: !!selectedPrdtId,
|
||||||
|
});
|
||||||
|
|
||||||
// API Mode: 실제 API 호출
|
// API Mode: 실제 API 호출
|
||||||
if (!isMockMode) {
|
if (!isMockMode) {
|
||||||
|
// ⚠️ 필수 값 검증
|
||||||
|
if (!selectedPatnrId || !selectedPrdtId) {
|
||||||
|
console.warn('[BuyOption] ⚠️ [getProductOption] MISSING REQUIRED PARAMS', {
|
||||||
|
selectedPatnrId,
|
||||||
|
selectedPrdtId,
|
||||||
|
reason: 'selectedPatnrId or selectedPrdtId is missing',
|
||||||
|
});
|
||||||
|
return; // API 호출하지 않음
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[BuyOption] 🔄 [getProductOption] API CALL - Dispatching getProductOption', {
|
||||||
|
patnrId: selectedPatnrId,
|
||||||
|
prdtId: selectedPrdtId,
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
getProductOption({
|
getProductOption({
|
||||||
patnrId: selectedPatnrId,
|
patnrId: selectedPatnrId,
|
||||||
@@ -411,6 +485,12 @@ const BuyOption = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('[BuyOption] 🔄 [getProductCouponSearch] API CALL - Dispatching getProductCouponSearch', {
|
||||||
|
patnrId: selectedPatnrId,
|
||||||
|
prdtId: selectedPrdtId,
|
||||||
|
mbrNo: userNumber,
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
getProductCouponSearch({
|
getProductCouponSearch({
|
||||||
patnrId: selectedPatnrId,
|
patnrId: selectedPatnrId,
|
||||||
@@ -418,6 +498,11 @@ const BuyOption = ({
|
|||||||
mbrNo: userNumber,
|
mbrNo: userNumber,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Mock Mode 상태 로그
|
||||||
|
console.log('[BuyOption] 🚫 Mock Mode Active - Skipping API calls');
|
||||||
|
console.log('[BuyOption] 🚫 [getProductOption] SKIPPED - Mock Mode enabled');
|
||||||
|
console.log('[BuyOption] 🚫 [getProductCouponSearch] SKIPPED - Mock Mode enabled');
|
||||||
}
|
}
|
||||||
// Mock Mode: API 호출 하지 않음
|
// Mock Mode: API 호출 하지 않음
|
||||||
}, [dispatch, selectedPatnrId, selectedPrdtId, userNumber, isMockMode]);
|
}, [dispatch, selectedPatnrId, selectedPrdtId, userNumber, isMockMode]);
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export default function MainView({ className, initService }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TV 배포용: Mock Mode 하드코딩 활성화
|
// TV 배포용: Mock Mode 하드코딩 활성화
|
||||||
// 모든 상품에서 BUY NOW 버튼이 표시됨
|
// 모든 상품에서 BUY NOW 버튼이 표시됨
|
||||||
const mockMode = true;
|
const mockMode = false;
|
||||||
|
|
||||||
// 모듈 변수에 저장 (이후 ProductAllSection 등에서 메모리에서만 읽음)
|
// 모듈 변수에 저장 (이후 ProductAllSection 등에서 메모리에서만 읽음)
|
||||||
BUYNOW_CONFIG.init(mockMode);
|
BUYNOW_CONFIG.init(mockMode);
|
||||||
|
|||||||
Reference in New Issue
Block a user