"use client"; import React, { useState } from "react"; import { DashboardElement, CustomMetricConfig } from "@/components/admin/dashboard/types"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { GripVertical, Plus, Trash2, ChevronDown, ChevronUp, X } from "lucide-react"; import { DatabaseConfig } from "../../data-sources/DatabaseConfig"; import { ChartDataSource } from "../../types"; import { ApiConfig } from "../../data-sources/ApiConfig"; import { QueryEditor } from "../../QueryEditor"; import { v4 as uuidv4 } from "uuid"; import { cn } from "@/lib/utils"; interface CustomMetricConfigSidebarProps { element: DashboardElement; isOpen: boolean; onClose: () => void; onApply: (updates: Partial) => void; } export default function CustomMetricConfigSidebar({ element, isOpen, onClose, onApply, }: CustomMetricConfigSidebarProps) { const [metrics, setMetrics] = useState(element.customMetricConfig?.metrics || []); const [expandedMetric, setExpandedMetric] = useState(null); const [queryColumns, setQueryColumns] = useState([]); const [dataSourceType, setDataSourceType] = useState<"database" | "api">(element.dataSource?.type || "database"); const [dataSource, setDataSource] = useState( element.dataSource || { type: "database", connectionType: "current", refreshInterval: 0 }, ); const [draggedIndex, setDraggedIndex] = useState(null); const [dragOverIndex, setDragOverIndex] = useState(null); const [customTitle, setCustomTitle] = useState(element.customTitle || element.title || ""); const [showHeader, setShowHeader] = useState(element.showHeader !== false); const [groupByMode, setGroupByMode] = useState(element.customMetricConfig?.groupByMode || false); const [groupByDataSource, setGroupByDataSource] = useState( element.customMetricConfig?.groupByDataSource, ); const [groupByQueryColumns, setGroupByQueryColumns] = useState([]); // 쿼리 실행 결과 처리 (일반 지표용) const handleQueryTest = (result: any) => { // QueryEditor에서 오는 경우: { success: true, data: { columns: [...], rows: [...] } } if (result.success && result.data?.columns) { setQueryColumns(result.data.columns); } // ApiConfig에서 오는 경우: { columns: [...], data: [...] } 또는 { success: true, columns: [...] } else if (result.columns && Array.isArray(result.columns)) { setQueryColumns(result.columns); } // 오류 처리 else { setQueryColumns([]); } }; // 쿼리 실행 결과 처리 (그룹별 카드용) const handleGroupByQueryTest = (result: any) => { if (result.success && result.data?.columns) { setGroupByQueryColumns(result.data.columns); } else if (result.columns && Array.isArray(result.columns)) { setGroupByQueryColumns(result.columns); } else { setGroupByQueryColumns([]); } }; // 메트릭 추가 const addMetric = () => { const newMetric = { id: uuidv4(), field: "", label: "새 지표", aggregation: "count" as const, unit: "", color: "gray" as const, decimals: 1, }; setMetrics([...metrics, newMetric]); setExpandedMetric(newMetric.id); }; // 메트릭 삭제 const deleteMetric = (id: string) => { setMetrics(metrics.filter((m) => m.id !== id)); if (expandedMetric === id) { setExpandedMetric(null); } }; // 메트릭 업데이트 const updateMetric = (id: string, field: string, value: any) => { setMetrics(metrics.map((m) => (m.id === id ? { ...m, [field]: value } : m))); }; // 메트릭 순서 변경 // 드래그 앤 드롭 핸들러 const handleDragStart = (index: number) => { setDraggedIndex(index); }; const handleDragOver = (e: React.DragEvent, index: number) => { e.preventDefault(); setDragOverIndex(index); }; const handleDrop = (e: React.DragEvent, dropIndex: number) => { e.preventDefault(); if (draggedIndex === null || draggedIndex === dropIndex) { setDraggedIndex(null); setDragOverIndex(null); return; } const newMetrics = [...metrics]; const [draggedItem] = newMetrics.splice(draggedIndex, 1); newMetrics.splice(dropIndex, 0, draggedItem); setMetrics(newMetrics); setDraggedIndex(null); setDragOverIndex(null); }; const handleDragEnd = () => { setDraggedIndex(null); setDragOverIndex(null); }; // 데이터 소스 업데이트 const handleDataSourceUpdate = (updates: Partial) => { const newDataSource = { ...dataSource, ...updates }; setDataSource(newDataSource); onApply({ dataSource: newDataSource }); }; // 데이터 소스 타입 변경 const handleDataSourceTypeChange = (type: "database" | "api") => { setDataSourceType(type); const newDataSource: ChartDataSource = type === "database" ? { type: "database", connectionType: "current", refreshInterval: 0 } : { type: "api", method: "GET", refreshInterval: 0 }; setDataSource(newDataSource); onApply({ dataSource: newDataSource }); setQueryColumns([]); }; // 그룹별 데이터 소스 업데이트 const handleGroupByDataSourceUpdate = (updates: Partial) => { const newDataSource = { ...groupByDataSource, ...updates } as ChartDataSource; setGroupByDataSource(newDataSource); }; // 저장 const handleSave = () => { onApply({ customTitle: customTitle, showHeader: showHeader, customMetricConfig: { groupByMode, groupByDataSource: groupByMode ? groupByDataSource : undefined, metrics, }, }); }; if (!isOpen) return null; return (
{/* 헤더 */}
📊
커스텀 카드 설정
{/* 본문: 스크롤 가능 영역 */}
{/* 헤더 설정 */}
헤더 설정
{/* 제목 입력 */}
setCustomTitle(e.target.value)} placeholder="위젯 제목을 입력하세요" className="h-8 text-xs" style={{ fontSize: "12px" }} />
{/* 헤더 표시 여부 */}
{/* 데이터 소스 타입 선택 */}
데이터 소스 타입
{/* 데이터 소스 설정 */} {dataSourceType === "database" ? ( <> ) : ( )} {/* 일반 지표 설정 (항상 표시) */}
일반 지표
{queryColumns.length > 0 && ( )}
{queryColumns.length === 0 ? (

먼저 쿼리를 실행하세요

) : (
{metrics.length === 0 ? (

추가된 지표가 없습니다

) : ( metrics.map((metric, index) => (
handleDragOver(e, index)} onDrop={(e) => handleDrop(e, index)} className={cn( "rounded-md border bg-background p-2 transition-all", draggedIndex === index && "opacity-50", dragOverIndex === index && draggedIndex !== index && "border-primary border-2", )} > {/* 헤더 */}
handleDragStart(index)} onDragEnd={handleDragEnd} className="cursor-grab active:cursor-grabbing" >
{metric.label || "새 지표"} {metric.aggregation.toUpperCase()}
{/* 설정 영역 */} {expandedMetric === metric.id && (
{/* 2열 그리드 레이아웃 */}
{/* 컬럼 */}
{/* 집계 함수 */}
{/* 단위 */}
updateMetric(metric.id, "unit", e.target.value)} className="h-6 w-full text-[10px]" placeholder="건, %, km" />
{/* 소수점 */}
{/* 표시 이름 (전체 너비) */}
updateMetric(metric.id, "label", e.target.value)} className="h-6 w-full text-[10px]" placeholder="라벨" />
{/* 삭제 버튼 */}
)}
)) )}
)}
{/* 그룹별 카드 생성 모드 (항상 표시) */}
표시 모드

쿼리 결과의 각 행을 개별 카드로 표시

{groupByMode && (

💡 사용 방법

  • • 첫 번째 컬럼: 카드 제목
  • • 두 번째 컬럼: 카드 값
  • • 예: SELECT status, COUNT(*) FROM drivers GROUP BY status
  • 아래 별도 쿼리로 설정 (일반 지표와 독립적)
)}
{/* 그룹별 카드 전용 쿼리 (활성화 시에만 표시) */} {groupByMode && groupByDataSource && (
그룹별 카드 쿼리
)}
{/* 푸터 */}
); }