🕐 커밋 시간: 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 컴포넌트 아키텍처 개선
454 lines
14 KiB
JavaScript
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();
|
|
};
|
|
};
|