From ea48168b1e94c55137c1ba66436133116afd0fb9 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Sun, 5 Apr 2026 13:31:57 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8B=A4=EC=8B=9C=EA=B0=84=20UI=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0:=20=EC=84=B8=EC=85=98=20=EC=99=84=EB=A3=8C/=EB=AA=85?= =?UTF-8?q?=EB=A0=B9=EC=96=B4=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C=20=EB=A0=8C?= =?UTF-8?q?=EB=8D=94=EB=9F=AC=20=EC=9E=90=EB=8F=99=20=EB=A6=AC=EB=A1=9C?= =?UTF-8?q?=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bootstrap: notifyRenderer('app:dataChanged') 이벤트 발행 - session-completed: Dashboard 통계 + History 갱신 - command:selected: CMD 페이지 활성 명령어 갱신 - preload: app.onDataChanged 이벤트 리스너 추가 - DashboardPage/CommandsPage/HistoryPage: onDataChanged 구독하여 자동 loadData() --- src/main/bootstrap.ts | 15 +++++++++++++++ src/preload/index.ts | 6 ++++++ src/renderer/pages/CommandsPage.tsx | 11 ++++++++++- src/renderer/pages/DashboardPage.tsx | 4 +++- src/renderer/pages/HistoryPage.tsx | 6 +++++- 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/main/bootstrap.ts b/src/main/bootstrap.ts index 82abc10..46b9257 100644 --- a/src/main/bootstrap.ts +++ b/src/main/bootstrap.ts @@ -15,6 +15,7 @@ import { getAutoLaunchService } from './services/AutoLaunchService' import { initDatabase } from './db' import { createMainWindow, + getMainWindow, preloadPopupWindows, showHistoryPopup, hideHistoryPopup, @@ -213,6 +214,9 @@ async function initVoiceMode(): Promise { } catch (error) { logger.warn(`Failed to save history: ${error instanceof Error ? error.message : String(error)}`) } + + // 렌더러 UI 갱신 알림 (Dashboard 통계 + History 목록) + notifyRenderer('app:dataChanged', { type: 'session-completed' }) }) voiceMode.on('session-cancelled', ({ reason }) => { @@ -229,6 +233,14 @@ async function initVoiceMode(): Promise { }) } +/** 메인 윈도우 렌더러에 UI 갱신 이벤트 전송 */ +function notifyRenderer(channel: string, data?: Record): void { + const win = getMainWindow() + if (win && !win.isDestroyed()) { + win.webContents.send(channel, data ?? {}) + } +} + async function initLLMPolling(): Promise { const llm = getLocalLLMService() llm.startPolling() @@ -275,6 +287,9 @@ function setupCommandPopupIPC(): void { configSet('activeInstructionId' as keyof import('@shared/types').AppConfig, data.id as never) configSet('defaultLLMAction', 'custom') logger.info(`Active command set: ${data.name} (${data.id})`) + + // CMD 페이지 UI 갱신 알림 + notifyRenderer('app:dataChanged', { type: 'command-changed', activeId: data.id }) }) // 팝업 닫기 diff --git a/src/preload/index.ts b/src/preload/index.ts index 8d52edb..f3ca195 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -243,6 +243,12 @@ const electronAPI = { invoke('instruction:update', params), delete: (params: { id: string }) => invoke('instruction:delete', params), reorder: (params: { ids: string[] }) => invoke('instruction:reorder', params), + }, + + // ── App 이벤트 (실시간 UI 갱신) ──────────────────────── + app: { + onDataChanged: (cb: (data: { type: string; activeId?: string }) => void): Unsubscribe => + on('app:dataChanged', cb), } } as const diff --git a/src/renderer/pages/CommandsPage.tsx b/src/renderer/pages/CommandsPage.tsx index ce26b35..bddb87a 100644 --- a/src/renderer/pages/CommandsPage.tsx +++ b/src/renderer/pages/CommandsPage.tsx @@ -46,7 +46,16 @@ export function CommandsPage(): React.ReactElement { setLoading(false) }, []) - useEffect(() => { loadData() }, [loadData]) + useEffect(() => { + loadData() + const unsub = window.electronAPI.app.onDataChanged((data) => { + if (data.type === 'command-changed' && data.activeId) { + setActiveId(data.activeId) + } + loadData() + }) + return unsub + }, [loadData]) const handleActivate = (id: string) => { const newId = activeId === id ? null : id // 토글: 같은 거 누르면 해제 diff --git a/src/renderer/pages/DashboardPage.tsx b/src/renderer/pages/DashboardPage.tsx index e17d0a8..e949eda 100644 --- a/src/renderer/pages/DashboardPage.tsx +++ b/src/renderer/pages/DashboardPage.tsx @@ -117,7 +117,9 @@ export function DashboardPage(): React.ReactElement { useEffect(() => { loadData() const interval = setInterval(loadData, 30000) - return () => clearInterval(interval) + // 실시간 갱신: 세션 완료/명령어 변경 시 즉시 리로드 + const unsub = window.electronAPI.app.onDataChanged(() => { loadData() }) + return () => { clearInterval(interval); unsub() } }, [loadData]) // 날짜별 그룹핑 diff --git a/src/renderer/pages/HistoryPage.tsx b/src/renderer/pages/HistoryPage.tsx index 95c2f1e..6fce813 100644 --- a/src/renderer/pages/HistoryPage.tsx +++ b/src/renderer/pages/HistoryPage.tsx @@ -54,7 +54,11 @@ export function HistoryPage(): React.ReactElement { setLoading(false) }, [search]) - useEffect(() => { loadData() }, [loadData]) + useEffect(() => { + loadData() + const unsub = window.electronAPI.app.onDataChanged(() => { loadData() }) + return unsub + }, [loadData]) // 날짜별 그룹핑 const groupedEntries = useMemo(() => {