"use client"; import React, { useState, useCallback, useEffect } from "react"; import { DashboardElement, ChartDataSource, ChartConfig, QueryResult } from "./types"; 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, Save } from "lucide-react"; interface ElementConfigModalProps { element: DashboardElement; isOpen: boolean; onClose: () => void; onSave: (element: DashboardElement) => void; } /** * 요소 설정 모달 컴포넌트 (리팩토링) * - 2단계 플로우: 데이터 소스 선택 → 데이터 설정 및 차트 설정 * - 새로운 데이터 소스 컴포넌트 통합 */ export function ElementConfigModal({ element, isOpen, onClose, onSave }: ElementConfigModalProps) { const [dataSource, setDataSource] = useState( element.dataSource || { type: "database", connectionType: "current", refreshInterval: 0 }, ); const [chartConfig, setChartConfig] = useState(element.chartConfig || {}); const [queryResult, setQueryResult] = useState(null); const [currentStep, setCurrentStep] = useState<1 | 2>(1); const [customTitle, setCustomTitle] = useState(element.customTitle || ""); const [showHeader, setShowHeader] = useState(element.showHeader !== false); // 차트 설정이 필요 없는 위젯 (쿼리/API만 필요) const isSimpleWidget = element.subtype === "todo" || // To-Do 위젯 element.subtype === "booking-alert" || // 예약 알림 위젯 element.subtype === "maintenance" || // 정비 일정 위젯 element.subtype === "document" || // 문서 위젯 element.subtype === "risk-alert" || // 리스크 알림 위젯 element.subtype === "vehicle-status" || element.subtype === "vehicle-list" || element.subtype === "status-summary" || // 커스텀 상태 카드 // element.subtype === "list-summary" || // 커스텀 목록 카드 (다른 분 작업 중 - 임시 주석) element.subtype === "delivery-status" || element.subtype === "delivery-status-summary" || element.subtype === "delivery-today-stats" || element.subtype === "cargo-list" || element.subtype === "customer-issues" || element.subtype === "driver-management" || element.subtype === "work-history" || // 작업 이력 위젯 (쿼리 필요) element.subtype === "transport-stats"; // 커스텀 통계 카드 위젯 (쿼리 필요) // 자체 기능 위젯 (DB 연결 불필요, 헤더 설정만 가능) const isSelfContainedWidget = element.subtype === "weather" || // 날씨 위젯 (외부 API) element.subtype === "exchange" || // 환율 위젯 (외부 API) element.subtype === "calculator"; // 계산기 위젯 (자체 기능) // 지도 위젯 (위도/경도 매핑 필요) const isMapWidget = element.subtype === "vehicle-map" || element.subtype === "map-summary"; // 주석 // 모달이 열릴 때 초기화 useEffect(() => { if (isOpen) { const dataSourceToSet = element.dataSource || { type: "database", connectionType: "current", refreshInterval: 0 }; setDataSource(dataSourceToSet); setChartConfig(element.chartConfig || {}); setQueryResult(null); setCurrentStep(1); setCustomTitle(element.customTitle || ""); setShowHeader(element.showHeader !== false); // showHeader 초기화 // 쿼리가 이미 있으면 자동 실행 if (dataSourceToSet.type === "database" && dataSourceToSet.query) { console.log("🔄 기존 쿼리 자동 실행:", dataSourceToSet.query); executeQueryAutomatically(dataSourceToSet); } } }, [isOpen, element]); // 쿼리 자동 실행 함수 const executeQueryAutomatically = async (dataSourceToExecute: ChartDataSource) => { if (dataSourceToExecute.type !== "database" || !dataSourceToExecute.query) return; try { const { queryApi } = await import("@/lib/api/query"); const result = await queryApi.executeQuery({ query: dataSourceToExecute.query, connectionType: dataSourceToExecute.connectionType || "current", externalConnectionId: dataSourceToExecute.externalConnectionId, }); console.log("✅ 쿼리 자동 실행 완료:", result); setQueryResult(result); } catch (error) { console.error("❌ 쿼리 자동 실행 실패:", error); // 실패해도 모달은 열리도록 (사용자가 다시 실행 가능) } }; // 데이터 소스 타입 변경 const handleDataSourceTypeChange = useCallback((type: "database" | "api") => { if (type === "database") { setDataSource({ type: "database", connectionType: "current", refreshInterval: 0, }); } else { setDataSource({ type: "api", method: "GET", refreshInterval: 0, }); } // 데이터 소스 변경 시 쿼리 결과와 차트 설정 초기화 setQueryResult(null); setChartConfig({}); }, []); // 데이터 소스 업데이트 const handleDataSourceUpdate = useCallback((updates: Partial) => { setDataSource((prev) => ({ ...prev, ...updates })); }, []); // 차트 설정 변경 처리 const handleChartConfigChange = useCallback((newConfig: ChartConfig) => { setChartConfig(newConfig); }, []); // 쿼리 테스트 결과 처리 const handleQueryTest = useCallback((result: QueryResult) => { setQueryResult(result); // 쿼리가 변경되었으므로 차트 설정 초기화 (X/Y축 리셋) // console.log("🔄 쿼리 변경 감지 - 차트 설정 초기화"); 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 handleSave = useCallback(() => { const updatedElement: DashboardElement = { ...element, dataSource, chartConfig, customTitle: customTitle.trim() || undefined, // 빈 문자열이면 undefined showHeader, // 헤더 표시 여부 }; // console.log(" 저장할 element:", updatedElement); onSave(updatedElement); onClose(); }, [element, dataSource, chartConfig, customTitle, showHeader, onSave, onClose]); // 모달이 열려있지 않으면 렌더링하지 않음 if (!isOpen) return null; // 시계, 달력, 날씨, 환율, 계산기 위젯은 헤더 설정만 가능 const isHeaderOnlyWidget = element.type === "widget" && (element.subtype === "clock" || element.subtype === "calendar" || isSelfContainedWidget); // 기사관리 위젯은 자체 설정 UI를 가지고 있으므로 모달 표시하지 않음 if (element.type === "widget" && element.subtype === "driver-management") { return null; } // 저장 가능 여부 확인 const isPieChart = element.subtype === "pie" || element.subtype === "donut"; const isApiSource = dataSource.type === "api"; // Y축 검증 헬퍼 const hasYAxis = chartConfig.yAxis && (typeof chartConfig.yAxis === "string" || (Array.isArray(chartConfig.yAxis) && chartConfig.yAxis.length > 0)); // customTitle이 변경되었는지 확인 const isTitleChanged = customTitle.trim() !== (element.customTitle || ""); // showHeader가 변경되었는지 확인 const isHeaderChanged = showHeader !== (element.showHeader !== false); const canSave = isTitleChanged || // 제목만 변경해도 저장 가능 isHeaderChanged || // 헤더 표시 여부만 변경해도 저장 가능 (isSimpleWidget ? // 간단한 위젯: 2단계에서 쿼리 테스트 후 저장 가능 (차트 설정 불필요) currentStep === 2 && queryResult && queryResult.rows.length > 0 : isMapWidget ? // 지도 위젯: 위도/경도 매핑 필요 currentStep === 2 && queryResult && queryResult.rows.length > 0 && chartConfig.latitudeColumn && chartConfig.longitudeColumn : // 차트: 기존 로직 (2단계에서 차트 설정 필요) currentStep === 2 && queryResult && queryResult.rows.length > 0 && chartConfig.xAxis && (isPieChart || isApiSource ? // 파이/도넛 차트 또는 REST API chartConfig.aggregation === "count" ? true // count는 Y축 없어도 됨 : hasYAxis // 다른 집계(sum, avg, max, min) 또는 집계 없음 → Y축 필수 : // 일반 차트 (DB): Y축 필수 hasYAxis)); return (
{/* 모달 헤더 */}

{element.title} 설정

{/* 커스텀 제목 입력 */}
setCustomTitle(e.target.value)} onKeyDown={(e) => { // 모든 키보드 이벤트를 input 필드 내부에서만 처리 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" />

비워두면 테이블명으로 자동 생성됩니다 (예: "maintenance_schedules 목록")

{/* 헤더 표시 옵션 */}
setShowHeader(e.target.checked)} className="text-primary focus:ring-primary h-4 w-4 rounded border-gray-300" />
{/* 진행 상황 표시 - 간단한 위젯과 헤더 전용 위젯은 표시 안 함 */} {!isSimpleWidget && !isHeaderOnlyWidget && (
단계 {currentStep} / 2: {currentStep === 1 ? "데이터 소스 선택" : "데이터 설정 및 차트 설정"}
)} {/* 단계별 내용 */} {!isHeaderOnlyWidget && (
{currentStep === 1 && ( )} {currentStep === 2 && (
{/* 왼쪽: 데이터 설정 */}
{dataSource.type === "database" ? ( <> ) : ( )}
{/* 오른쪽: 설정 패널 */} {!isSimpleWidget && (
{isMapWidget ? ( // 지도 위젯: 위도/경도 매핑 패널 queryResult && queryResult.rows.length > 0 ? ( ) : (
데이터를 가져온 후 지도 설정이 표시됩니다
) ) : // 차트: 차트 설정 패널 queryResult && queryResult.rows.length > 0 ? ( ) : (
데이터를 가져온 후 차트 설정이 표시됩니다
)}
)}
)}
)} {/* 모달 푸터 */}
{queryResult && {queryResult.rows.length}개 데이터 로드됨}
{!isSimpleWidget && !isHeaderOnlyWidget && currentStep > 1 && ( )} {isHeaderOnlyWidget ? ( // 헤더 전용 위젯: 바로 저장 ) : currentStep === 1 ? ( // 1단계: 다음 버튼 (차트 위젯, 간단한 위젯 모두) ) : ( // 2단계: 저장 버튼 )}
); }