[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 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);
}; };

View File

@@ -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(),
}, },
}) })

View File

@@ -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}>

View File

@@ -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(() => {

View File

@@ -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]);

View File

@@ -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);