Files
shoptime/com.twin.app.shoptime/src/actions/queuedPanelActions.js
optrader 741c4338ca [251124] fix: Log정리-5
🕐 커밋 시간: 2025. 11. 24. 12:43:58

📊 변경 통계:
  • 총 파일: 40개
  • 추가: +774줄
  • 삭제: -581줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/appDataActions.js
  ~ com.twin.app.shoptime/src/actions/billingActions.js
  ~ com.twin.app.shoptime/src/actions/brandActions.js
  ~ com.twin.app.shoptime/src/actions/cancelActions.js
  ~ com.twin.app.shoptime/src/actions/cardActions.js
  ~ com.twin.app.shoptime/src/actions/cartActions.js
  ~ com.twin.app.shoptime/src/actions/checkoutActions.js
  ~ com.twin.app.shoptime/src/actions/commonActions.js
  ~ com.twin.app.shoptime/src/actions/convertActions.js
  ~ com.twin.app.shoptime/src/actions/couponActions.js
  ~ com.twin.app.shoptime/src/actions/deviceActions.js
  ~ com.twin.app.shoptime/src/actions/empActions.js
  ~ com.twin.app.shoptime/src/actions/eventActions.js
  ~ com.twin.app.shoptime/src/actions/forYouActions.js
  ~ com.twin.app.shoptime/src/actions/homeActions.js
  ~ com.twin.app.shoptime/src/actions/logActions.js
  ~ com.twin.app.shoptime/src/actions/mediaActions.js
  ~ com.twin.app.shoptime/src/actions/mockCartActions.js
  ~ com.twin.app.shoptime/src/actions/myPageActions.js
  ~ com.twin.app.shoptime/src/actions/onSaleActions.js
  ~ com.twin.app.shoptime/src/actions/orderActions.js
  ~ com.twin.app.shoptime/src/actions/panelActions.js
  ~ com.twin.app.shoptime/src/actions/panelNavigationActions.js
  ~ com.twin.app.shoptime/src/actions/pinCodeActions.js
  ~ com.twin.app.shoptime/src/actions/productActions.js
  ~ com.twin.app.shoptime/src/actions/queuedPanelActions.js
  ~ com.twin.app.shoptime/src/actions/searchActions.js
  ~ com.twin.app.shoptime/src/actions/shippingActions.js
  ~ com.twin.app.shoptime/src/actions/voiceActions.js
  ~ com.twin.app.shoptime/src/actions/webSpeechActions.js
  ~ com.twin.app.shoptime/src/reducers/localSettingsReducer.js
  ~ com.twin.app.shoptime/src/reducers/mediaOverlayReducer.js
  ~ com.twin.app.shoptime/src/reducers/mockCartReducer.js
  ~ com.twin.app.shoptime/src/reducers/playReducer.js
  ~ com.twin.app.shoptime/src/reducers/productReducer.js
  ~ com.twin.app.shoptime/src/reducers/videoOverlayReducer.js
  ~ com.twin.app.shoptime/src/views/UserReview/ShowUserReviews.jsx
  ~ com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx
  ~ com.twin.app.shoptime/src/views/UserReview/components/UserReviewsList.jsx
  ~ com.twin.app.shoptime/src/views/UserReview/components/VirtualScrollBar.jsx

🔧 함수 변경 내용:
  📊 Function-level changes summary across 40 files:
    • Functions added: 14
    • Functions modified: 34
    • Functions deleted: 18
  📋 By language:
    • javascript: 40 files, 66 function changes

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 로깅 시스템 개선
  • 설정 관리 시스템 개선
  • UI 컴포넌트 아키텍처 개선
2025-11-24 12:47:57 +09:00

454 lines
14 KiB
JavaScript

