[251025] feat: usePanelHistory && panelHistoryMiddleware

🕐 커밋 시간: 2025. 10. 25. 09:46:43

📊 변경 통계:
  • 총 파일: 8개
  • 추가: +195줄
  • 삭제: -84줄

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

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/App/App.js
  ~ com.twin.app.shoptime/src/actions/panelHistoryActions.js
  ~ com.twin.app.shoptime/src/hooks/usePanelHistory/usePanelHistory.js
  ~ com.twin.app.shoptime/src/middleware/panelHistoryMiddleware.js
  ~ com.twin.app.shoptime/src/reducers/panelHistoryReducer.js
  ~ com.twin.app.shoptime/src/views/HomePanel/BestSeller/BestSeller.jsx
  ~ com.twin.app.shoptime/src/views/SearchPanel/SearchResultsNew/ItemCard.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 중간 규모 기능 개선
  • 모듈 구조 개선
This commit is contained in:
2025-10-25 09:46:45 +00:00
parent d09a59a86e
commit 728e503976
8 changed files with 378 additions and 82 deletions

View File

@@ -42,6 +42,7 @@ import { getShoptimeTerms } from '../actions/empActions';
import { getHomeMenu, getHomeTerms } from '../actions/homeActions'; import { getHomeMenu, getHomeTerms } from '../actions/homeActions';
import { getMyRecommandedKeyword, getMyUpcomingAlertShow } from '../actions/myPageActions'; import { getMyRecommandedKeyword, getMyUpcomingAlertShow } from '../actions/myPageActions';
import { pushPanel } from '../actions/panelActions'; import { pushPanel } from '../actions/panelActions';
import { enqueuePanelHistory } from '../actions/panelHistoryActions';
import NotSupportedVersion from '../components/NotSupportedVersion/NotSupportedVersion'; import NotSupportedVersion from '../components/NotSupportedVersion/NotSupportedVersion';
import ToastContainer from '../components/TToast/ToastContainer'; import ToastContainer from '../components/TToast/ToastContainer';
import usePrevious from '../hooks/usePrevious'; import usePrevious from '../hooks/usePrevious';
@@ -387,6 +388,17 @@ function AppBase(props) {
console.log('[App.js] initService,httpHeaderRef.current', httpHeaderRef.current); console.log('[App.js] initService,httpHeaderRef.current', httpHeaderRef.current);
console.log('[App.js] haveyInit', haveyInit); console.log('[App.js] haveyInit', haveyInit);
// 앱 초기화 시 HomePanel 자동 기록
console.log('[App.js] Recording initial HomePanel on app start');
dispatch(enqueuePanelHistory(
'homepanel',
{},
'APP_INITIALIZE',
new Date().toISOString(),
false, // fromGNB: false (앱 초기화)
false // fromResetPanel: false (앱 초기화)
));
if (httpHeaderRef.current) { if (httpHeaderRef.current) {
if (haveyInit) { if (haveyInit) {
dispatch(changeAppStatus({ connectionFailed: false })); dispatch(changeAppStatus({ connectionFailed: false }));

View File

@@ -9,16 +9,21 @@ import { types } from './actionTypes';
* Panel을 히스토리에 추가 * Panel을 히스토리에 추가
* @param {string} panelName - Panel 이름 * @param {string} panelName - Panel 이름
* @param {Object} panelInfo - Panel 상태 정보 * @param {Object} panelInfo - Panel 상태 정보
* @param {string} action - 발생한 Redux 액션 ('PUSH' | 'POP' | 'UPDATE' | 'RESET') * @param {string} action - 발생한 Redux 액션 ('PUSH' | 'POP' | 'UPDATE' | 'RESET' | 'APP_INITIALIZE')
* @param {string} timestamp - 타임스탬프 (선택사항)
* @param {boolean} fromGNB - GNB를 통해 이동했는지 여부
* @param {boolean} fromResetPanel - RESET_PANELS를 통해 이동했는지 여부
* @returns {Object} Redux action * @returns {Object} Redux action
*/ */
export const enqueuePanelHistory = (panelName, panelInfo = {}, action = 'PUSH') => ({ export const enqueuePanelHistory = (panelName, panelInfo = {}, action = 'PUSH', timestamp, fromGNB = false, fromResetPanel = false) => ({
type: types.ENQUEUE_PANEL_HISTORY, type: types.ENQUEUE_PANEL_HISTORY,
payload: { payload: {
panelName, panelName,
panelInfo, panelInfo,
action, action,
timestamp: new Date().toISOString(), timestamp: timestamp || new Date().toISOString(),
fromGNB,
fromResetPanel,
}, },
}); });

View File

@@ -0,0 +1,185 @@
/**
* src/actions/panelNavigationActions.js
* Panel navigation 순차 처리를 위한 액션 크리에이터
*
* Chrome 68 호환성을 위한 callback-free 순차 네비게이션
*/
import { pushPanel, updatePanel } from './panelActions';
import { panel_names } from '../utils/Config';
/**
* 상품 클릭 시 순차 네비게이션 (Search → Detail)
* @param {string} patnrId - 파트너 ID
* @param {string} prdtId - 상품 ID
* @param {string} searchQuery - 검색어
* @param {string} currentSpot - 현재 spotlight ID
* @param {Object} additionalInfo - 추가 패널 정보
* @returns {Function} Redux thunk function
*/
export const navigateToDetailPanel = (
patnrId,
prdtId,
searchQuery,
currentSpot,
additionalInfo = {}
) => (dispatch, getState) => {
// 현재 상태에서 lastPanelAction 카운트 저장
const currentActionCount = getState().panels.lastPanelAction || 0;
console.log('[PanelNavigation] Starting navigation to detail:', {
patnrId,
prdtId,
searchQuery,
currentSpot,
currentActionCount
});
// 1. 먼저 현재 패널(예: SearchPanel) 업데이트
dispatch(updatePanel({
name: panel_names.SEARCH_PANEL,
panelInfo: {
searchVal: searchQuery,
currentSpot,
tab: 0,
...additionalInfo
}
}));
// 2. Redux store 구독하여 상태 변화 감지
// 직접 store 접근 대신 타이머 기반 방식 사용 (Chrome 68 호환)
const storeUnsubscribe = (() => {
let isUnsubscribed = false;
const checkStateChange = () => {
if (isUnsubscribed) return;
const newState = getState();
const newActionCount = newState.panels.lastPanelAction || 0;
// updatePanel이 완료되면 (action count가 변경되면)
if (newActionCount !== currentActionCount) {
console.log('[PanelNavigation] UpdatePanel completed, pushing DetailPanel');
// 구독 해제
isUnsubscribed = true;
// 3. DetailPanel push
dispatch(pushPanel({
name: panel_names.DETAIL_PANEL,
panelInfo: {
patnrId,
prdtId,
fromSearch: true,
searchQuery,
...additionalInfo
}
}));
}
};
// 즉시 한번 확인하고, 그 후 주기적으로 확인
setTimeout(checkStateChange, 0);
const intervalId = setInterval(checkStateChange, 16); // 60fps
return () => {
isUnsubscribed = true;
clearInterval(intervalId);
};
})();
// 타임아웃 방어 (최대 1초 대기)
setTimeout(() => {
storeUnsubscribe();
console.log('[PanelNavigation] Timeout fallback, pushing DetailPanel');
dispatch(pushPanel({
name: panel_names.DETAIL_PANEL,
panelInfo: {
patnrId,
prdtId,
fromSearch: true,
searchQuery,
...additionalInfo
}
}));
}, 1000);
};
/**
* HomePanel에서 상품 클릭 시 순차 네비게이션
* @param {string} patnrId - 파트너 ID
* @param {string} prdtId - 상품 ID
* @param {Object} additionalInfo - 추가 패널 정보
* @returns {Function} Redux thunk function
*/
export const navigateToDetailFromHome = (
patnrId,
prdtId,
additionalInfo = {}
) => (dispatch, getState) => {
const currentActionCount = getState().panels.lastPanelAction || 0;
console.log('[PanelNavigation] Starting navigation from home:', {
patnrId,
prdtId,
currentActionCount
});
dispatch(updatePanel({
name: panel_names.HOME_PANEL,
panelInfo: {
lastSelectedProduct: { patnrId, prdtId },
...additionalInfo
}
}));
const storeUnsubscribe = (() => {
let isUnsubscribed = false;
const checkStateChange = () => {
if (isUnsubscribed) return;
const newState = getState();
const newActionCount = newState.panels.lastPanelAction || 0;
if (newActionCount !== currentActionCount) {
console.log('[PanelNavigation] HomePanel update completed, pushing DetailPanel');
isUnsubscribed = true;
dispatch(pushPanel({
name: panel_names.DETAIL_PANEL,
panelInfo: {
patnrId,
prdtId,
fromHome: true,
...additionalInfo
}
}));
}
};
setTimeout(checkStateChange, 0);
const intervalId = setInterval(checkStateChange, 16);
return () => {
isUnsubscribed = true;
clearInterval(intervalId);
};
})();
setTimeout(() => {
storeUnsubscribe();
console.log('[PanelNavigation] Timeout fallback from home');
dispatch(pushPanel({
name: panel_names.DETAIL_PANEL,
panelInfo: {
patnrId,
prdtId,
fromHome: true,
...additionalInfo
}
}));
}, 1000);
};

View File

@@ -35,17 +35,11 @@ export const usePanelHistory = () => {
* [current, previous, older, oldest, ...] * [current, previous, older, oldest, ...]
*/ */
const getHistory = useCallback(() => { const getHistory = useCallback(() => {
const { history, head, size } = panelHistory; const { history } = panelHistory;
if (size === 0) return []; if (history.length === 0) return [];
const sorted = []; // history는 오래된 순서이므로, 최신 순으로 뒤집기
for (let i = 0; i < size; i++) { return [...history].reverse();
const index = (head - i + 10) % 10;
if (history[index]) {
sorted.push(history[index]);
}
}
return sorted;
}, [panelHistory]); }, [panelHistory]);
/** /**
@@ -55,10 +49,11 @@ export const usePanelHistory = () => {
*/ */
const getHistoryAt = useCallback( const getHistoryAt = useCallback(
(distance) => { (distance) => {
const { history, head, size } = panelHistory; const { history } = panelHistory;
if (distance >= size || distance < 0) return null; if (distance >= history.length || distance < 0) return null;
const index = (head - distance + 10) % 10; // history는 오래된 순서, distance는 최신부터 계산
const index = history.length - 1 - distance;
return history[index] || null; return history[index] || null;
}, },
[panelHistory] [panelHistory]
@@ -76,7 +71,7 @@ export const usePanelHistory = () => {
return { pattern: 'no-panel', confidence: 0 }; return { pattern: 'no-panel', confidence: 0 };
} }
// 🔽 Back navigation: A → B → A // Back navigation: A → B → A
if ( if (
current && current &&
older && older &&
@@ -92,7 +87,7 @@ export const usePanelHistory = () => {
}; };
} }
// 🔽 Detail flow: Search/Home → Detail // Detail flow: Search/Home → Detail
if (current.panelName === 'detailpanel' && previous) { if (current.panelName === 'detailpanel' && previous) {
if (previous.panelName === 'searchpanel') { if (previous.panelName === 'searchpanel') {
return { return {
@@ -108,7 +103,7 @@ export const usePanelHistory = () => {
} }
} }
// 🔽 Modal overlay: base → modal // Modal overlay: base → modal
const modalPanels = ['imagepanel', 'mediapanel', 'playerpanel']; const modalPanels = ['imagepanel', 'mediapanel', 'playerpanel'];
if (modalPanels.includes(current.panelName) && previous) { if (modalPanels.includes(current.panelName) && previous) {
return { return {
@@ -119,7 +114,7 @@ export const usePanelHistory = () => {
}; };
} }
// 🔽 Search flow: Home → Search // Search flow: Home → Search
if (current.panelName === 'searchpanel' && previous?.panelName === 'homepanel') { if (current.panelName === 'searchpanel' && previous?.panelName === 'homepanel') {
return { return {
pattern: 'home-to-search', pattern: 'home-to-search',
@@ -127,7 +122,7 @@ export const usePanelHistory = () => {
}; };
} }
// 🔽 Cart flow: Product → Cart // Cart flow: Product → Cart
if (current.panelName === 'cartpanel' && previous) { if (current.panelName === 'cartpanel' && previous) {
return { return {
pattern: 'product-to-cart', pattern: 'product-to-cart',
@@ -135,7 +130,7 @@ export const usePanelHistory = () => {
}; };
} }
// 🔽 Checkout flow: Cart → Checkout // Checkout flow: Cart → Checkout
if (current.panelName === 'checkoutpanel' && previous?.panelName === 'cartpanel') { if (current.panelName === 'checkoutpanel' && previous?.panelName === 'cartpanel') {
return { return {
pattern: 'cart-to-checkout', pattern: 'cart-to-checkout',

View File

@@ -3,7 +3,7 @@
* Panel history 자동 추적 middleware * Panel history 자동 추적 middleware
* *
* Panel action (PUSH, POP, UPDATE, RESET)을 감지하고 * Panel action (PUSH, POP, UPDATE, RESET)을 감지하고
* 자동으로 panel history에 기록합니다. * 자동으로 panel history에 기록
*/ */
import { types } from '../actions/actionTypes'; import { types } from '../actions/actionTypes';
@@ -17,6 +17,37 @@ import { enqueuePanelHistory, clearPanelHistory } from '../actions/panelHistoryA
* @returns {Function} middleware function * @returns {Function} middleware function
*/ */
export const panelHistoryMiddleware = (store) => (next) => (action) => { export const panelHistoryMiddleware = (store) => (next) => (action) => {
// 모든 PANEL 관련 액션 로깅
if (action.type && (
action.type.includes('PANEL') ||
action.type === 'CLEAR_PANEL_HISTORY'
)) {
const caller = new Error().stack.split('\n')[1]?.trim();
console.log(`[PANEL DEBUG] ${action.type} from: ${caller}`);
console.log(' Payload:', action.payload);
}
// GNB 호출 식별을 위한 helper 함수
const isGNBCall = () => {
const stack = new Error().stack;
if (!stack) return false;
const stackLines = stack.split('\n');
for (const line of stackLines) {
if (line.includes('TabLayout.jsx') ||
line.includes('TIconButton.jsx')) {
return true;
}
}
return false;
};
// 임시: CLEAR_PANEL_HISTORY 무력화
if (action.type === 'CLEAR_PANEL_HISTORY') {
console.log('[PANEL DEBUG] CLEAR_PANEL_HISTORY BLOCKED!');
return action; // 액션을 전달하지 않음
}
// 원래 액션 실행 // 원래 액션 실행
const result = next(action); const result = next(action);
@@ -32,7 +63,19 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
const { name: panelName, panelInfo = {} } = action.payload; const { name: panelName, panelInfo = {} } = action.payload;
if (panelName) { if (panelName) {
store.dispatch(enqueuePanelHistory(panelName, panelInfo, 'PUSH')); const isGNB = isGNBCall();
console.log('[PANEL] PUSH_PANEL:', { panelName, panelInfo, isGNB, timestamp: new Date().toISOString() });
store.dispatch(enqueuePanelHistory(panelName, panelInfo, 'PUSH', new Date().toISOString(), isGNB, false));
// PanelHistory 상태 로그 (state 업데이트 후)
const logPanelHistoryAfter = () => {
const stateAfter = store.getState();
const panelHistoryAfter = stateAfter.panelHistory;
console.log('[PANEL_HISTORY] After PUSH_PANEL:', panelHistoryAfter);
};
// state 업데이트가 완료된 후 로그
Promise.resolve().then(logPanelHistoryAfter);
} }
} }
break; break;
@@ -44,7 +87,19 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
if (panels.length > 0) { if (panels.length > 0) {
const topPanel = panels[panels.length - 1]; const topPanel = panels[panels.length - 1];
if (topPanel && topPanel.name) { if (topPanel && topPanel.name) {
store.dispatch(enqueuePanelHistory(topPanel.name, topPanel.panelInfo || {}, 'POP')); const isGNB = isGNBCall();
console.log('[PANEL] POP_PANEL:', { panelName: topPanel.name, panelInfo: topPanel.panelInfo || {}, isGNB, timestamp: new Date().toISOString() });
store.dispatch(enqueuePanelHistory(topPanel.name, topPanel.panelInfo || {}, 'POP', new Date().toISOString(), isGNB, false));
// PanelHistory 상태 로그 (state 업데이트 후)
const logPanelHistoryAfter = () => {
const stateAfter = store.getState();
const panelHistoryAfter = stateAfter.panelHistory;
console.log('[PANEL_HISTORY] After POP_PANEL:', panelHistoryAfter);
};
// state 업데이트가 완료된 후 로그
Promise.resolve().then(logPanelHistoryAfter);
} }
} }
break; break;
@@ -56,15 +111,76 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
const { name: panelName, panelInfo = {} } = action.payload; const { name: panelName, panelInfo = {} } = action.payload;
if (panelName) { if (panelName) {
store.dispatch(enqueuePanelHistory(panelName, panelInfo, 'UPDATE')); const isGNB = isGNBCall();
console.log('[PANEL] UPDATE_PANEL:', { panelName, panelInfo, isGNB, timestamp: new Date().toISOString() });
store.dispatch(enqueuePanelHistory(panelName, panelInfo, 'UPDATE', new Date().toISOString(), isGNB, false));
// PanelHistory 상태 로그 (state 업데이트 후)
const logPanelHistoryAfter = () => {
const stateAfter = store.getState();
const panelHistoryAfter = stateAfter.panelHistory;
console.log('[PANEL_HISTORY] After UPDATE_PANEL:', panelHistoryAfter);
};
// state 업데이트가 완료된 후 로그
Promise.resolve().then(logPanelHistoryAfter);
} }
} }
break; break;
} }
// RESET_PANELS: 히스토리 초기화 // RESET_PANELS: GNB 네비게이션 또는 완전 초기화
case types.RESET_PANELS: { case types.RESET_PANELS: {
store.dispatch(clearPanelHistory()); console.log('[PANEL] RESET_PANELS:', {
payload: action.payload,
timestamp: new Date().toISOString()
});
console.log('[PANEL_HISTORY] Before RESET_PANELS:', store.getState().panelHistory);
// 모든 RESET_PANELS를 기록
const isGNB = isGNBCall();
if (action.payload && action.payload.length > 0) {
// payload가 있는 경우 (GNB 탭, 패널 내 네비게이션 등)
const firstPanel = action.payload[0];
if (firstPanel && firstPanel.name) {
console.log('[PANEL_DEBUG] RESET_PANELS to:', firstPanel.name, { isGNB, fromResetPanel: true });
// RESET 이동을 히스토리에 기록
store.dispatch(enqueuePanelHistory(
firstPanel.name,
firstPanel.panelInfo || {},
'RESET',
new Date().toISOString(),
isGNB, // TabLayout/TIconButton이면 true
true // fromResetPanel: 항상 true
));
}
} else {
// 완전 초기화 (payload가 없는 경우 - HomePanel, 앱 초기화 등)
console.log('[PANEL_DEBUG] Complete panel history reset (payload empty)', { isGNB, fromResetPanel: true });
store.dispatch(clearPanelHistory());
// HomePanel 초기화 기록 (앱 시작 시)
console.log('[PANEL_DEBUG] Recording initial HomePanel');
store.dispatch(enqueuePanelHistory(
'homepanel',
{},
'APP_INITIALIZE',
new Date().toISOString(),
isGNB, // TIconButton Home 버튼이면 true
true // fromResetPanel: true
));
}
// PanelHistory 상태 로그 (초기화 후)
const logPanelHistoryAfter = () => {
const stateAfter = store.getState();
const panelHistoryAfter = stateAfter.panelHistory;
console.log('[PANEL_HISTORY] After RESET_PANELS:', panelHistoryAfter);
};
// state 업데이트가 완료된 후 로그
Promise.resolve().then(logPanelHistoryAfter);
break; break;
} }

View File

@@ -10,51 +10,49 @@ import { types } from '../actions/actionTypes';
// 초기 상태 // 초기 상태
const initialState = { const initialState = {
history: new Array(10).fill(null), // 10개 ring buffer history: [], // 최대 10개의 히스토리 배열 (오래된 순서)
head: -1, // 현재 위치 (-1 ~ 9)
size: 0, // 현재 저장된 개수 (0 ~ 10)
current: null, // 현재 panel entry current: null, // 현재 panel entry
previous: null, // 이전 panel entry previous: null, // 이전 panel entry
hasTransition: false, // panel 전환 여부 hasTransition: false, // panel 전환 여부
}; };
/** /**
* Panel을 Ring buffer에 추가 * Panel을 히스토리 배열에 추가
* @param {Object} state - 현재 상태 * @param {Object} state - 현재 상태
* @param {string} panelName - 추가할 panel 이름 * @param {string} panelName - 추가할 panel 이름
* @param {Object} panelInfo - panel 정보 * @param {Object} panelInfo - panel 정보
* @param {string} action - Redux 액션 타입 ('PUSH' | 'POP' | 'UPDATE') * @param {string} action - Redux 액션 타입 ('PUSH' | 'POP' | 'UPDATE')
* @param {string} timestamp - ISO 8601 timestamp * @param {string} timestamp - ISO 8601 timestamp
* @param {boolean} fromGNB - GNB를 통해 이동했는지 여부
* @param {boolean} fromResetPanel - RESET_PANELS를 통해 이동했는지 여부
* @returns {Object} 새로운 상태 * @returns {Object} 새로운 상태
*/ */
const enqueuePanel = (state, panelName, panelInfo, action, timestamp) => { const enqueuePanel = (state, panelName, panelInfo, action, timestamp, fromGNB = false, fromResetPanel = false) => {
// State 불변성 유지
const newHistory = state.history.map((item) => item);
// 다음 위치 계산 (ring buffer 순환)
const newHead = (state.head + 1) % 10;
// 새 entry 생성 // 새 entry 생성
const newEntry = { const newEntry = {
panelName, panelName,
panelInfo: { ...panelInfo }, // Deep copy panelInfo: { ...panelInfo }, // Deep copy
timestamp, timestamp,
action, action,
fromGNB, // GNB 이동 여부
fromResetPanel, // RESET_PANELS 이동 여부
}; };
// Ring buffer에 저장 // 최대 10개 유지하며 새로운 히스토리 생성
newHistory[newHead] = newEntry; let newHistory;
if (state.history.length >= 10) {
// Size 업데이트 (최대 10) // 10개가 차면 첫 번째 요소 제거하고 추가
const newSize = Math.min(state.size + 1, 10); newHistory = [...state.history.slice(1), newEntry];
} else {
// 10개 미만이면 그냥 추가
newHistory = [...state.history, newEntry];
}
// 이전 panel 추출 // 이전 panel 추출
const previousEntry = state.current; const previousEntry = state.current;
return { return {
history: newHistory, history: newHistory,
head: newHead,
size: newSize,
current: newEntry, current: newEntry,
previous: previousEntry, previous: previousEntry,
hasTransition: previousEntry !== null && previousEntry.panelName !== newEntry.panelName, hasTransition: previousEntry !== null && previousEntry.panelName !== newEntry.panelName,
@@ -68,7 +66,7 @@ export const panelHistoryReducer = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
// 🔽 Panel history에 새 entry 추가 // 🔽 Panel history에 새 entry 추가
case types.ENQUEUE_PANEL_HISTORY: { case types.ENQUEUE_PANEL_HISTORY: {
const { panelName, panelInfo = {}, action: actionType, timestamp } = action.payload; const { panelName, panelInfo = {}, action: actionType, timestamp, fromGNB = false, fromResetPanel = false } = action.payload;
// 입력값 검증 // 입력값 검증
if (!panelName || typeof panelName !== 'string') { if (!panelName || typeof panelName !== 'string') {
@@ -85,12 +83,14 @@ export const panelHistoryReducer = (state = initialState, action) => {
panelInfo: { ...panelInfo }, // panelInfo만 업데이트 panelInfo: { ...panelInfo }, // panelInfo만 업데이트
timestamp, timestamp,
action: actionType, // 최신 action 기록 action: actionType, // 최신 action 기록
fromGNB: fromGNB || state.current.fromGNB, // GNB 플래그 유지 또는 업데이트
fromResetPanel: fromResetPanel || state.current.fromResetPanel, // ResetPanel 플래그 유지 또는 업데이트
}, },
}; };
} }
// 🔽 다른 panelName이면 새로운 버퍼 항목 추가 // 🔽 다른 panelName이면 새로운 버퍼 항목 추가
return enqueuePanel(state, panelName, panelInfo, actionType, timestamp); return enqueuePanel(state, panelName, panelInfo, actionType, timestamp, fromGNB, fromResetPanel);
} }
// 🔽 History 초기화 // 🔽 History 초기화
@@ -98,10 +98,8 @@ export const panelHistoryReducer = (state = initialState, action) => {
return { ...initialState }; return { ...initialState };
} }
// 🔽 Panel reset 시 history도 리셋 // 🔽 RESET_PANELS는 middleware에서 직접 처리하므로 여기서는 초기화하지 않음
case types.RESET_PANELS: { // GNB 네비게이션을 위해 히스토리를 유지해야 함
return { ...initialState };
}
// 🔽 Panel history 명시적 리셋 // 🔽 Panel history 명시적 리셋
case types.RESET_PANEL_HISTORY: { case types.RESET_PANEL_HISTORY: {
@@ -120,18 +118,11 @@ export const selectPanelHistory = (state) => state.panelHistory;
export const selectCurrentPanel = (state) => state.panelHistory.current; export const selectCurrentPanel = (state) => state.panelHistory.current;
export const selectPreviousPanel = (state) => state.panelHistory.previous; export const selectPreviousPanel = (state) => state.panelHistory.previous;
export const selectPanelHistoryList = (state) => { export const selectPanelHistoryList = (state) => {
const { history, head, size } = state.panelHistory; const { history } = state.panelHistory;
if (size === 0) return []; if (history.length === 0) return [];
// 최신순으로 정렬된 히스토리 반환 // 최신순으로 정렬된 히스토리 반환 (오래된 순서를 뒤집기)
const sorted = []; return [...history].reverse();
for (let i = 0; i < size; i++) {
const index = (head - i + 10) % 10;
if (history[index]) {
sorted.push(history[index]);
}
}
return sorted;
}; };
export default panelHistoryReducer; export default panelHistoryReducer;

View File

@@ -20,6 +20,7 @@ import {
pushPanel, pushPanel,
updatePanel, updatePanel,
} from '../../../actions/panelActions'; } from '../../../actions/panelActions';
import { navigateToDetailFromHome } from '../../../actions/panelNavigationActions';
import SectionTitle from '../../../components/SectionTitle/SectionTitle'; import SectionTitle from '../../../components/SectionTitle/SectionTitle';
import Tag from '../../../components/TItemCard/Tag'; import Tag from '../../../components/TItemCard/Tag';
import TItemCard from '../../../components/TItemCard/TItemCard'; import TItemCard from '../../../components/TItemCard/TItemCard';

View File

@@ -5,6 +5,7 @@ import { useDispatch } from 'react-redux';
import Spotlight from '@enact/spotlight'; import Spotlight from '@enact/spotlight';
import { pushPanel, updatePanel } from '../../../actions/panelActions'; import { pushPanel, updatePanel } from '../../../actions/panelActions';
import { navigateToDetailPanel } from '../../../actions/panelNavigationActions';
import TItemCardNew from '../../../components/TItemCard/TItemCard.new'; import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
import TScroller from '../../../components/TScroller/TScroller'; import TScroller from '../../../components/TScroller/TScroller';
import { panel_names } from '../../../utils/Config'; import { panel_names } from '../../../utils/Config';
@@ -19,25 +20,15 @@ const ItemCard = ({ onClick, itemInfo, searchQuery }) => {
if (onClick) { if (onClick) {
onClick(ev); onClick(ev);
} }
dispatch(
updatePanel({ // 순차 네비게이션 사용 (Chrome 68 호환)
name: panel_names.SEARCH_PANEL, dispatch(navigateToDetailPanel(
panelInfo: { patnrId,
searchVal: searchQuery, prdtId,
currentSpot, searchQuery,
tab: 0, currentSpot,
}, { tab: 0 }
}) ));
);
dispatch(
pushPanel({
name: panel_names.DETAIL_PANEL,
panelInfo: {
patnrId: patnrId,
prdtId: prdtId,
},
})
);
}, },
[onClick, dispatch, searchQuery] [onClick, dispatch, searchQuery]
); );