사이드바 디자인 다듬기

This commit is contained in:
dohyeons
2025-10-22 12:48:17 +09:00
parent 8a421cfced
commit 85987af65e
5 changed files with 414 additions and 370 deletions

View File

@@ -12,7 +12,8 @@ import { Card } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Play, Loader2, Database, Code } from "lucide-react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Play, Loader2, Database, Code, ChevronDown, ChevronRight } from "lucide-react";
import { applyQueryFilters } from "./utils/queryHelpers";
interface QueryEditorProps {
@@ -32,6 +33,7 @@ export function QueryEditor({ dataSource, onDataSourceChange, onQueryTest }: Que
const [isExecuting, setIsExecuting] = useState(false);
const [queryResult, setQueryResult] = useState<QueryResult | null>(null);
const [error, setError] = useState<string | null>(null);
const [sampleQueryOpen, setSampleQueryOpen] = useState(false);
// 쿼리 실행
const executeQuery = useCallback(async () => {
@@ -155,55 +157,75 @@ ORDER BY 하위부서수 DESC`,
}, []);
return (
<div className="space-y-6">
<div className="space-y-3">
{/* 쿼리 에디터 헤더 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Database className="h-5 w-5 text-blue-600" />
<h4 className="text-lg font-semibold text-gray-800">SQL </h4>
<div className="flex items-center gap-1.5">
<Database className="h-3.5 w-3.5 text-blue-600" />
<h4 className="text-xs font-semibold text-gray-800">SQL </h4>
</div>
<Button onClick={executeQuery} disabled={isExecuting || !query.trim()} size="sm">
<Button onClick={executeQuery} disabled={isExecuting || !query.trim()} size="sm" className="h-7 text-xs">
{isExecuting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
...
<Loader2 className="mr-1.5 h-3 w-3 animate-spin" />
</>
) : (
<>
<Play className="mr-2 h-4 w-4" />
<Play className="mr-1.5 h-3 w-3" />
</>
)}
</Button>
</div>
{/* 샘플 쿼리 버튼들 */}
<Card className="p-4">
<div className="flex flex-wrap items-center gap-2">
<Label className="text-sm text-gray-600"> :</Label>
<Button variant="outline" size="sm" onClick={() => insertSampleQuery("users")}>
<Code className="mr-2 h-3 w-3" />
</Button>
<Button variant="outline" size="sm" onClick={() => insertSampleQuery("dept")}>
<Code className="mr-2 h-3 w-3" />
</Button>
<Button variant="outline" size="sm" onClick={() => insertSampleQuery("usersByDate")}>
</Button>
<Button variant="outline" size="sm" onClick={() => insertSampleQuery("usersByPosition")}>
</Button>
<Button variant="outline" size="sm" onClick={() => insertSampleQuery("deptHierarchy")}>
</Button>
</div>
</Card>
{/* 샘플 쿼리 아코디언 */}
<Collapsible open={sampleQueryOpen} onOpenChange={setSampleQueryOpen}>
<CollapsibleTrigger className="flex w-full items-center gap-1.5 rounded border border-gray-200 bg-gray-50 px-2 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-100">
{sampleQueryOpen ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
</CollapsibleTrigger>
<CollapsibleContent className="mt-2">
<div className="flex flex-wrap gap-1.5">
<button
onClick={() => insertSampleQuery("users")}
className="flex items-center gap-1 rounded border border-gray-200 bg-white px-2 py-1 text-[11px] transition-colors hover:bg-gray-50"
>
<Code className="h-3 w-3" />
</button>
<button
onClick={() => insertSampleQuery("dept")}
className="flex items-center gap-1 rounded border border-gray-200 bg-white px-2 py-1 text-[11px] transition-colors hover:bg-gray-50"
>
<Code className="h-3 w-3" />
</button>
<button
onClick={() => insertSampleQuery("usersByDate")}
className="rounded border border-gray-200 bg-white px-2 py-1 text-[11px] transition-colors hover:bg-gray-50"
>
</button>
<button
onClick={() => insertSampleQuery("usersByPosition")}
className="rounded border border-gray-200 bg-white px-2 py-1 text-[11px] transition-colors hover:bg-gray-50"
>
</button>
<button
onClick={() => insertSampleQuery("deptHierarchy")}
className="rounded border border-gray-200 bg-white px-2 py-1 text-[11px] transition-colors hover:bg-gray-50"
>
</button>
</div>
</CollapsibleContent>
</Collapsible>
{/* SQL 쿼리 입력 영역 */}
<div className="space-y-2">
<Label>SQL </Label>
<div className="space-y-1.5">
<Label className="text-xs">SQL </Label>
<div className="relative">
<Textarea
value={query}
@@ -213,14 +235,14 @@ ORDER BY 하위부서수 DESC`,
e.stopPropagation();
}}
placeholder="SELECT * FROM your_table WHERE condition = 'value';"
className="h-40 resize-none font-mono text-sm"
className="h-32 resize-none font-mono text-[11px]"
/>
</div>
</div>
{/* 새로고침 간격 설정 */}
<div className="flex items-center gap-3">
<Label className="text-sm"> :</Label>
<div className="flex items-center gap-2">
<Label className="text-xs"> :</Label>
<Select
value={String(dataSource?.refreshInterval ?? 0)}
onValueChange={(value) =>
@@ -232,26 +254,38 @@ ORDER BY 하위부서수 DESC`,
})
}
>
<SelectTrigger className="w-32">
<SelectTrigger className="h-7 w-24 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent className="z-[99999]">
<SelectItem value="0"></SelectItem>
<SelectItem value="10000">10</SelectItem>
<SelectItem value="30000">30</SelectItem>
<SelectItem value="60000">1</SelectItem>
<SelectItem value="300000">5</SelectItem>
<SelectItem value="600000">10</SelectItem>
<SelectItem value="0" className="text-xs">
</SelectItem>
<SelectItem value="10000" className="text-xs">
10
</SelectItem>
<SelectItem value="30000" className="text-xs">
30
</SelectItem>
<SelectItem value="60000" className="text-xs">
1
</SelectItem>
<SelectItem value="300000" className="text-xs">
5
</SelectItem>
<SelectItem value="600000" className="text-xs">
10
</SelectItem>
</SelectContent>
</Select>
</div>
{/* 오류 메시지 */}
{error && (
<Alert variant="destructive">
<Alert variant="destructive" className="py-2">
<AlertDescription>
<div className="text-sm font-medium"></div>
<div className="mt-1 text-sm">{error}</div>
<div className="text-xs font-medium"></div>
<div className="mt-0.5 text-xs">{error}</div>
</AlertDescription>
</Alert>
)}
@@ -259,24 +293,28 @@ ORDER BY 하위부서수 DESC`,
{/* 쿼리 결과 미리보기 */}
{queryResult && (
<Card>
<div className="border-b border-gray-200 bg-gray-50 px-4 py-3">
<div className="border-b border-gray-200 bg-gray-50 px-2 py-1.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700"> </span>
<Badge variant="secondary">{queryResult.rows.length}</Badge>
<div className="flex items-center gap-1.5">
<span className="text-xs font-medium text-gray-700"> </span>
<Badge variant="secondary" className="h-4 text-[10px]">
{queryResult.rows.length}
</Badge>
</div>
<span className="text-xs text-gray-500"> : {queryResult.executionTime}ms</span>
<span className="text-[10px] text-gray-500"> : {queryResult.executionTime}ms</span>
</div>
</div>
<div className="p-3">
<div className="p-2">
{queryResult.rows.length > 0 ? (
<div className="max-h-60 overflow-auto">
<div className="max-h-48 overflow-auto">
<Table>
<TableHeader>
<TableRow>
{queryResult.columns.map((col, idx) => (
<TableHead key={idx}>{col}</TableHead>
<TableHead key={idx} className="h-7 text-[11px]">
{col}
</TableHead>
))}
</TableRow>
</TableHeader>
@@ -284,7 +322,9 @@ ORDER BY 하위부서수 DESC`,
{queryResult.rows.slice(0, 10).map((row, idx) => (
<TableRow key={idx}>
{queryResult.columns.map((col, colIdx) => (
<TableCell key={colIdx}>{String(row[col] ?? "")}</TableCell>
<TableCell key={colIdx} className="py-1 text-[11px]">
{String(row[col] ?? "")}
</TableCell>
))}
</TableRow>
))}
@@ -292,13 +332,13 @@ ORDER BY 하위부서수 DESC`,
</Table>
{queryResult.rows.length > 10 && (
<div className="mt-3 text-center text-xs text-gray-500">
<div className="mt-2 text-center text-[10px] text-gray-500">
... {queryResult.rows.length - 10} ( 10 )
</div>
)}
</div>
) : (
<div className="py-8 text-center text-gray-500"> .</div>
<div className="py-6 text-center text-xs text-gray-500"> .</div>
)}
</div>
</Card>