import { types } from './actionTypes';
import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
/**
* [251106] 큐 기반 패널 액션들
*
* 이 액션들은 패널 액션 큐를 통해 순차적으로 실행되어,
* 비동기 dispatch로 인한 순서 문제를 해결합니다.
*
* 기존의 pushPanel, popPanel 등과 완전히 호환되며,
* 새로운 큐 시스템이 필요한 경우에만 사용합니다.
*/
// 고유 ID 생성을 위한 카운터
let queueItemId = 0;
/**
* 패널을 큐에 추가하여 순차적으로 실행합니다.
* @param {Object} panel - 패널 정보
* @param {boolean} duplicatable - 중복 허용 여부
* @returns {Object} Redux action
*/
export const pushPanelQueued = (panel, duplicatable = false) => ({
type: types.ENQUEUE_PANEL_ACTION,
payload: {
id: `queue_item_${++queueItemId}_${Date.now()}`,
action: 'PUSH_PANEL',
panel,
duplicatable,
timestamp: Date.now(),
},
});
/**
* 큐를 통해 패널을 제거합니다.
* @param {string} panelName - 제거할 패널 이름 (optional, 없으면 마지막 패널 제거)
* @returns {Object} Redux action
*/
export const popPanelQueued = (panelName = null) => ({
type: types.ENQUEUE_PANEL_ACTION,
payload: {
id: `queue_item_${++queueItemId}_${Date.now()}`,
action: 'POP_PANEL',
panelName,
timestamp: Date.now(),
},
});
/**
* 큐를 통해 패널 정보를 업데이트합니다.
* @param {Object} panelInfo - 업데이트할 패널 정보
* @returns {Object} Redux action
*/
export const updatePanelQueued = (panelInfo) => ({
type: types.ENQUEUE_PANEL_ACTION,
payload: {
id: `queue_item_${++queueItemId}_${Date.now()}`,
action: 'UPDATE_PANEL',
panelInfo,
timestamp: Date.now(),
},
});
/**
* 큐를 통해 패널들을 초기화합니다.
* @param {Array} panels - 초기화할 패널 배열 (optional)
* @returns {Object} Redux action
*/
export const resetPanelsQueued = (panels = null) => ({
type: types.ENQUEUE_PANEL_ACTION,
payload: {
id: `queue_item_${++queueItemId}_${Date.now()}`,
action: 'RESET_PANELS',
panels,
timestamp: Date.now(),
},
});
/**
* 패널 액션 큐를 즉시 비웁니다.
* @returns {Object} Redux action
*/
export const clearPanelQueue = () => ({
type: types.CLEAR_PANEL_QUEUE,
payload: {
timestamp: Date.now(),
},
});
/**
* 패널 액션 큐 처리를 시작합니다.
* (주로 내부 사용용)
* @returns {Object} Redux action
*/
export const processPanelQueue = () => ({
type: types.PROCESS_PANEL_QUEUE,
payload: {
timestamp: Date.now(),
},
});
/**
* 큐 처리 상태를 설정합니다.
* (주로 내부 사용용)
* @param {boolean} isProcessing - 처리 중 상태
* @returns {Object} Redux action
*/
export const setQueueProcessing = (isProcessing) => ({
type: types.SET_QUEUE_PROCESSING,
payload: {
isProcessing,
timestamp: Date.now(),
},
});
/**
* 여러 패널 액션들을 한 번에 큐에 추가합니다.
* @param {Array} actions - 패널 액션 배열
* @returns {Function} dispatch 함수
*/
export const enqueueMultiplePanelActions = (actions) => {
return (dispatch) => {
actions.forEach((action) => {
dispatch(action);
});
// 마지막에 큐 처리 시작
dispatch(processPanelQueue());
};
};
/**
* 패널 시퀀스를 큐에 추가하는 헬퍼 함수
* @param {Array} sequence - 패널 액션 시퀀스
* @returns {Function} dispatch 함수
*/
export const createPanelSequence = (sequence) => {
return (dispatch) => {
const queuedActions = sequence
.map((item) => {
switch (item.type) {
case 'push':
return pushPanelQueued(item.panel, item.duplicatable);
case 'pop':
return popPanelQueued(item.panelName);
case 'update':
return updatePanelQueued(item.panelInfo);
case 'reset':
return resetPanelsQueued(item.panels);
default:
return null;
}
})
.filter(Boolean);
dispatch(enqueueMultiplePanelActions(queuedActions));
};
};
// ===================================================================
// 🔽 [251106] 비동기 액션 완료 처리 기능
// ===================================================================
/**
* 비동기 패널 액션을 큐에 추가합니다.
* API 호출이나 다른 비동기 작업이 포함된 패널 액션을 순차적으로 처리합니다.
*
* @param {Object} config - 비동기 액션 설정
* @param {string} config.id - 액션 고유 ID
* @param {Function} config.asyncAction - 비동기 액션 (dispatch, getState, onSuccess, onFail) => void
* @param {Function} config.onSuccess - 성공 콜백 (response) => void
* @param {Function} config.onFail - 실패 콜백 (error) => void
* @param {Function} config.onFinish - 완료 콜백 (isSuccess, result) => void
* @param {number} config.timeout - 타임아웃 (ms, 기본값: 10000)
* @returns {Function} Redux thunk
*/
export const enqueueAsyncPanelAction = (config) => {
return (dispatch, getState) => {
const actionId = config.id || `async_action_${++queueItemId}_${Date.now()}`;
dlog('[queuedPanelActions] 🔄 ENQUEUE_ASYNC_PANEL_ACTION', {
actionId,
timestamp: Date.now(),
});
dispatch({
type: types.ENQUEUE_ASYNC_PANEL_ACTION,
payload: {
id: actionId,
asyncAction: config.asyncAction,
onSuccess: config.onSuccess,
onFail: config.onFail,
onFinish: config.onFinish,
timeout: config.timeout || 10000,
timestamp: Date.now(),
status: 'pending',
},
});
// 비동기 액션 실행
executeAsyncAction(dispatch, getState, actionId);
};
};
/**
* 비동기 액션을 실행하고 완료 처리를 합니다.
* @param {Function} dispatch - Redux dispatch
* @param {Function} getState - Redux getState
* @param {string} actionId - 액션 ID
*/
const executeAsyncAction = (dispatch, getState, actionId) => {
const state = getState();
const asyncAction = state.panels?.panelActionQueue?.find((item) => item.id === actionId);
if (!asyncAction) {
dwarn('[queuedPanelActions] ⚠️ ASYNC_ACTION_NOT_FOUND', actionId);
return;
}
dlog('[queuedPanelActions] ⚡ EXECUTING_ASYNC_ACTION', actionId);
// 비동기 액션을 Promise로 래핑하여 실행
import('../utils/asyncActionUtils')
.then(({ wrapAsyncAction, withTimeout }) => {
const actionPromise = wrapAsyncAction(asyncAction.asyncAction, { dispatch, getState });
const timeoutPromise = withTimeout(actionPromise, asyncAction.timeout);
timeoutPromise
.then((result) => {
dlog('[queuedPanelActions] 📊 ASYNC_ACTION_RESULT', {
actionId,
success: result.success,
hasError: !!result.error,
errorCode: result.error?.code,
});
if (result.success) {
// 성공 처리
dlog('[queuedPanelActions] ✅ ASYNC_ACTION_SUCCESS', actionId);
// 사용자 정의 성공 콜백 실행
if (asyncAction.onSuccess) {
try {
asyncAction.onSuccess(result.data);
} catch (error) {
derror('[queuedPanelActions] ❌ USER_ON_SUCCESS_ERROR', error);
}
}
// 완료 콜백 실행
if (asyncAction.onFinish) {
try {
asyncAction.onFinish(true, result.data);
} catch (error) {
derror('[queuedPanelActions] ❌ USER_ON_FINISH_ERROR', error);
}
}
// Redux 상태 업데이트
dispatch({
type: types.COMPLETE_ASYNC_PANEL_ACTION,
payload: {
actionId,
result: result.data,
timestamp: Date.now(),
},
});
} else {
// 실패 처리
derror('[queuedPanelActions] ❌ ASYNC_ACTION_FAILED', {
actionId,
error: result.error,
errorCode: result.error?.code,
});
// 사용자 정의 실패 콜백 실행
if (asyncAction.onFail) {
try {
asyncAction.onFail(result.error);
} catch (callbackError) {
derror('[queuedPanelActions] ❌ USER_ON_FAIL_ERROR', callbackError);
}
}
// 완료 콜백 실행
if (asyncAction.onFinish) {
try {
asyncAction.onFinish(false, result.error);
} catch (callbackError) {
derror('[queuedPanelActions] ❌ USER_ON_FINISH_ERROR', callbackError);
}
}
// Redux 상태 업데이트
dispatch({
type: types.FAIL_ASYNC_PANEL_ACTION,
payload: {
actionId,
error: result.error,
timestamp: Date.now(),
},
});
}
})
.catch((error) => {
derror('[queuedPanelActions] 💥 ASYNC_ACTION_EXECUTION_ERROR', { actionId, error });
// 치명적인 에러 처리
if (asyncAction.onFail) {
try {
asyncAction.onFail(error);
} catch (callbackError) {
derror('[queuedPanelActions] ❌ USER_ON_FAIL_ERROR', callbackError);
}
}
if (asyncAction.onFinish) {
try {
asyncAction.onFinish(false, error);
} catch (callbackError) {
derror('[queuedPanelActions] ❌ USER_ON_FINISH_ERROR', callbackError);
}
}
dispatch({
type: types.FAIL_ASYNC_PANEL_ACTION,
payload: {
actionId,
error: {
code: 'EXECUTION_ERROR',
message: error.message || '비동기 액션 실행 중 치명적인 오류 발생',
},
timestamp: Date.now(),
},
});
});
})
.catch((error) => {
derror('[queuedPanelActions] 💥 ASYNC_UTILS_IMPORT_ERROR', error);
// 유틸리티 import 실패 시 기본 처리
if (asyncAction.onFail) {
asyncAction.onFail(error);
}
if (asyncAction.onFinish) {
asyncAction.onFinish(false, error);
}
});
};
/**
* API 호출 후 패널 액션을 실행하는 헬퍼 함수
* @param {Object} config - 설정
* @param {Function} config.apiCall - API 호출 함수 (dispatch, getState, onSuccess, onFail) => void
* @param {Array} config.panelActions - API 성공 후 실행할 패널 액션들
* @param {Function} config.onApiSuccess - API 성공 콜백 (선택)
* @param {Function} config.onApiFail - API 실패 콜백 (선택)
* @returns {Function} Redux thunk
*/
export const createApiWithPanelActions = (config) => {
return enqueueAsyncPanelAction({
asyncAction: (dispatch, getState, onSuccess, onFail) => {
dlog('[queuedPanelActions] 🌐 API_CALL_START');
config.apiCall(dispatch, getState, onSuccess, onFail);
},
onSuccess: (response) => {
dlog('[queuedPanelActions] 🎯 API_SUCCESS_EXECUTING_PANELS');
// API 성공 콜백 실행
if (config.onApiSuccess) {
config.onApiSuccess(response);
}
// 패널 액션들 순차 실행
if (config.panelActions && config.panelActions.length > 0) {
config.panelActions.forEach((panelAction, index) => {
setTimeout(() => {
if (typeof panelAction === 'function') {
dispatch(panelAction(response));
} else {
dispatch(panelAction);
}
}, index * 10); // 10ms 간격으로 실행
});
}
},
onFail: (error) => {
dlog('[queuedPanelActions] 🚫 API_FAILED', error);
// API 실패 콜백 실행
if (config.onApiFail) {
config.onApiFail(error);
}
},
onFinish: (isSuccess, result) => {
dlog('[queuedPanelActions] 🏁 API_WITH_PANELS_COMPLETE', { isSuccess });
},
});
};
/**
* 여러 비동기 액션들을 순차적으로 실행합니다.
* @param {Array} asyncConfigs - 비동기 액션 설정 배열
* @returns {Function} Redux thunk
*/
export const createAsyncPanelSequence = (asyncConfigs) => {
return (dispatch, getState) => {
let currentIndex = 0;
const executeNext = () => {
if (currentIndex >= asyncConfigs.length) {
dlog('[queuedPanelActions] 🎊 ASYNC_SEQUENCE_COMPLETE');
return;
}
const config = asyncConfigs[currentIndex];
dlog('[queuedPanelActions] 📋 EXECUTING_ASYNC_SEQUENCE_ITEM', {
index: currentIndex,
total: asyncConfigs.length,
});
// 현재 액션에 다음 액션 실행 로직 추가
const enhancedConfig = {
...config,
onFinish: (isSuccess, result) => {
// 원래 onFinish 콜백 실행
if (config.onFinish) {
config.onFinish(isSuccess, result);
}
// 성공한 경우에만 다음 액션 실행
if (isSuccess) {
currentIndex++;
setTimeout(executeNext, 50); // 50ms 후 다음 액션 실행
} else {
derror('[queuedPanelActions] ⛔ ASYNC_SEQUENCE_STOPPED_ON_ERROR', {
index: currentIndex,
error: result,
});
}
},
};
dispatch(enqueueAsyncPanelAction(enhancedConfig));
};
// 첫 번째 액션 실행
executeNext();
};
};