사이드바에 탭 방식 적용
This commit is contained in:
parent
bdf9bd0075
commit
2433658e01
|
|
@ -5,13 +5,13 @@ import { DashboardElement, ChartDataSource, ChartConfig, QueryResult } from "./t
|
|||
import { QueryEditor } from "./QueryEditor";
|
||||
import { ChartConfigPanel } from "./ChartConfigPanel";
|
||||
import { VehicleMapConfigPanel } from "./VehicleMapConfigPanel";
|
||||
import { DataSourceSelector } from "./data-sources/DataSourceSelector";
|
||||
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, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { X } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
|
||||
interface ElementConfigSidebarProps {
|
||||
element: DashboardElement | null;
|
||||
|
|
@ -34,7 +34,6 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
|
|||
});
|
||||
const [chartConfig, setChartConfig] = useState<ChartConfig>({});
|
||||
const [queryResult, setQueryResult] = useState<QueryResult | null>(null);
|
||||
const [currentStep, setCurrentStep] = useState<1 | 2>(1);
|
||||
const [customTitle, setCustomTitle] = useState<string>("");
|
||||
const [showHeader, setShowHeader] = useState<boolean>(true);
|
||||
|
||||
|
|
@ -44,7 +43,6 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
|
|||
setDataSource(element.dataSource || { type: "database", connectionType: "current", refreshInterval: 0 });
|
||||
setChartConfig(element.chartConfig || {});
|
||||
setQueryResult(null);
|
||||
setCurrentStep(1);
|
||||
setCustomTitle(element.customTitle || "");
|
||||
setShowHeader(element.showHeader !== false);
|
||||
}
|
||||
|
|
@ -100,25 +98,6 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
|
|||
setChartConfig({});
|
||||
}, []);
|
||||
|
||||
// 다음 단계로 이동
|
||||
const handleNext = useCallback(() => {
|
||||
if (currentStep === 1) {
|
||||
setCurrentStep(2);
|
||||
}
|
||||
}, [currentStep]);
|
||||
|
||||
// 이전 단계로 이동
|
||||
const handlePrev = useCallback(() => {
|
||||
if (currentStep > 1) {
|
||||
setCurrentStep((prev) => (prev - 1) as 1 | 2);
|
||||
}
|
||||
}, [currentStep]);
|
||||
|
||||
// 취소 처리
|
||||
const handleCancel = useCallback(() => {
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
// 적용 처리
|
||||
const handleApply = useCallback(() => {
|
||||
if (!element) return;
|
||||
|
|
@ -184,15 +163,10 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
|
|||
isTitleChanged ||
|
||||
isHeaderChanged ||
|
||||
(isSimpleWidget
|
||||
? currentStep === 2 && queryResult && queryResult.rows.length > 0
|
||||
? queryResult && queryResult.rows.length > 0
|
||||
: isMapWidget
|
||||
? currentStep === 2 &&
|
||||
queryResult &&
|
||||
queryResult.rows.length > 0 &&
|
||||
chartConfig.latitudeColumn &&
|
||||
chartConfig.longitudeColumn
|
||||
: currentStep === 2 &&
|
||||
queryResult &&
|
||||
? queryResult && queryResult.rows.length > 0 && chartConfig.latitudeColumn && chartConfig.longitudeColumn
|
||||
: queryResult &&
|
||||
queryResult.rows.length > 0 &&
|
||||
chartConfig.xAxis &&
|
||||
(isPieChart || isApiSource ? (chartConfig.aggregation === "count" ? true : hasYAxis) : hasYAxis));
|
||||
|
|
@ -214,89 +188,105 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
|
|||
|
||||
{/* 본문: 스크롤 가능 영역 */}
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
{/* 커스텀 제목 입력 */}
|
||||
<div className="mb-4">
|
||||
<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="mb-4 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>
|
||||
</div>
|
||||
|
||||
{/* 진행 상황 표시 */}
|
||||
{!isSimpleWidget && !isHeaderOnlyWidget && (
|
||||
<div className="mb-4 rounded-md bg-gray-50 p-3">
|
||||
<div className="text-sm font-medium text-gray-700">
|
||||
단계 {currentStep} / 2: {currentStep === 1 ? "데이터 소스 선택" : "데이터 설정"}
|
||||
</div>
|
||||
{/* 기본 설정 */}
|
||||
<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 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 헤더 전용 위젯이 아닐 때만 데이터 소스 탭 표시 */}
|
||||
{!isHeaderOnlyWidget && (
|
||||
<div className="space-y-4">
|
||||
{currentStep === 1 && (
|
||||
<DataSourceSelector dataSource={dataSource} onTypeChange={handleDataSourceTypeChange} />
|
||||
)}
|
||||
<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>
|
||||
|
||||
{currentStep === 2 && (
|
||||
<div className="space-y-4">
|
||||
{/* 데이터 설정 */}
|
||||
{dataSource.type === "database" ? (
|
||||
<>
|
||||
<DatabaseConfig dataSource={dataSource} onChange={handleDataSourceUpdate} />
|
||||
<QueryEditor
|
||||
dataSource={dataSource}
|
||||
onDataSourceChange={handleDataSourceUpdate}
|
||||
onQueryTest={handleQueryTest}
|
||||
<TabsContent value="database" className="mt-4 space-y-4">
|
||||
<DatabaseConfig dataSource={dataSource} onChange={handleDataSourceUpdate} />
|
||||
<QueryEditor
|
||||
dataSource={dataSource}
|
||||
onDataSourceChange={handleDataSourceUpdate}
|
||||
onQueryTest={handleQueryTest}
|
||||
/>
|
||||
|
||||
{/* 차트/지도 설정 */}
|
||||
{!isSimpleWidget && queryResult && queryResult.rows.length > 0 && (
|
||||
<div className="mt-4">
|
||||
{isMapWidget ? (
|
||||
<VehicleMapConfigPanel
|
||||
config={chartConfig}
|
||||
queryResult={queryResult}
|
||||
onConfigChange={handleChartConfigChange}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<ApiConfig dataSource={dataSource} onChange={handleDataSourceUpdate} onTestResult={handleQueryTest} />
|
||||
)}
|
||||
) : (
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<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-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>
|
||||
)}
|
||||
|
||||
{/* 데이터 로드 상태 */}
|
||||
|
|
@ -307,32 +297,14 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* 푸터: 단계 이동 및 적용 버튼 */}
|
||||
<div className="flex items-center justify-between border-t p-4">
|
||||
<div className="flex gap-2">
|
||||
{!isSimpleWidget && !isHeaderOnlyWidget && currentStep > 1 && (
|
||||
<Button variant="outline" size="sm" onClick={handlePrev}>
|
||||
<ChevronLeft className="mr-1 h-4 w-4" />
|
||||
이전
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={handleCancel}>
|
||||
취소
|
||||
</Button>
|
||||
{isHeaderOnlyWidget || currentStep === 1 ? (
|
||||
<Button size="sm" onClick={isHeaderOnlyWidget ? handleApply : handleNext}>
|
||||
{isHeaderOnlyWidget ? "적용" : "다음"}
|
||||
{!isHeaderOnlyWidget && <ChevronRight className="ml-1 h-4 w-4" />}
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="sm" onClick={handleApply} disabled={!canApply}>
|
||||
적용
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{/* 푸터: 적용 버튼 */}
|
||||
<div className="flex items-center justify-end gap-2 border-t p-4">
|
||||
<Button variant="outline" size="sm" onClick={onClose}>
|
||||
취소
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleApply} disabled={isHeaderOnlyWidget ? false : !canApply}>
|
||||
적용
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
|
|||
>
|
||||
<Database className="mr-2 h-4 w-4" />
|
||||
<div className="text-left">
|
||||
<div className="font-medium">현재 데이터베이스</div>
|
||||
<div className="text-xs opacity-80">애플리케이션 기본 DB</div>
|
||||
<div className="font-medium">현재 DB</div>
|
||||
<div className="text-xs opacity-80">기본 DB</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
|
|||
>
|
||||
<Server className="mr-2 h-4 w-4" />
|
||||
<div className="text-left">
|
||||
<div className="font-medium">외부 데이터베이스</div>
|
||||
<div className="font-medium">외부 DB</div>
|
||||
<div className="text-xs opacity-80">등록된 외부 커넥션</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
|
@ -183,7 +183,11 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
|
|||
{(dataSource.connectionType === "current" ||
|
||||
(dataSource.connectionType === "external" && dataSource.externalConnectionId)) && (
|
||||
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3">
|
||||
<div className="text-sm text-blue-800">✅ 데이터베이스가 선택되었습니다. 아래에서 SQL 쿼리를 작성하세요.</div>
|
||||
<div className="text-sm text-blue-800">
|
||||
데이터베이스가 선택되었습니다.
|
||||
<br />
|
||||
아래에서 SQL 쿼리를 작성하세요.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue