사이드바 디자인 다듬기

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

@@ -7,8 +7,6 @@ import { ChartConfigPanel } from "./ChartConfigPanel";
import { VehicleMapConfigPanel } from "./VehicleMapConfigPanel";
import { DatabaseConfig } from "./data-sources/DatabaseConfig";
import { ApiConfig } from "./data-sources/ApiConfig";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -174,137 +172,169 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
return (
<div
className={cn(
"fixed top-14 left-0 z-[100] flex h-[calc(100vh-3.5rem)] w-96 flex-col bg-white shadow-2xl transition-transform duration-300 ease-in-out",
"fixed top-14 left-0 z-[100] flex h-[calc(100vh-3.5rem)] w-80 flex-col bg-gray-50 transition-transform duration-300 ease-in-out",
isOpen ? "translate-x-0" : "translate-x-[-100%]",
)}
>
{/* 헤더 */}
<div className="flex items-center justify-between border-b p-4">
<h3 className="text-lg font-semibold text-gray-900">{element.title} </h3>
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8">
<X className="h-5 w-5" />
</Button>
<div className="flex items-center justify-between bg-white px-3 py-2 shadow-sm">
<div className="flex items-center gap-2">
<div className="bg-primary/10 flex h-6 w-6 items-center justify-center rounded">
<span className="text-primary text-xs font-bold"></span>
</div>
<span className="text-xs font-semibold text-gray-900">{element.title}</span>
</div>
<button
onClick={onClose}
className="flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-gray-100"
>
<X className="h-3.5 w-3.5 text-gray-500" />
</button>
</div>
{/* 본문: 스크롤 가능 영역 */}
<div className="flex-1 overflow-y-auto p-4">
{/* 기본 설정 */}
<div className="mb-4 space-y-3">
{/* 커스텀 제목 입력 */}
<div>
<label className="mb-1 block text-sm font-medium text-gray-700"> </label>
<input
type="text"
value={customTitle}
onChange={(e) => setCustomTitle(e.target.value)}
onKeyDown={(e) => e.stopPropagation()}
placeholder="제목을 입력해주세요."
className="focus:border-primary focus:ring-primary w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:ring-1 focus:outline-none"
/>
</div>
<div className="flex-1 overflow-y-auto p-3">
{/* 기본 설정 카드 */}
<div className="mb-3 rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="space-y-2">
{/* 커스텀 제목 입력 */}
<div>
<input
type="text"
value={customTitle}
onChange={(e) => setCustomTitle(e.target.value)}
onKeyDown={(e) => e.stopPropagation()}
placeholder="위젯 제목"
className="focus:border-primary focus:ring-primary/20 h-8 w-full rounded border border-gray-200 bg-gray-50 px-2 text-xs placeholder:text-gray-400 focus:bg-white focus:ring-1 focus:outline-none"
/>
</div>
{/* 헤더 표시 옵션 */}
<div className="flex items-center gap-2">
<input
type="checkbox"
id="showHeader"
checked={showHeader}
onChange={(e) => setShowHeader(e.target.checked)}
className="text-primary focus:ring-primary h-4 w-4 rounded border-gray-300"
/>
<label htmlFor="showHeader" className="text-sm font-medium text-gray-700">
{/* 헤더 표시 옵션 */}
<label className="flex cursor-pointer items-center gap-2 rounded border border-gray-200 bg-gray-50 px-2 py-1.5 transition-colors hover:border-gray-300">
<input
type="checkbox"
id="showHeader"
checked={showHeader}
onChange={(e) => setShowHeader(e.target.checked)}
className="text-primary focus:ring-primary h-3 w-3 rounded border-gray-300"
/>
<span className="text-xs text-gray-700"> </span>
</label>
</div>
</div>
{/* 헤더 전용 위젯이 아닐 때만 데이터 소스 표시 */}
{/* 헤더 전용 위젯이 아닐 때만 데이터 소스 표시 */}
{!isHeaderOnlyWidget && (
<Tabs
defaultValue={dataSource.type}
onValueChange={(value) => handleDataSourceTypeChange(value as "database" | "api")}
className="w-full"
>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="database"></TabsTrigger>
<TabsTrigger value="api">REST API</TabsTrigger>
</TabsList>
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<TabsContent value="database" className="mt-4 space-y-4">
<DatabaseConfig dataSource={dataSource} onChange={handleDataSourceUpdate} />
<QueryEditor
dataSource={dataSource}
onDataSourceChange={handleDataSourceUpdate}
onQueryTest={handleQueryTest}
/>
<Tabs
defaultValue={dataSource.type}
onValueChange={(value) => handleDataSourceTypeChange(value as "database" | "api")}
className="w-full"
>
<TabsList className="grid h-7 w-full grid-cols-2 bg-gray-100 p-0.5">
<TabsTrigger
value="database"
className="h-6 rounded text-[11px] data-[state=active]:bg-white data-[state=active]:shadow-sm"
>
</TabsTrigger>
<TabsTrigger
value="api"
className="h-6 rounded text-[11px] data-[state=active]:bg-white data-[state=active]:shadow-sm"
>
REST API
</TabsTrigger>
</TabsList>
{/* 차트/지도 설정 */}
{!isSimpleWidget && queryResult && queryResult.rows.length > 0 && (
<div className="mt-4">
{isMapWidget ? (
<VehicleMapConfigPanel
config={chartConfig}
queryResult={queryResult}
onConfigChange={handleChartConfigChange}
/>
) : (
<ChartConfigPanel
config={chartConfig}
queryResult={queryResult}
onConfigChange={handleChartConfigChange}
chartType={element.subtype}
dataSourceType={dataSource.type}
query={dataSource.query}
/>
)}
</div>
)}
</TabsContent>
<TabsContent value="database" className="mt-2 space-y-2">
<DatabaseConfig dataSource={dataSource} onChange={handleDataSourceUpdate} />
<QueryEditor
dataSource={dataSource}
onDataSourceChange={handleDataSourceUpdate}
onQueryTest={handleQueryTest}
/>
<TabsContent value="api" className="mt-4 space-y-4">
<ApiConfig dataSource={dataSource} onChange={handleDataSourceUpdate} onTestResult={handleQueryTest} />
{/* 차트/지도 설정 */}
{!isSimpleWidget && queryResult && queryResult.rows.length > 0 && (
<div className="mt-2">
{isMapWidget ? (
<VehicleMapConfigPanel
config={chartConfig}
queryResult={queryResult}
onConfigChange={handleChartConfigChange}
/>
) : (
<ChartConfigPanel
config={chartConfig}
queryResult={queryResult}
onConfigChange={handleChartConfigChange}
chartType={element.subtype}
dataSourceType={dataSource.type}
query={dataSource.query}
/>
)}
</div>
)}
</TabsContent>
{/* 차트/지도 설정 */}
{!isSimpleWidget && queryResult && queryResult.rows.length > 0 && (
<div className="mt-4">
{isMapWidget ? (
<VehicleMapConfigPanel
config={chartConfig}
queryResult={queryResult}
onConfigChange={handleChartConfigChange}
/>
) : (
<ChartConfigPanel
config={chartConfig}
queryResult={queryResult}
onConfigChange={handleChartConfigChange}
chartType={element.subtype}
dataSourceType={dataSource.type}
query={dataSource.query}
/>
)}
</div>
)}
</TabsContent>
</Tabs>
)}
<TabsContent value="api" className="mt-2 space-y-2">
<ApiConfig dataSource={dataSource} onChange={handleDataSourceUpdate} onTestResult={handleQueryTest} />
{/* 데이터 로드 상태 */}
{queryResult && (
<div className="mt-4">
<Badge variant="default">{queryResult.rows.length} </Badge>
{/* 차트/지도 설정 */}
{!isSimpleWidget && queryResult && queryResult.rows.length > 0 && (
<div className="mt-2">
{isMapWidget ? (
<VehicleMapConfigPanel
config={chartConfig}
queryResult={queryResult}
onConfigChange={handleChartConfigChange}
/>
) : (
<ChartConfigPanel
config={chartConfig}
queryResult={queryResult}
onConfigChange={handleChartConfigChange}
chartType={element.subtype}
dataSourceType={dataSource.type}
query={dataSource.query}
/>
)}
</div>
)}
</TabsContent>
</Tabs>
{/* 데이터 로드 상태 */}
{queryResult && (
<div className="mt-2 flex items-center gap-1.5 rounded bg-green-50 px-2 py-1">
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
<span className="text-[10px] font-medium text-green-700">
{queryResult.rows.length}
</span>
</div>
)}
</div>
)}
</div>
{/* 푸터: 적용 버튼 */}
<div className="flex items-center justify-end gap-2 border-t p-4">
<Button variant="outline" size="sm" onClick={onClose}>
<div className="flex gap-2 bg-white p-2 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
<button
onClick={onClose}
className="flex-1 rounded bg-gray-100 py-2 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-200"
>
</Button>
<Button size="sm" onClick={handleApply} disabled={isHeaderOnlyWidget ? false : !canApply}>
</button>
<button
onClick={handleApply}
disabled={isHeaderOnlyWidget ? false : !canApply}
className="bg-primary hover:bg-primary/90 flex-1 rounded py-2 text-xs font-medium text-white transition-colors disabled:cursor-not-allowed disabled:opacity-50"
>
</Button>
</button>
</div>
</div>
);