Compare commits

..

2 Commits

Author SHA1 Message Date
leeheejin 03635ff82e Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node 2025-10-15 13:36:11 +09:00
leeheejin cb2377b8d7 위젯 전용 설정 모달 로직 추가: 차트와 분리하여 충돌 방지
- 간단한 위젯(차량상태/목록, 배송/화물, 기사관리): 쿼리만으로 저장 가능
- 지도 위젯(차량위치지도): 위도/경도 매핑 패널 표시
- 차트: 기존 로직 유지 (차트 설정 필수)
- 모달 z-index 9999로 상향 조정
2025-10-15 13:32:20 +09:00
1 changed files with 94 additions and 42 deletions

View File

@ -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" />