"use client"; import React, { useState, useCallback, useEffect } from "react"; import { DashboardElement, ChartDataSource, QueryResult } from "../types"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { ChevronLeft, ChevronRight, Save, X } from "lucide-react"; import { DataSourceSelector } from "../data-sources/DataSourceSelector"; import { DatabaseConfig } from "../data-sources/DatabaseConfig"; import { ApiConfig } from "../data-sources/ApiConfig"; import { QueryEditor } from "../QueryEditor"; interface TodoWidgetConfigModalProps { isOpen: boolean; element: DashboardElement; onClose: () => void; onSave: (updates: Partial) => void; } /** * 일정관리 위젯 설정 모달 (범용) * - 2단계 설정: 데이터 소스 → 쿼리 입력/테스트 */ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: TodoWidgetConfigModalProps) { const [currentStep, setCurrentStep] = useState<1 | 2>(1); const [title, setTitle] = useState(element.title || "일정관리 위젯"); const [dataSource, setDataSource] = useState( element.dataSource || { type: "database", connectionType: "current", refreshInterval: 0 }, ); const [queryResult, setQueryResult] = useState(null); // 데이터베이스 연동 설정 const [enableDbSync, setEnableDbSync] = useState(element.chartConfig?.enableDbSync || false); const [dbSyncMode, setDbSyncMode] = useState<"simple" | "advanced">(element.chartConfig?.dbSyncMode || "simple"); const [tableName, setTableName] = useState(element.chartConfig?.tableName || ""); const [columnMapping, setColumnMapping] = useState(element.chartConfig?.columnMapping || { id: "id", title: "title", description: "description", priority: "priority", status: "status", assignedTo: "assigned_to", dueDate: "due_date", isUrgent: "is_urgent", }); // 모달 열릴 때 element에서 설정 로드 useEffect(() => { if (isOpen) { setTitle(element.title || "일정관리 위젯"); // 데이터 소스 설정 로드 (저장된 설정 우선, 없으면 기본값) const loadedDataSource = element.dataSource || { type: "database", connectionType: "current", refreshInterval: 0 }; setDataSource(loadedDataSource); // 저장된 쿼리가 있으면 자동으로 실행 (실제 결과 가져오기) if (loadedDataSource.query) { // 쿼리 자동 실행 const executeQuery = async () => { try { const token = localStorage.getItem("authToken"); const userLang = localStorage.getItem("userLang") || "KR"; const apiUrl = loadedDataSource.connectionType === "external" && loadedDataSource.externalConnectionId ? `http://localhost:9771/api/external-db/query?userLang=${userLang}` : `http://localhost:9771/api/dashboards/execute-query?userLang=${userLang}`; const requestBody = loadedDataSource.connectionType === "external" && loadedDataSource.externalConnectionId ? { connectionId: parseInt(loadedDataSource.externalConnectionId), query: loadedDataSource.query, } : { query: loadedDataSource.query }; const response = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(requestBody), }); if (response.ok) { const result = await response.json(); const rows = result.data?.rows || result.data || []; setQueryResult({ rows: rows, rowCount: rows.length, executionTime: 0, }); } else { // 실패해도 더미 결과로 2단계 진입 가능 setQueryResult({ rows: [{ _info: "저장된 쿼리가 있습니다. 다시 테스트해주세요." }], rowCount: 1, executionTime: 0, }); } } catch (error) { // 에러 발생해도 2단계 진입 가능 setQueryResult({ rows: [{ _info: "저장된 쿼리가 있습니다. 다시 테스트해주세요." }], rowCount: 1, executionTime: 0, }); } }; executeQuery(); } // DB 동기화 설정 로드 setEnableDbSync(element.chartConfig?.enableDbSync || false); setDbSyncMode(element.chartConfig?.dbSyncMode || "simple"); setTableName(element.chartConfig?.tableName || ""); if (element.chartConfig?.columnMapping) { setColumnMapping(element.chartConfig.columnMapping); } setCurrentStep(1); } }, [isOpen, element.id]); // 데이터 소스 타입 변경 const handleDataSourceTypeChange = useCallback((type: "database" | "api") => { if (type === "database") { setDataSource((prev) => ({ ...prev, type: "database", connectionType: "current", })); } else { setDataSource((prev) => ({ ...prev, type: "api", method: "GET", })); } setQueryResult(null); }, []); // 데이터 소스 업데이트 const handleDataSourceUpdate = useCallback((updates: Partial) => { setDataSource((prev) => ({ ...prev, ...updates })); }, []); // 쿼리 실행 결과 처리 const handleQueryTest = useCallback( (result: QueryResult) => { // console.log("🎯 TodoWidget - handleQueryTest 호출됨!"); // console.log("📊 쿼리 결과:", result); // console.log("📝 rows 개수:", result.rows?.length); // console.log("❌ error:", result.error); setQueryResult(result); // console.log("✅ setQueryResult 호출 완료!"); // 강제 리렌더링 확인 // setTimeout(() => { // console.log("🔄 1초 후 queryResult 상태:", result); // }, 1000); }, [], ); // 저장 const handleSave = useCallback(() => { if (!dataSource.query || !queryResult || queryResult.error) { alert("쿼리를 입력하고 테스트를 먼저 실행해주세요."); return; } if (!queryResult.rows || queryResult.rows.length === 0) { alert("쿼리 결과가 없습니다. 데이터가 반환되는 쿼리를 입력해주세요."); return; } // 간편 모드에서 테이블명 필수 체크 if (enableDbSync && dbSyncMode === "simple" && !tableName.trim()) { alert("데이터베이스 연동을 활성화하려면 테이블명을 입력해주세요."); return; } onSave({ title, dataSource, chartConfig: { ...element.chartConfig, enableDbSync, dbSyncMode, tableName, columnMapping, insertQuery: element.chartConfig?.insertQuery, updateQuery: element.chartConfig?.updateQuery, deleteQuery: element.chartConfig?.deleteQuery, }, }); onClose(); }, [title, dataSource, queryResult, enableDbSync, dbSyncMode, tableName, columnMapping, element.chartConfig, onSave, onClose]); // 다음 단계로 const handleNext = useCallback(() => { if (currentStep === 1) { if (dataSource.type === "database") { if (!dataSource.connectionId && dataSource.connectionType === "external") { alert("외부 데이터베이스를 선택해주세요."); return; } } else if (dataSource.type === "api") { if (!dataSource.url) { alert("API URL을 입력해주세요."); return; } } setCurrentStep(2); } }, [currentStep, dataSource]); // 이전 단계로 const handlePrev = useCallback(() => { if (currentStep === 2) { setCurrentStep(1); } }, [currentStep]); if (!isOpen) return null; return (
{/* 헤더 */}

일정관리 위젯 설정

데이터 소스와 쿼리를 설정하면 자동으로 일정 목록이 표시됩니다

{/* 진행 상태 */}
1
데이터 소스 선택
2
쿼리 입력 및 테스트
{/* 본문 */}
{/* Step 1: 데이터 소스 선택 */} {currentStep === 1 && (
setTitle(e.target.value)} placeholder="예: 오늘의 일정" className="mt-2" />
{dataSource.type === "database" && ( )} {dataSource.type === "api" && }
)} {/* Step 2: 쿼리 입력 및 테스트 */} {currentStep === 2 && (

💡 컬럼명 가이드

쿼리 결과에 다음 컬럼명이 있으면 자동으로 일정 항목으로 변환됩니다:

  • id - 고유 ID (없으면 자동 생성)
  • title,{" "} task,{" "} name - 제목 (필수)
  • description,{" "} desc,{" "} content - 상세 설명
  • priority - 우선순위 (urgent, high, normal, low)
  • status - 상태 (pending, in_progress, completed)
  • assigned_to,{" "} assignedTo,{" "} user - 담당자
  • due_date,{" "} dueDate,{" "} deadline - 마감일
  • is_urgent,{" "} isUrgent,{" "} urgent - 긴급 여부
{/* 디버그: 항상 표시되는 테스트 메시지 */}

🔍 디버그: queryResult 상태 = {queryResult ? "있음" : "없음"}

{queryResult && (

rows: {queryResult.rows?.length}개, error: {queryResult.error || "없음"}

)}
{queryResult && !queryResult.error && queryResult.rows && queryResult.rows.length > 0 && (

✅ 쿼리 테스트 성공!

{queryResult.rows.length}개의 일정 항목을 찾았습니다.

첫 번째 데이터 미리보기:

                      {JSON.stringify(queryResult.rows[0], null, 2)}
                    
)} {/* 데이터베이스 연동 쿼리 (선택사항) */}

🔗 데이터베이스 연동 (선택사항)

위젯에서 추가/수정/삭제 시 데이터베이스에 직접 반영

{enableDbSync && ( <> {/* 모드 선택 */}
{/* 간편 모드 */} {dbSyncMode === "simple" && (

테이블명과 컬럼 매핑만 입력하면 자동으로 INSERT/UPDATE/DELETE 쿼리가 생성됩니다.

{/* 테이블명 */}
setTableName(e.target.value)} placeholder="예: tasks" className="mt-2" />
{/* 컬럼 매핑 */}
setColumnMapping({ ...columnMapping, id: e.target.value })} placeholder="id" className="mt-1 h-8 text-sm" />
setColumnMapping({ ...columnMapping, title: e.target.value })} placeholder="title" className="mt-1 h-8 text-sm" />
setColumnMapping({ ...columnMapping, description: e.target.value })} placeholder="description" className="mt-1 h-8 text-sm" />
setColumnMapping({ ...columnMapping, priority: e.target.value })} placeholder="priority" className="mt-1 h-8 text-sm" />
setColumnMapping({ ...columnMapping, status: e.target.value })} placeholder="status" className="mt-1 h-8 text-sm" />
setColumnMapping({ ...columnMapping, assignedTo: e.target.value })} placeholder="assigned_to" className="mt-1 h-8 text-sm" />
setColumnMapping({ ...columnMapping, dueDate: e.target.value })} placeholder="due_date" className="mt-1 h-8 text-sm" />
setColumnMapping({ ...columnMapping, isUrgent: e.target.value })} placeholder="is_urgent" className="mt-1 h-8 text-sm" />
)} {/* 고급 모드 */} {dbSyncMode === "advanced" && (

복잡한 로직이 필요한 경우 직접 쿼리를 작성하세요.

{/* INSERT 쿼리 */}

사용 가능한 변수: ${"{title}"}, ${"{description}"}, ${"{priority}"}, ${"{status}"}, ${"{assignedTo}"}, ${"{dueDate}"}, ${"{isUrgent}"}