"use client"; import React, { useState, useCallback, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Globe, Settings, TestTube, History, Info } from "lucide-react"; // 타입 import import { ExternalCallConfig, ExternalCallPanelProps, RestApiSettings as RestApiSettingsType, ApiTestResult, } from "@/types/external-call/ExternalCallTypes"; import { DataMappingConfig, TableInfo } from "@/types/external-call/DataMappingTypes"; // API import import { DataFlowAPI } from "@/lib/api/dataflow"; import { toast } from "sonner"; // 하위 컴포넌트 import import RestApiSettings from "./RestApiSettings"; import ExternalCallTestPanel from "./ExternalCallTestPanel"; import { DataMappingSettings } from "./DataMappingSettings"; /** * 🌐 외부호출 메인 패널 컴포넌트 * * 데이터 저장 기능과 완전히 분리된 독립적인 외부호출 전용 패널 * REST API 설정, 테스트, 실행 이력 등을 통합 관리 */ const ExternalCallPanel: React.FC = ({ relationshipId, onSettingsChange, initialSettings, readonly = false, }) => { console.log("🌐 [ExternalCallPanel] Component mounted with props:", { relationshipId, initialSettings, readonly, }); // 상태 관리 const [config, setConfig] = useState( () => { if (initialSettings) { console.log("🔄 [ExternalCallPanel] 기존 설정 로드:", initialSettings); return initialSettings; } console.log("🔄 [ExternalCallPanel] 기본 설정 사용"); return { callType: "rest-api", restApiSettings: { apiUrl: "", httpMethod: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, bodyTemplate: `{ "message": "데이터가 업데이트되었습니다", "data": {{sourceData}}, "timestamp": "{{timestamp}}", "relationshipId": "{{relationshipId}}" }`, authentication: { type: "none", }, timeout: 30000, // 30초 retryCount: 3, }, }; }, ); const [activeTab, setActiveTab] = useState("settings"); const [lastTestResult, setLastTestResult] = useState(null); const [isConfigValid, setIsConfigValid] = useState(false); // 데이터 매핑 상태 const [dataMappingConfig, setDataMappingConfig] = useState(() => { // initialSettings에서 데이터 매핑 정보 불러오기 if (initialSettings?.dataMappingConfig) { console.log("🔄 [ExternalCallPanel] 기존 데이터 매핑 설정 로드:", initialSettings.dataMappingConfig); return initialSettings.dataMappingConfig; } console.log("🔄 [ExternalCallPanel] 기본 데이터 매핑 설정 사용"); return { direction: "none", }; }); // 사용 가능한 테이블 목록 (실제 API에서 로드) const [availableTables, setAvailableTables] = useState([]); const [tablesLoading, setTablesLoading] = useState(false); // 테이블 목록 로드 useEffect(() => { const loadTables = async () => { try { setTablesLoading(true); const tables = await DataFlowAPI.getTables(); // 테이블 정보를 TableInfo 형식으로 변환 const tableInfos: TableInfo[] = await Promise.all( tables.map(async (table) => { try { const columns = await DataFlowAPI.getTableColumns(table.tableName); return { name: table.tableName, displayName: table.displayName || table.tableName, fields: columns.map((col) => ({ name: col.columnName, dataType: col.dataType, nullable: col.nullable, isPrimaryKey: col.isPrimaryKey || false, })), }; } catch (error) { console.warn(`테이블 ${table.tableName} 컬럼 정보 로드 실패:`, error); return { name: table.tableName, displayName: table.displayName || table.tableName, fields: [], }; } }) ); setAvailableTables(tableInfos); } catch (error) { console.error("테이블 목록 로드 실패:", error); toast.error("테이블 목록을 불러오는데 실패했습니다."); // 실패 시 빈 배열로 설정 setAvailableTables([]); } finally { setTablesLoading(false); } }; loadTables(); }, []); // 설정 변경 핸들러 const handleRestApiSettingsChange = useCallback( (newSettings: RestApiSettingsType) => { const updatedConfig: ExternalCallConfig = { ...config, restApiSettings: newSettings, metadata: { ...config.metadata, updatedAt: new Date().toISOString(), version: "1.0", }, }; setConfig(updatedConfig); onSettingsChange({ ...updatedConfig, dataMappingConfig, }); }, [config, onSettingsChange, dataMappingConfig], ); // 데이터 매핑 설정 변경 핸들러 const handleDataMappingConfigChange = useCallback( (newMappingConfig: DataMappingConfig) => { console.log("🔄 [ExternalCallPanel] 데이터 매핑 설정 변경:", newMappingConfig); setDataMappingConfig(newMappingConfig); // 전체 설정에 데이터 매핑 정보 포함하여 상위로 전달 onSettingsChange({ ...config, dataMappingConfig: newMappingConfig, }); }, [config, onSettingsChange], ); // 테스트 결과 핸들러 const handleTestResult = useCallback((result: ApiTestResult) => { setLastTestResult(result); // 테스트 탭에 머물러서 응답 정보를 바로 확인할 수 있도록 함 // (이전에는 성공 시 자동으로 history 탭으로 이동했음) }, []); // 설정 유효성 검사 const validateConfig = useCallback(() => { const { restApiSettings } = config; // HTTP 메서드에 따라 바디 필요 여부 결정 const methodNeedsBody = !["GET", "HEAD", "DELETE"].includes(restApiSettings.httpMethod?.toUpperCase()); const isValid = !!( restApiSettings.apiUrl && restApiSettings.apiUrl.startsWith("http") && restApiSettings.httpMethod && (methodNeedsBody ? restApiSettings.bodyTemplate : true) // GET/HEAD/DELETE는 바디 불필요 ); setIsConfigValid(isValid); return isValid; }, [config]); // 설정 변경 시 유효성 검사 실행 useEffect(() => { validateConfig(); }, [validateConfig]); return (
{/* 헤더 */}
외부 호출 설정 {isConfigValid ? "설정 완료" : "설정 필요"}
관계 실행 시 외부 API를 호출하여 데이터를 전송하거나 알림을 보낼 수 있습니다.
{/* 메인 탭 컨텐츠 */} API 설정 🔄 데이터 매핑 테스트 이력 정보 {/* API 설정 탭 */} {/* 데이터 매핑 탭 */} {/* 테스트 탭 */} {isConfigValid ? ( ) : ( API 테스트를 실행하려면 먼저 설정 탭에서 필수 정보를 입력해주세요. )} {/* 이력 탭 */} 실행 이력 {lastTestResult ? (
최근 테스트 결과 {lastTestResult.success ? "성공" : "실패"}
상태 코드: {lastTestResult.statusCode || "N/A"}
응답 시간: {lastTestResult.responseTime}ms
{lastTestResult.error && ( {lastTestResult.error} )} {lastTestResult.responseData && (
응답 데이터:
                        {JSON.stringify(lastTestResult.responseData, null, 2)}
                      
)}
) : (

아직 실행 이력이 없습니다.

테스트 탭에서 API 호출을 테스트해보세요.

)}
{/* 정보 탭 */} 템플릿 변수 가이드
요청 바디에서 사용할 수 있는 템플릿 변수들입니다:
{"{{sourceData}}"} 소스 노드의 전체 데이터
{"{{timestamp}}"} 현재 타임스탬프
{"{{relationshipId}}"} 관계 ID
{"{{userId}}"} 현재 사용자 ID
{"{{executionId}}"} 실행 ID
템플릿 변수는 실제 실행 시 해당 값으로 자동 치환됩니다. JSON 형식의 데이터는 따옴표 없이 사용하세요. (예: {"{{sourceData}}"})
설정 정보
관계 ID: {relationshipId}
호출 타입: {config.callType.toUpperCase()}
설정 상태: {isConfigValid ? "완료" : "미완료"}
{config.metadata?.updatedAt && (
마지막 수정: {new Date(config.metadata.updatedAt).toLocaleString()}
)}
); }; export default ExternalCallPanel;