"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 } from "lucide-react"; import { cn } from "@/lib/utils"; interface ElementConfigSidebarProps { element: DashboardElement | null; isOpen: boolean; onClose: () => void; onApply: (element: DashboardElement) => void; } /** * 요소 설정 사이드바 컴포넌트 * - 왼쪽에서 슬라이드 인/아웃 * - 캔버스 위에 오버레이 * - "적용" 버튼으로 명시적 저장 */ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: ElementConfigSidebarProps) { const [dataSource, setDataSource] = useState({ type: "database", connectionType: "current", refreshInterval: 0, }); const [chartConfig, setChartConfig] = useState({}); const [queryResult, setQueryResult] = useState(null); const [currentStep, setCurrentStep] = useState<1 | 2>(1); const [customTitle, setCustomTitle] = useState(""); const [showHeader, setShowHeader] = useState(true); // 사이드바가 열릴 때 초기화 useEffect(() => { if (isOpen && element) { setDataSource(element.dataSource || { type: "database", connectionType: "current", refreshInterval: 0 }); setChartConfig(element.chartConfig || {}); setQueryResult(null); setCurrentStep(1); setCustomTitle(element.customTitle || ""); setShowHeader(element.showHeader !== false); } }, [isOpen, element]); // Esc 키로 사이드바 닫기 useEffect(() => { if (!isOpen) return; const handleEsc = (e: KeyboardEvent) => { if (e.key === "Escape") { onClose(); } }; window.addEventListener("keydown", handleEsc); return () => window.removeEventListener("keydown", handleEsc); }, [isOpen, onClose]); // 데이터 소스 타입 변경 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); 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; const updatedElement: DashboardElement = { ...element, dataSource, chartConfig, customTitle: customTitle.trim() || undefined, showHeader, }; onApply(updatedElement); // 사이드바는 열린 채로 유지 (연속 수정 가능) }, [element, dataSource, chartConfig, customTitle, showHeader, onApply]); // 요소가 없으면 렌더링하지 않음 if (!element) return null; // 차트 설정이 필요 없는 위젯 (쿼리/API만 필요) const isSimpleWidget = element.subtype === "todo" || 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 === "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" || element.subtype === "exchange" || element.subtype === "calculator"; // 지도 위젯 (위도/경도 매핑 필요) const isMapWidget = element.subtype === "vehicle-map" || element.subtype === "map-summary"; // 헤더 전용 위젯 const isHeaderOnlyWidget = element.type === "widget" && (element.subtype === "clock" || element.subtype === "calendar" || isSelfContainedWidget); // 저장 가능 여부 확인 const isPieChart = element.subtype === "pie" || element.subtype === "donut"; const isApiSource = dataSource.type === "api"; const hasYAxis = chartConfig.yAxis && (typeof chartConfig.yAxis === "string" || (Array.isArray(chartConfig.yAxis) && chartConfig.yAxis.length > 0)); const isTitleChanged = customTitle.trim() !== (element.customTitle || ""); const isHeaderChanged = showHeader !== (element.showHeader !== false); const canApply = isTitleChanged || isHeaderChanged || (isSimpleWidget ? currentStep === 2 && queryResult && queryResult.rows.length > 0 : isMapWidget ? currentStep === 2 && queryResult && queryResult.rows.length > 0 && chartConfig.latitudeColumn && chartConfig.longitudeColumn : currentStep === 2 && queryResult && queryResult.rows.length > 0 && chartConfig.xAxis && (isPieChart || isApiSource ? (chartConfig.aggregation === "count" ? true : hasYAxis) : hasYAxis)); return (
{/* 헤더 */}

{element.title} 설정

{/* 본문: 스크롤 가능 영역 */}
{/* 커스텀 제목 입력 */}
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" />
{/* 헤더 표시 옵션 */}
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 && queryResult && queryResult.rows.length > 0 && (
{isMapWidget ? ( ) : ( )}
)}
)}
)} {/* 데이터 로드 상태 */} {queryResult && (
{queryResult.rows.length}개 데이터 로드됨
)}
{/* 푸터: 단계 이동 및 적용 버튼 */}
{!isSimpleWidget && !isHeaderOnlyWidget && currentStep > 1 && ( )}
{isHeaderOnlyWidget || currentStep === 1 ? ( ) : ( )}
); }