[251102] fix: MainView rendering Panel Update

🕐 커밋 시간: 2025. 11. 02. 06:59:07

📊 변경 통계:
  • 총 파일: 4개
  • 추가: +86줄
  • 삭제: -35줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/actions/mockCartActions.js

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.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-02 06:59:10 +09:00
parent 4fb13fcfaa
commit dda368ab65
4 changed files with 287 additions and 34 deletions

View File

@@ -0,0 +1,202 @@
import { BUYNOW_CONFIG } from '../utils/BuyNowConfig';
import { createMockCartListData, addMockCartItem, removeMockCartItem, updateMockCartItemQuantity } from '../utils/BuyNowDataManipulator';
// Mock Cart Action Types
export const MOCK_CART_TYPES = {
INIT_MOCK_CART: 'mockCart/INIT_MOCK_CART',
ADD_TO_MOCK_CART: 'mockCart/ADD_TO_MOCK_CART',
REMOVE_FROM_MOCK_CART: 'mockCart/REMOVE_FROM_MOCK_CART',
UPDATE_MOCK_CART_ITEM: 'mockCart/UPDATE_MOCK_CART_ITEM',
CLEAR_MOCK_CART: 'mockCart/CLEAR_MOCK_CART',
SET_MOCK_CART_QUANTITY: 'mockCart/SET_MOCK_CART_QUANTITY',
};
/**
* Mock 장바구니 초기화
* BuyOption에서 ADD TO CART 시 호출
*/
export const initializeMockCart = (productData, optionInfo = {}, quantity = 1) => (dispatch, getState) => {
if (!BUYNOW_CONFIG.isMockMode()) {
return;
}
console.log('[MockCartActions] initializeMockCart - productData:', productData);
// 초기 Mock 장바구니 데이터 생성
const mockCartData = createMockCartListData(productData, optionInfo, quantity);
dispatch({
type: MOCK_CART_TYPES.INIT_MOCK_CART,
payload: {
items: mockCartData,
lastAction: {
type: 'init',
data: productData,
timestamp: Date.now(),
},
},
});
};
/**
* Mock 장바구니에 상품 추가
* @param {Object} productData - 상품 데이터
* @param {Object} optionInfo - 옵션 정보
* @param {number} quantity - 수량
*/
export const addToMockCart = (productData, optionInfo = {}, quantity = 1) => (dispatch, getState) => {
if (!BUYNOW_CONFIG.isMockMode()) {
return;
}
console.log('[MockCartActions] addToMockCart - productData:', productData);
// Mock 장바구니 데이터 생성
const newCartItem = addMockCartItem(productData, optionInfo, quantity);
dispatch({
type: MOCK_CART_TYPES.ADD_TO_MOCK_CART,
payload: {
item: newCartItem,
lastAction: {
type: 'add',
data: newCartItem,
timestamp: Date.now(),
},
},
});
};
/**
* Mock 장바구니에서 상품 제거
* @param {string} prodSno - 상품 고유번호
*/
export const removeFromMockCart = (prodSno) => (dispatch, getState) => {
if (!BUYNOW_CONFIG.isMockMode()) {
return;
}
console.log('[MockCartActions] removeFromMockCart - prodSno:', prodSno);
dispatch({
type: MOCK_CART_TYPES.REMOVE_FROM_MOCK_CART,
payload: {
prodSno,
lastAction: {
type: 'remove',
data: { prodSno },
timestamp: Date.now(),
},
},
});
};
/**
* Mock 장바구니 상품 수량 업데이트
* @param {string} prodSno - 상품 고유번호
* @param {number} quantity - 새로운 수량
*/
export const updateMockCartItem = (prodSno, quantity) => (dispatch, getState) => {
if (!BUYNOW_CONFIG.isMockMode()) {
return;
}
console.log('[MockCartActions] updateMockCartItem - prodSno:', prodSno, 'quantity:', quantity);
const updatedItem = updateMockCartItemQuantity(prodSno, quantity);
dispatch({
type: MOCK_CART_TYPES.UPDATE_MOCK_CART_ITEM,
payload: {
item: updatedItem,
prodSno,
quantity,
lastAction: {
type: 'update',
data: { prodSno, quantity },
timestamp: Date.now(),
},
},
});
};
/**
* Mock 장바구니 상품 수량 직접 설정
* @param {string} prodSno - 상품 고유번호
* @param {number} quantity - 설정할 수량
*/
export const setMockCartItemQuantity = (prodSno, quantity) => (dispatch, getState) => {
if (!BUYNOW_CONFIG.isMockMode()) {
return;
}
if (quantity <= 0) {
// 수량이 0이면 상품 제거
dispatch(removeFromMockCart(prodSno));
return;
}
console.log('[MockCartActions] setMockCartItemQuantity - prodSno:', prodSno, 'quantity:', quantity);
const updatedItem = updateMockCartItemQuantity(prodSno, quantity);
dispatch({
type: MOCK_CART_TYPES.SET_MOCK_CART_QUANTITY,
payload: {
item: updatedItem,
prodSno,
quantity,
lastAction: {
type: 'set_quantity',
data: { prodSno, quantity },
timestamp: Date.now(),
},
},
});
};
/**
* Mock 장바구니 전체 비우기
*/
export const clearMockCart = () => (dispatch, getState) => {
if (!BUYNOW_CONFIG.isMockMode()) {
return;
}
console.log('[MockCartActions] clearMockCart');
dispatch({
type: MOCK_CART_TYPES.CLEAR_MOCK_CART,
payload: {
lastAction: {
type: 'clear',
timestamp: Date.now(),
},
},
});
};
/**
* Mock 장바구니 데이터 재설정 (초기 상태로 복원)
*/
export const resetMockCart = () => (dispatch, getState) => {
if (!BUYNOW_CONFIG.isMockMode()) {
return;
}
console.log('[MockCartActions] resetMockCart');
// 기본 Mock 장바구니 데이터로 재설정
const defaultMockCartData = createMockCartListData();
dispatch({
type: MOCK_CART_TYPES.INIT_MOCK_CART,
payload: {
items: defaultMockCartData,
lastAction: {
type: 'reset',
timestamp: Date.now(),
},
},
});
};

