diff --git a/src/main/ipc/handlers.ts b/src/main/ipc/handlers.ts index 155bec0..003a436 100644 --- a/src/main/ipc/handlers.ts +++ b/src/main/ipc/handlers.ts @@ -2,6 +2,7 @@ import { BrowserWindow, ipcMain, shell, dialog } from 'electron' import fs from 'fs' import { createSubWindow } from '../windows/subWindow' import axios from 'axios' +import { getMainWindow } from '../windows/mainWindow' /** * NOTE: IPC Handler 한 번에 등록, 관리 @@ -35,6 +36,14 @@ export function registerIpcHandlers(mainWindow?: BrowserWindow): void { win?.close() }) + // 연결 목록 업데이트 신호 중계 + ipcMain.on('connections-updated', () => { + const mainWindow = getMainWindow() + if (mainWindow) { + mainWindow.webContents.send('connections-updated') + } + }) + // 파일 저장 핸들러: SQL ipcMain.on('save-sql', async (_event, sqlContent: string) => { const window = BrowserWindow.getFocusedWindow() diff --git a/src/renderer/src/components/connection-wizard/wizard-modal.tsx b/src/renderer/src/components/connection-wizard/wizard-modal.tsx index 7b4215f..12944eb 100644 --- a/src/renderer/src/components/connection-wizard/wizard-modal.tsx +++ b/src/renderer/src/components/connection-wizard/wizard-modal.tsx @@ -148,35 +148,31 @@ export function ConnectionWizard(): JSX.Element { const filteredPayload = Object.fromEntries(Object.entries(payload).filter(([, v]) => v != null)) - api - .post('/api/user/db/create/profile', filteredPayload) - .then((response) => { - const id = response.data.id as string - setConnectionDetail((prev) => ({ - ...prev, - id: id - })) - createAnnotation(id) - }) - .catch(() => { - toast.error('데이터베이스 연결 생성 중 오류가 발생했습니다.') - }) - .finally(() => { - // NOTE: 페이지 새로고침 -> 저장된거 불러오도록 - window.location.reload() - onClose() - }) + try { + const response = await api.post('/api/user/db/create/profile', filteredPayload) + const id = response.data.id as string + await createAnnotation(id) + + // 메인 프로세스를 통해 메인 윈도우에 변경 사항을 알립니다. + window.electron.ipcRenderer.send('connections-updated') + toast.success('데이터베이스 연결이 성공적으로 저장되었습니다.') + onClose() + } catch (error) { + toast.error('데이터베이스 연결 저장 중 오류가 발생했습니다.') + console.error('Failed to save connection:', error) + } } - const createAnnotation = (db_profile_id: string): void => { - api - .post('/api/annotations/create', { + const createAnnotation = async (db_profile_id: string): Promise => { + try { + await api.post('/api/annotations/create', { db_profile_id: db_profile_id }) - .then(() => {}) - .catch(() => { - toast.error('어노테이션 생성 중 오류가 발생했습니다.') - }) + } catch (error) { + // 어노테이션 생성 실패는 일단 에러 토스트만 띄우고 플로우는 계속 진행시킵니다. + toast.error('어노테이션 생성 중 오류가 발생했습니다.') + console.error('Failed to create annotation:', error) + } } return ( diff --git a/src/renderer/src/components/layout/side-bar.tsx b/src/renderer/src/components/layout/side-bar.tsx index 6753bfb..7a8e2ab 100644 --- a/src/renderer/src/components/layout/side-bar.tsx +++ b/src/renderer/src/components/layout/side-bar.tsx @@ -5,6 +5,10 @@ import { cn } from '@/lib/utils' import type { NavItem } from '../workspace/types' import { useLocation, useNavigate } from 'react-router-dom' +interface SidebarProps { + hasConnections: boolean +} + const bottomNavItems: NavItem[] = [ { id: 'settings', @@ -40,7 +44,7 @@ function NavButton({ item }: { item: NavItem }): React.JSX.Element { 'size-10 rounded-lg', item.active && 'bg-neutral-700 outline-1 outline-offset-[-1px] outline-white/20', !item.active && 'hover:bg-neutral-700/50', - item.disabled && 'cursor-not-allowed' + item.disabled && 'cursor-not-allowed opacity-50' )} onClick={item.onClick} disabled={item.disabled} @@ -57,7 +61,7 @@ function NavButton({ item }: { item: NavItem }): React.JSX.Element { ) } -export function Sidebar(): React.JSX.Element { +export function Sidebar({ hasConnections }: SidebarProps): React.JSX.Element { const navigate = useNavigate() const location = useLocation() @@ -72,9 +76,9 @@ export function Sidebar(): React.JSX.Element { id: 'tags', icon: Tag, active: location.pathname === '/erd', - disabled: false, + disabled: !hasConnections, onClick: (): void | Promise => navigate('/erd') - } // TODO: DB 연결 후에 disabled: false + } ] return ( diff --git a/src/renderer/src/components/workspace/main-page.tsx b/src/renderer/src/components/workspace/main-page.tsx index a083b76..50f190f 100644 --- a/src/renderer/src/components/workspace/main-page.tsx +++ b/src/renderer/src/components/workspace/main-page.tsx @@ -1,15 +1,50 @@ +import { useEffect, useState, useCallback } from 'react' import { Sidebar } from '../layout/side-bar' import WorkSpace from './workspace' import { WorkspaceEmptyState } from './workspace-empty-state' +import { ConnectionProfile } from '@renderer/types/database' +import { ApiResponse } from '@renderer/types' +import { api } from '@renderer/utils/api' +import { toast } from 'sonner' export function MainPage(): React.JSX.Element { - // TODO: DB 연결 상태에 따라 WorkspaceEmptyState 또는 WorkSpace를 렌더링해야 함 - const isConnected = true // 이 값은 실제 DB 연결 상태에 따라 동적으로 변경되어야 함 + const [connections, setConnections] = useState([]) + + const checkConnections = useCallback(async (): Promise => { + try { + const res = await api.get>('/api/user/db/find/all') + if (res && Array.isArray(res.data)) { + setConnections(res.data) + } else { + throw new Error('Invalid API response format') + } + } catch (error) { + toast.error('DB 연결 목록을 불러오는 데 실패했습니다.') + console.error('[MainPage] DB 연결 목록 조회 실패:', error) + } + }, []) + + useEffect(() => { + checkConnections() + + // connections-updated 신호를 수신하여 연결 목록을 다시 불러옵니다. + const unsubscribe = window.electron.ipcRenderer.on('connections-updated', () => { + console.log('[MainPage] Received connections-updated signal. Refetching connections...') + checkConnections() + }) + + // 컴포넌트가 언마운트될 때 리스너를 정리합니다. + return () => { + if (typeof unsubscribe === 'function') { + unsubscribe() + } + } + }, [checkConnections]) return (
- - {isConnected ? : } + 0} /> + {connections.length > 0 ? : }
) } diff --git a/src/renderer/src/components/workspace/workspace.tsx b/src/renderer/src/components/workspace/workspace.tsx index 278208e..642205a 100644 --- a/src/renderer/src/components/workspace/workspace.tsx +++ b/src/renderer/src/components/workspace/workspace.tsx @@ -5,7 +5,6 @@ import { QueryPanel, type QueryResultData, type ActiveTab } from './query-panel' import { api } from '@renderer/utils/api' import { toast } from 'sonner' import { ConnectionProfile } from '@renderer/types/database' -import { ApiResponse } from '@renderer/types' // API 응답 타입을 Workspace 레벨에서 정의합니다. interface QueryApiResponse { @@ -15,13 +14,13 @@ interface QueryApiResponse { } } -const WorkSpace = (): React.JSX.Element => { - // --- 기존 상태 --- - const [connections, setConnections] = useState([]) - const [activeConnection, setActiveConnection] = useState(null) - const [isConnectionLoading, setIsConnectionLoading] = useState(true) +interface WorkSpaceProps { + connections: ConnectionProfile[] +} - // --- QueryPanel로부터 이동된 상태 --- +const WorkSpace = ({ connections }: WorkSpaceProps): React.JSX.Element => { + // --- 상태 관리 --- + const [activeConnection, setActiveConnection] = useState(null) const [query, setQuery] = useState( 'SELECT p.ProductName, SUM(sod.sales_quantity) as total_quantity_sold, SUM(sod.sales_quantity * sod.UnitPrice) as total_revenue FROM Products p JOIN SalesOrderDetails sod ON p.ProductID = sod.ProductID GROUP BY p.ProductID, p.ProductName ORDER BY total_revenue DESC LIMIT 5;' ) @@ -30,30 +29,18 @@ const WorkSpace = (): React.JSX.Element => { const [queryError, setQueryError] = useState(null) const [activeTab, setActiveTab] = useState('editor') + // MainPage로부터 받은 connections prop이 변경될 때 activeConnection을 설정합니다. useEffect(() => { - const fetchConnections = async (): Promise => { - try { - const res = await api.get>('/api/user/db/find/all') - if (res && Array.isArray(res.data)) { - const fetchedConnections = res.data - setConnections(fetchedConnections) - if (fetchedConnections.length > 0) { - setActiveConnection(fetchedConnections[0]) - } - } else { - throw new Error('Invalid API response format') - } - } catch (error) { - toast.error('DB 연결 목록을 불러오는 데 실패했습니다.') - console.error('[Workspace] DB 연결 목록 조회 실패:', error) - } finally { - setIsConnectionLoading(false) - } + const isActiveConnectionValid = connections.some((c) => c.id === activeConnection?.id) + + if (!isActiveConnectionValid && connections.length > 0) { + setActiveConnection(connections[0]) + } else if (connections.length === 0) { + setActiveConnection(null) } - fetchConnections() - }, []) + }, [connections, activeConnection]) - // --- QueryPanel로부터 이동된 쿼리 실행 함수 --- + // --- 핸들러 --- const handleExecuteQuery = async ( queryToExecute: string, chatMessageId?: string @@ -92,36 +79,31 @@ const WorkSpace = (): React.JSX.Element => { } } - // --- AI 채팅 패널에서 호출할 함수 --- const handleAiQueryExecute = (sql: string, chatMessageId: string): void => { - setQuery(sql) // 쿼리 편집기 내용 업데이트 - handleExecuteQuery(sql, chatMessageId) // 쿼리 실행 + setQuery(sql) + handleExecuteQuery(sql, chatMessageId) } return (
- +
handleExecuteQuery(query)} />