Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/main/windows/mainWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ let mainWindow: BrowserWindow | null = null

export function createMainWindow(): void {
mainWindow = new BrowserWindow({
width: 900,
height: 670,
width: 1280,
minWidth: 1280,
height: 832,
minHeight: 832,
show: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HashRouter, Route, Routes } from 'react-router-dom'
import { MainPage } from '@/components/main-page'
import { MainPage } from '@/components/workspace/main-page'
import { ConnectionWizard } from '@/components/connection-wizard/wizard-modal'

function App(): React.JSX.Element {
Expand Down
62 changes: 62 additions & 0 deletions src/renderer/src/components/layout/side-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Button } from '@/components/ui/button'
import AppIcon from '@renderer/assets/icon.svg'
import { Database, Plus, Settings, Tag } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { NavItem } from '../workspace/types'

const topNavItems: NavItem[] = [
{ id: 'database', icon: Database, active: true },
{ id: 'tags', icon: Tag, disabled: true } // TODO: DB 연결 후에 disabled: false
]

const bottomNavItems: NavItem[] = [
{ id: 'settings', icon: Settings },
{ id: 'add', icon: Plus }
]

function NavButton({ item }: { item: NavItem }): React.JSX.Element {
const Icon = item.icon
return (
<Button
variant="ghost"
size="icon"
className={cn(
'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'
)}
onClick={item.onClick}
disabled={item.disabled}
>
<Icon
className={cn(
'size-6',
item.active && 'stroke-neutral-200',
!item.active && !item.disabled && 'stroke-neutral-500',
item.disabled && 'stroke-neutral-700'
)}
/>
</Button>
)
}

export function Sidebar(): React.JSX.Element {
return (
<aside className="h-full flex flex-col bg-zinc-900 p-4 items-center gap-6">
<img src={AppIcon} className="size-6" alt="QGenie" />
<div className="flex-1 flex flex-col justify-between items-start">
<nav className="flex flex-col justify-start items-start gap-2">
{topNavItems.map((item) => (
<NavButton key={item.id} item={item} />
))}
</nav>
<nav className="flex flex-col justify-start items-start gap-2">
{bottomNavItems.map((item) => (
<NavButton key={item.id} item={item} />
))}
</nav>
</div>
</aside>
)
}
27 changes: 0 additions & 27 deletions src/renderer/src/components/main-page.tsx

This file was deleted.

21 changes: 21 additions & 0 deletions src/renderer/src/components/workspace/feature-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { FeatureCardData } from './types'

interface FeatureCardProps {
card: FeatureCardData
}

export function FeatureCard({ card }: FeatureCardProps): React.JSX.Element {
return (
<div className="flex-1 bg-gradient-to-b from-stone-900 to-neutral-800 rounded-[20px] outline-1 outline-offset-[-1px] outline-neutral-700 flex flex-col justify-start items-center overflow-hidden">
<div className="w-full h-48 bg-neutral-700" />
<div className="self-stretch p-5 flex flex-col justify-center items-center gap-4">
<div className="self-stretch text-center text-violet-400 text-xs font-medium font-['Pretendard'] leading-none">
{card.title}
</div>
<div className="self-stretch text-center text-neutral-200 text-sm font-bold font-['Pretendard'] leading-tight">
{card.description}
</div>
</div>
</div>
)
}
11 changes: 11 additions & 0 deletions src/renderer/src/components/workspace/main-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Sidebar } from '../layout/side-bar'
import { WorkspaceEmptyState } from './workspace-empty-state'

export function MainPage(): React.JSX.Element {
return (
<div className="w-screen h-screen bg-zinc-900 flex overflow-hidden">
<Sidebar />
<WorkspaceEmptyState />
</div>
)
}
15 changes: 15 additions & 0 deletions src/renderer/src/components/workspace/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { LucideIcon } from 'lucide-react'

export interface NavItem {
id: string
icon: LucideIcon
active?: boolean
disabled?: boolean
onClick?: () => void
}

export interface FeatureCardData {
title: string
description: React.ReactNode
image?: string
}
77 changes: 77 additions & 0 deletions src/renderer/src/components/workspace/workspace-empty-state.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Plus } from 'lucide-react'
import { FeatureCard } from './feature-card'
import type { FeatureCardData } from './types'

const featureCards: FeatureCardData[] = [
{
title: '자연어 질의',
description: (
<>
SQL을 몰라도 질문할 수 있어요.
<br />
자연어로 입력하면 쿼리를 만들어줘요.
</>
)
},
{
title: '쿼리 실행 결과 확인',
description: (
<>
생성된 SQL을 직접 실행해볼 수 있어요.
<br />
결과는 표로 정리돼 한눈에 보여요.
</>
)
},
{
title: '자동 컬럼 설명 생성',
description: (
<>
AI가 테이블과 컬럼을 분석해
<br />
이해하기 쉬운 설명을 자동으로 생성해줘요.
</>
)
}
]

function handleConnectClick(): void {
window.api.send('open-sub-window', {
width: 800,
height: 610,
route: '/connection-wizard'
})
}

export function WorkspaceEmptyState(): React.JSX.Element {
return (
<main className="flex-1 self-stretch bg-neutral-800 flex flex-col justify-center items-center gap-16 p-16">
<div className="w-full flex flex-col justify-start items-center gap-4">
<h1 className="self-stretch text-center text-neutral-200 text-xl font-bold font-['Pretendard']">
데이터가 연결되지 않았어요
</h1>
<p className="text-center text-zinc-500 text-xs font-medium font-['Pretendard'] leading-normal">
데이터베이스를 연결하고
<br />
질문하거나 쿼리를 작성해보세요.
</p>
</div>

<div className="flex justify-center items-stretch gap-6 w-full max-w-5xl">
{featureCards.map((card) => (
<FeatureCard key={card.title} card={card} />
))}
</div>

<button
onClick={handleConnectClick}
className="pl-5 pr-7 py-3 bg-gradient-to-b from-violet-700 to-violet-800 rounded-2xl outline-2 outline-offset-[-2px] outline-white/20 inline-flex justify-center items-center gap-4 hover:from-violet-600 hover:to-violet-700 transition-colors hover:cursor-pointer"
>
<Plus className="size-4 stroke-neutral-200" />
<span className="text-neutral-200 text-sm font-bold font-['Pretendard'] leading-tight">
데이터베이스 연결하기
</span>
</button>
</main>
)
}