View File

@@ -51,7 +51,7 @@ export default function CheckOutPanel({ panelInfo }) {
// console.log 오버라이드를 위한 ref
const originalConsoleLog = useRef();
// 컴포넌트 마운트 시 console.log 오버라이드
// 컴포넌트 마운트 시 console.log 오버라이드 및 PlayerPanel 충돌 방지
useEffect(() => {
// 원래 함수 저장
originalConsoleLog.current = console.log;
@@ -66,12 +66,36 @@ export default function CheckOutPanel({ panelInfo }) {
console.log('%c[BuyOption][CheckOutPanel] ▶️ Component mounted START', 'background: blue; color: white; font-weight: bold; padding: 5px;');
console.log('[BuyOption][CheckOutPanel] panelInfo:', panelInfo);
console.log('[BuyOption][CheckOutPanel] panelInfo.logInfo:', panelInfo?.logInfo);
console.log('[BuyOption][CheckOutPanel] Current panels:', panels?.map((p) => ({ name: p.name, hasModal: !!p.panelInfo?.modal })));
// PlayerPanel 충돌 방지: PlayerPanel이 있고 modal 상태면 비활성화
const playerPanelIndex = panels?.findIndex(p =>
p.name === Config.panel_names.PLAYER_PANEL ||
p.name === Config.panel_names.PLAYER_PANEL_NEW ||
p.name === Config.panel_names.MEDIA_PANEL
);
if (playerPanelIndex >= 0) {
console.log('[BuyOption][CheckOutPanel] 🚨 PlayerPanel/MediaPanel detected at index:', playerPanelIndex);
console.log('[BuyOption][CheckOutPanel] PlayerPanel info:', panels[playerPanelIndex]);
// PlayerPanel/MediaPanel 상태를 비활성화하여 CheckOutPanel과의 충돌 방지
if (panels[playerPanelIndex].panelInfo?.modal) {
console.log('[BuyOption][CheckOutPanel] 🔄 Disabling modal PlayerPanel to prevent conflicts');
// 필요하다면 여기서 PlayerPanel 상태를 비활성화하는 액션을 디스패치할 수 있음
// dispatch(updatePanel({
// name: panels[playerPanelIndex].name,
// panelInfo: { ...panels[playerPanelIndex].panelInfo, isActive: false }
// }));
}
}
return () => {
// console.log 원래 함수로 복원
console.log = originalConsoleLog.current;
console.log('[BuyOption][CheckOutPanel] 🔄 Component unmounting - cleaning up');
};
}, []); // 의존성 배열이 비어있어 컴포넌트 마운트 시 한 번만 실행
}, [panels, panelInfo]); // panels와 panelInfo를 의존성에 추가
const dispatch = useDispatch();
const panels = useSelector((state) => state.panels.panels);
console.log(
@@ -317,8 +341,13 @@ export default function CheckOutPanel({ panelInfo }) {
const onBackClick = useCallback(() => {
console.log('[BuyOption][CheckOutPanel] onBackClick called - dispatching popPanel');
dispatch(popPanel());
}, [dispatch]);
console.log('[BuyOption][CheckOutPanel] Current panels before pop:', panels?.map((p) => p.name));
// 확실한 popPanel 호출 - CheckOutPanel만 제거
dispatch(popPanel(Config.panel_names.CHECKOUT_PANEL));
console.log('[BuyOption][CheckOutPanel] popPanel dispatched for CHECKOUT_PANEL');
}, [dispatch, panels]);
const toggleOrderSideBar = useCallback(() => {
console.log('[BuyOption][CheckOutPanel] toggleOrderSideBar called - current state:', orderSideBarOpen);
@@ -440,12 +469,17 @@ export default function CheckOutPanel({ panelInfo }) {
}
if (!orderSideBarOpen && !offerSideBarOpen) {
console.log('[BuyOption][CheckOutPanel] Calling popPanel()');
dispatch(popPanel());
console.log('[BuyOption][CheckOutPanel] onCancelCheckoutPanel - calling popPanel for CHECKOUT_PANEL');
console.log('[BuyOption][CheckOutPanel] Current panels before cancel pop:', panels?.map((p) => p.name));
// 확실한 popPanel 호출 - CheckOutPanel만 제거
dispatch(popPanel(Config.panel_names.CHECKOUT_PANEL));
e.stopPropagation();
console.log('[BuyOption][CheckOutPanel] popPanel dispatched for CHECKOUT_PANEL from cancel');
}
},
[orderSideBarOpen, offerSideBarOpen, dispatch]
[orderSideBarOpen, offerSideBarOpen, dispatch, panels]
);
const doSendLogPaymentEntry = useCallback(() => {

View File

@@ -618,6 +618,9 @@ const BuyOption = ({
console.log('[BuyOption] Mock Mode - Simulating checkout');
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());

View File

@@ -198,33 +198,42 @@ export default function MainView({ className, initService }) {
if (panels && panels.length > 0) {
let renderingPanels = [];
// 3-layer 구조 체크: PlayerPanel + DetailPanel + MediaPanel(modal)
const hasThreeLayerStructure =
panels.length >= 3 &&
// PlayerPanel이 맨 아래
(panels[panels.length - 3]?.name === Config.panel_names.PLAYER_PANEL ||
panels[panels.length - 3]?.name === Config.panel_names.PLAYER_PANEL_NEW) &&
// DetailPanel이 중간
panels[panels.length - 2]?.name === Config.panel_names.DETAIL_PANEL &&
// MediaPanel modal이 맨 위
panels[panels.length - 1]?.name === Config.panel_names.MEDIA_PANEL &&
panels[panels.length - 1]?.panelInfo?.modal === true;
const topPanel = panels[panels.length - 1];
if (hasThreeLayerStructure) {
console.log(
'[MainView] Rendering 3-layer structure: PlayerPanel + DetailPanel + MediaPanel'
);
renderingPanels = panels.slice(-3);
} else if (
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL ||
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL_NEW ||
panels[panels.length - 1]?.name === Config.panel_names.MEDIA_PANEL ||
panels[panels.length - 2]?.name === Config.panel_names.PLAYER_PANEL ||
panels[panels.length - 2]?.name === Config.panel_names.MEDIA_PANEL
) {
renderingPanels = panels.slice(-2);
} else {
renderingPanels = panels.slice(-1);
// CheckOutPanel은 독립적으로 항상 단독 렌더링
if (topPanel?.name === Config.panel_names.CHECKOUT_PANEL) {
console.log('[MainView] CheckOutPanel detected - rendering independently');
renderingPanels = [topPanel]; // CheckOutPanel만 단독으로 렌더링
}
// 기존 3-layer 구조 체크: PlayerPanel + DetailPanel + MediaPanel(modal)
else {
const hasThreeLayerStructure =
panels.length >= 3 &&
// PlayerPanel이 맨 아래
(panels[panels.length - 3]?.name === Config.panel_names.PLAYER_PANEL ||
panels[panels.length - 3]?.name === Config.panel_names.PLAYER_PANEL_NEW) &&
// DetailPanel이 중간
panels[panels.length - 2]?.name === Config.panel_names.DETAIL_PANEL &&
// MediaPanel modal이 맨 위
panels[panels.length - 1]?.name === Config.panel_names.MEDIA_PANEL &&
panels[panels.length - 1]?.panelInfo?.modal === true;
if (hasThreeLayerStructure) {
console.log(
'[MainView] Rendering 3-layer structure: PlayerPanel + DetailPanel + MediaPanel'
);
renderingPanels = panels.slice(-3);
} else if (
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL ||
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL_NEW ||
panels[panels.length - 1]?.name === Config.panel_names.MEDIA_PANEL ||
panels[panels.length - 2]?.name === Config.panel_names.PLAYER_PANEL ||
panels[panels.length - 2]?.name === Config.panel_names.MEDIA_PANEL
) {
renderingPanels = panels.slice(-2);
} else {
renderingPanels = panels.slice(-1);
}
}
return (
<>
@@ -239,8 +248,13 @@ export default function MainView({ className, initService }) {
const Component = panelMap[panel.name];
let isPanelOnTop = false;
// CheckOutPanel은 항상 onTop
if (panel.name === Config.panel_names.CHECKOUT_PANEL) {
isPanelOnTop = true;
console.log('[MainView] CheckOutPanel is always onTop');
}
// 3-layer 케이스: 중간 패널(DetailPanel)이 onTop
if (renderingPanels.length === 3) {
else if (renderingPanels.length === 3) {
if (index === 1) {
// DetailPanel (중간)
isPanelOnTop = true;