Compare commits
2 Commits
254a7c5bd5
...
03635ff82e
| Author | SHA1 | Date |
|---|---|---|
|
|
03635ff82e | |
|
|
cb2377b8d7 |
|
|
@ -4,6 +4,7 @@ import React, { useState, useCallback, useEffect } from "react";
|
||||||
import { DashboardElement, ChartDataSource, ChartConfig, QueryResult } from "./types";
|
import { DashboardElement, ChartDataSource, ChartConfig, QueryResult } from "./types";
|
||||||
import { QueryEditor } from "./QueryEditor";
|
import { QueryEditor } from "./QueryEditor";
|
||||||
import { ChartConfigPanel } from "./ChartConfigPanel";
|
import { ChartConfigPanel } from "./ChartConfigPanel";
|
||||||
|
import { VehicleMapConfigPanel } from "./VehicleMapConfigPanel";
|
||||||
import { DataSourceSelector } from "./data-sources/DataSourceSelector";
|
import { DataSourceSelector } from "./data-sources/DataSourceSelector";
|
||||||
import { DatabaseConfig } from "./data-sources/DatabaseConfig";
|
import { DatabaseConfig } from "./data-sources/DatabaseConfig";
|
||||||
import { ApiConfig } from "./data-sources/ApiConfig";
|
import { ApiConfig } from "./data-sources/ApiConfig";
|
||||||
|
|
@ -31,6 +32,17 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
const [chartConfig, setChartConfig] = useState<ChartConfig>(element.chartConfig || {});
|
const [chartConfig, setChartConfig] = useState<ChartConfig>(element.chartConfig || {});
|
||||||
const [queryResult, setQueryResult] = useState<QueryResult | null>(null);
|
const [queryResult, setQueryResult] = useState<QueryResult | null>(null);
|
||||||
const [currentStep, setCurrentStep] = useState<1 | 2>(1);
|
const [currentStep, setCurrentStep] = useState<1 | 2>(1);
|
||||||
|
|
||||||
|
// 차트 설정이 필요 없는 위젯 (쿼리/API만 필요)
|
||||||
|
const isSimpleWidget =
|
||||||
|
element.subtype === "vehicle-status" ||
|
||||||
|
element.subtype === "vehicle-list" ||
|
||||||
|
element.subtype === "delivery-status" ||
|
||||||
|
element.subtype === "driver-management";
|
||||||
|
|
||||||
|
// 지도 위젯 (위도/경도 매핑 필요)
|
||||||
|
const isMapWidget = element.subtype === "vehicle-map";
|
||||||
|
|
||||||
// 주석
|
// 주석
|
||||||
// 모달이 열릴 때 초기화
|
// 모달이 열릴 때 초기화
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -118,21 +130,33 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
const isPieChart = element.subtype === "pie" || element.subtype === "donut";
|
const isPieChart = element.subtype === "pie" || element.subtype === "donut";
|
||||||
const isApiSource = dataSource.type === "api";
|
const isApiSource = dataSource.type === "api";
|
||||||
|
|
||||||
const canSave =
|
const canSave = isSimpleWidget
|
||||||
currentStep === 2 &&
|
? // 간단한 위젯: 2단계에서 쿼리 테스트 후 저장 가능
|
||||||
queryResult &&
|
currentStep === 2 &&
|
||||||
queryResult.rows.length > 0 &&
|
queryResult &&
|
||||||
chartConfig.xAxis &&
|
queryResult.rows.length > 0
|
||||||
(isPieChart || isApiSource
|
: isMapWidget
|
||||||
? // 파이/도넛 차트 또는 REST API: Y축 또는 집계 함수 필요
|
? // 지도 위젯: 위도/경도 매핑 필요
|
||||||
chartConfig.yAxis ||
|
currentStep === 2 &&
|
||||||
(Array.isArray(chartConfig.yAxis) && chartConfig.yAxis.length > 0) ||
|
queryResult &&
|
||||||
chartConfig.aggregation === "count"
|
queryResult.rows.length > 0 &&
|
||||||
: // 일반 차트 (DB): Y축 필수
|
chartConfig.latitudeColumn &&
|
||||||
chartConfig.yAxis || (Array.isArray(chartConfig.yAxis) && chartConfig.yAxis.length > 0));
|
chartConfig.longitudeColumn
|
||||||
|
: // 차트: 기존 로직 (2단계에서 차트 설정 필요)
|
||||||
|
currentStep === 2 &&
|
||||||
|
queryResult &&
|
||||||
|
queryResult.rows.length > 0 &&
|
||||||
|
chartConfig.xAxis &&
|
||||||
|
(isPieChart || isApiSource
|
||||||
|
? // 파이/도넛 차트 또는 REST API: Y축 또는 집계 함수 필요
|
||||||
|
chartConfig.yAxis ||
|
||||||
|
(Array.isArray(chartConfig.yAxis) && chartConfig.yAxis.length > 0) ||
|
||||||
|
chartConfig.aggregation === "count"
|
||||||
|
: // 일반 차트 (DB): Y축 필수
|
||||||
|
chartConfig.yAxis || (Array.isArray(chartConfig.yAxis) && chartConfig.yAxis.length > 0));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col rounded-xl border bg-white shadow-2xl ${
|
className={`flex flex-col rounded-xl border bg-white shadow-2xl ${
|
||||||
currentStep === 1 ? "h-auto max-h-[70vh] w-full max-w-3xl" : "h-[85vh] w-full max-w-5xl"
|
currentStep === 1 ? "h-auto max-h-[70vh] w-full max-w-3xl" : "h-[85vh] w-full max-w-5xl"
|
||||||
|
|
@ -143,7 +167,11 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold text-gray-900">{element.title} 설정</h2>
|
<h2 className="text-xl font-semibold text-gray-900">{element.title} 설정</h2>
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
{currentStep === 1 ? "데이터 소스를 선택하세요" : "쿼리를 실행하고 차트를 설정하세요"}
|
{isSimpleWidget
|
||||||
|
? "데이터 소스를 설정하세요"
|
||||||
|
: currentStep === 1
|
||||||
|
? "데이터 소스를 선택하세요"
|
||||||
|
: "쿼리를 실행하고 차트를 설정하세요"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8">
|
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8">
|
||||||
|
|
@ -151,16 +179,18 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 진행 상황 표시 */}
|
{/* 진행 상황 표시 - 간단한 위젯은 표시 안 함 */}
|
||||||
<div className="border-b bg-gray-50 px-6 py-4">
|
{!isSimpleWidget && (
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="border-b bg-gray-50 px-6 py-4">
|
||||||
<div className="text-sm font-medium text-gray-700">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
단계 {currentStep} / 2: {currentStep === 1 ? "데이터 소스 선택" : "데이터 설정 및 차트 설정"}
|
<div className="text-sm font-medium text-gray-700">
|
||||||
|
단계 {currentStep} / 2: {currentStep === 1 ? "데이터 소스 선택" : "데이터 설정 및 차트 설정"}
|
||||||
|
</div>
|
||||||
|
<Badge variant="secondary">{Math.round((currentStep / 2) * 100)}% 완료</Badge>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="secondary">{Math.round((currentStep / 2) * 100)}% 완료</Badge>
|
<Progress value={(currentStep / 2) * 100} className="h-2" />
|
||||||
</div>
|
</div>
|
||||||
<Progress value={(currentStep / 2) * 100} className="h-2" />
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 단계별 내용 */}
|
{/* 단계별 내용 */}
|
||||||
<div className="flex-1 overflow-auto p-6">
|
<div className="flex-1 overflow-auto p-6">
|
||||||
|
|
@ -169,7 +199,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentStep === 2 && (
|
{currentStep === 2 && (
|
||||||
<div className="grid grid-cols-2 gap-6">
|
<div className={`grid ${isSimpleWidget ? 'grid-cols-1' : 'grid-cols-2'} gap-6`}>
|
||||||
{/* 왼쪽: 데이터 설정 */}
|
{/* 왼쪽: 데이터 설정 */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{dataSource.type === "database" ? (
|
{dataSource.type === "database" ? (
|
||||||
|
|
@ -186,24 +216,44 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 오른쪽: 차트 설정 */}
|
{/* 오른쪽: 설정 패널 */}
|
||||||
<div>
|
{!isSimpleWidget && (
|
||||||
{queryResult && queryResult.rows.length > 0 ? (
|
<div>
|
||||||
<ChartConfigPanel
|
{isMapWidget ? (
|
||||||
config={chartConfig}
|
// 지도 위젯: 위도/경도 매핑 패널
|
||||||
queryResult={queryResult}
|
queryResult && queryResult.rows.length > 0 ? (
|
||||||
onConfigChange={handleChartConfigChange}
|
<VehicleMapConfigPanel
|
||||||
chartType={element.subtype}
|
config={chartConfig}
|
||||||
dataSourceType={dataSource.type}
|
queryResult={queryResult}
|
||||||
/>
|
onConfigChange={handleChartConfigChange}
|
||||||
) : (
|
/>
|
||||||
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-8 text-center">
|
) : (
|
||||||
<div>
|
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-8 text-center">
|
||||||
<div className="mt-1 text-xs text-gray-500">데이터를 가져온 후 차트 설정이 표시됩니다</div>
|
<div>
|
||||||
</div>
|
<div className="mt-1 text-xs text-gray-500">데이터를 가져온 후 지도 설정이 표시됩니다</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
)
|
||||||
|
) : (
|
||||||
|
// 차트: 차트 설정 패널
|
||||||
|
queryResult && queryResult.rows.length > 0 ? (
|
||||||
|
<ChartConfigPanel
|
||||||
|
config={chartConfig}
|
||||||
|
queryResult={queryResult}
|
||||||
|
onConfigChange={handleChartConfigChange}
|
||||||
|
chartType={element.subtype}
|
||||||
|
dataSourceType={dataSource.type}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-8 text-center">
|
||||||
|
<div>
|
||||||
|
<div className="mt-1 text-xs text-gray-500">데이터를 가져온 후 차트 설정이 표시됩니다</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -219,7 +269,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
{currentStep > 1 && (
|
{!isSimpleWidget && currentStep > 1 && (
|
||||||
<Button variant="outline" onClick={handlePrev}>
|
<Button variant="outline" onClick={handlePrev}>
|
||||||
<ChevronLeft className="mr-2 h-4 w-4" />
|
<ChevronLeft className="mr-2 h-4 w-4" />
|
||||||
이전
|
이전
|
||||||
|
|
@ -229,11 +279,13 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
취소
|
취소
|
||||||
</Button>
|
</Button>
|
||||||
{currentStep === 1 ? (
|
{currentStep === 1 ? (
|
||||||
|
// 1단계: 다음 버튼 (모든 타입 공통)
|
||||||
<Button onClick={handleNext}>
|
<Button onClick={handleNext}>
|
||||||
다음
|
다음
|
||||||
<ChevronRight className="ml-2 h-4 w-4" />
|
<ChevronRight className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
|
// 2단계: 저장 버튼 (모든 타입 공통)
|
||||||
<Button onClick={handleSave} disabled={!canSave}>
|
<Button onClick={handleSave} disabled={!canSave}>
|
||||||
<Save className="mr-2 h-4 w-4" />
|
<Save className="mr-2 h-4 w-4" />
|
||||||
저장
|
저장
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue