[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:
2025-11-06 18:02:14 +09:00
parent 82641bf17d
commit d5f5b9b166
6 changed files with 163 additions and 6 deletions

View File

@@ -35,14 +35,59 @@ const createRequestThunk =
const query = params(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) => {
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) });
onSuccessExtra(props, dispatch, getState, response);
};
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);
};

View File

@@ -18,6 +18,10 @@ export const showToast = curry(
position = 'bottom-center',
id,
onToastClose, // Toast가 닫힐 때 호출할 콜백
productInfo, // 🚀 BuyOption용 상품 정보
selectedPatnrId, // 🚀 BuyOption용 파트너 ID
selectedPrdtId, // 🚀 BuyOption용 상품 ID
...otherProps // 🚀 추가 props 전달 지원
}) => ({
type: TOAST_ACTIONS.ADD_TOAST,
payload: {
@@ -27,6 +31,10 @@ export const showToast = curry(
duration,
position,
onToastClose, // 콜백 전달
productInfo, // 🚀 BuyOption에 전달할 상품 정보
selectedPatnrId, // 🚀 BuyOption에 전달할 파트너 ID
selectedPrdtId, // 🚀 BuyOption에 전달할 상품 ID
...otherProps, // 🚀 추가 props 지원
timestamp: Date.now(),
},
})

View File

@@ -34,6 +34,9 @@ export default function TToastEnhanced({
id,
onClose, // ToastContainer에서 전달하는 toast 제거 콜백
onToastClose, // payload의 onClose - Toast 닫힐 때 호출할 추가 콜백
productInfo, // 🚀 BuyOption에 전달할 상품 정보
selectedPatnrId, // 🚀 BuyOption에 전달할 파트너 ID
selectedPrdtId, // 🚀 BuyOption에 전달할 상품 ID
...rest
}) {
const dispatch = useDispatch();
@@ -186,7 +189,11 @@ export default function TToastEnhanced({
>
{type === 'buyOption' ? (
<div ref={buyOptionRef} onBlur={handleBuyOptionBlur}>
<BuyOption />
<BuyOption
productInfo={productInfo}
selectedPatnrId={selectedPatnrId}
selectedPrdtId={selectedPrdtId}
/>
</div>
) : (
<div className={css.content}>

View File

@@ -371,6 +371,14 @@ export default function ProductAllSection({
const handleBuyNowClick = useCallback((e) => {
// console.log('[BuyNow] Buy Now button clicked');
e.stopPropagation();
console.log('[ProductAllSection] 🛒 BUY NOW clicked - productData:', {
prdtId: productData?.prdtId,
patnrId: productData?.patnrId,
prdtNm: productData?.prdtNm,
hasProductData: !!productData,
});
dispatch(
showToast({
id: productData.prdtId,
@@ -378,6 +386,10 @@ export default function ProductAllSection({
type: 'buyOption',
duration: 0,
position: 'bottom-center',
// 🚀 BuyOption에 전달할 props 데이터
productInfo: productData,
selectedPatnrId: productData?.patnrId,
selectedPrdtId: productData?.prdtId,
// BuyOption Toast가 닫힐 때 BUY NOW 버튼으로 포커스 복구
onToastClose: () => {
setTimeout(() => {
@@ -386,7 +398,7 @@ export default function ProductAllSection({
},
})
);
}, [dispatch]);
}, [dispatch, productData]);
//닫히도록
const handleCloseToast = useCallback(() => {

View File

@@ -74,7 +74,18 @@ const BuyOption = ({
type,
}) => {
// 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
const originalConsoleLog = useRef(console.log);
@@ -107,6 +118,16 @@ const BuyOption = ({
const productInfo = propsProductInfo || reduxProductInfo;
const reduxProductOptionInfos = useSelector((state) => state.product.prdtOptInfo);
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(
(state) => state.coupon.productCouponSearchData || {}
);
@@ -137,6 +158,34 @@ const BuyOption = ({
const selectedPrdtId = propsSelectedPrdtId || productInfo?.prdtId;
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에서 옵션 데이터 처리
const productOptionInfos = useMemo(() => {
console.log('[BuyOption] productOptionInfos useMemo - isMockMode:', isMockMode);
@@ -402,8 +451,33 @@ const BuyOption = ({
// 필수 데이터 로드 (SingleOption과 동일)
// Mock Mode: API 호출 스킵 (Mock 데이터만 사용)
useEffect(() => {
// 디버그 로그: useEffect 진입
console.log('[BuyOption] 📍 DATA LOADING useEffect - Dependencies changed', {
isMockMode,
selectedPatnrId,
selectedPrdtId,
userNumber,
hasSelectedPatnrId: !!selectedPatnrId,
hasSelectedPrdtId: !!selectedPrdtId,
});
// API Mode: 실제 API 호출
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(
getProductOption({
patnrId: selectedPatnrId,
@@ -411,6 +485,12 @@ const BuyOption = ({
})
);
console.log('[BuyOption] 🔄 [getProductCouponSearch] API CALL - Dispatching getProductCouponSearch', {
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
mbrNo: userNumber,
});
dispatch(
getProductCouponSearch({
patnrId: selectedPatnrId,
@@ -418,6 +498,11 @@ const BuyOption = ({
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 호출 하지 않음
}, [dispatch, selectedPatnrId, selectedPrdtId, userNumber, isMockMode]);

View File

@@ -167,7 +167,7 @@ export default function MainView({ className, initService }) {
useEffect(() => {
// TV 배포용: Mock Mode 하드코딩 활성화
// 모든 상품에서 BUY NOW 버튼이 표시됨
const mockMode = true;
const mockMode = false;
// 모듈 변수에 저장 (이후 ProductAllSection 등에서 메모리에서만 읽음)
BUYNOW_CONFIG.init(mockMode);