"use client"; import React, { useState, useEffect } from "react"; import { useParams, useRouter } from "next/navigation"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { RefreshCw, Save, ArrowLeft, Plus, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { BatchAPI, BatchConfig, BatchMapping, ConnectionInfo, } from "@/lib/api/batch"; import { BatchManagementAPI } from "@/lib/api/batchManagement"; interface BatchColumnInfo { column_name: string; data_type: string; is_nullable: string; } // 배치 타입 감지 함수 const detectBatchType = (mapping: BatchMapping): 'db-to-db' | 'restapi-to-db' | 'db-to-restapi' => { const fromType = mapping.from_connection_type; const toType = mapping.to_connection_type; if (fromType === 'restapi' && (toType === 'internal' || toType === 'external')) { return 'restapi-to-db'; } else if ((fromType === 'internal' || fromType === 'external') && toType === 'restapi') { return 'db-to-restapi'; } else { return 'db-to-db'; } }; export default function BatchEditPage() { const params = useParams(); const router = useRouter(); const batchId = parseInt(params.id as string); // 기본 상태 const [loading, setLoading] = useState(false); const [batchConfig, setBatchConfig] = useState(null); const [batchName, setBatchName] = useState(""); const [cronSchedule, setCronSchedule] = useState("0 12 * * *"); const [description, setDescription] = useState(""); const [isActive, setIsActive] = useState("Y"); const [saveMode, setSaveMode] = useState<"INSERT" | "UPSERT">("INSERT"); const [conflictKey, setConflictKey] = useState(""); const [authServiceName, setAuthServiceName] = useState(""); const [authServiceNames, setAuthServiceNames] = useState([]); // 연결 정보 const [connections, setConnections] = useState([]); const [fromConnection, setFromConnection] = useState(null); const [toConnection, setToConnection] = useState(null); // 테이블 및 컬럼 정보 const [fromTables, setFromTables] = useState([]); const [toTables, setToTables] = useState([]); const [fromTable, setFromTable] = useState(""); const [toTable, setToTable] = useState(""); const [fromColumns, setFromColumns] = useState([]); const [toColumns, setToColumns] = useState([]); // 매핑 정보 const [mappings, setMappings] = useState([]); // 배치 타입 감지 const [batchType, setBatchType] = useState<'db-to-db' | 'restapi-to-db' | 'db-to-restapi' | null>(null); // REST API 미리보기 상태 const [apiPreviewData, setApiPreviewData] = useState([]); // 페이지 로드 시 배치 정보 조회 useEffect(() => { if (batchId) { loadBatchConfig(); loadConnections(); loadAuthServiceNames(); } }, [batchId]); // 인증 서비스명 목록 로드 const loadAuthServiceNames = async () => { try { const names = await BatchAPI.getAuthServiceNames(); setAuthServiceNames(names); } catch (error) { console.error("인증 서비스 목록 로드 실패:", error); } }; // 연결 정보가 로드된 후 배치 설정의 연결 정보 설정 useEffect(() => { if (batchConfig && connections.length > 0 && batchConfig.batch_mappings && batchConfig.batch_mappings.length > 0) { const firstMapping = batchConfig.batch_mappings[0]; console.log("🔗 연결 정보 설정 시작:", firstMapping); // FROM 연결 정보 설정 if (firstMapping.from_connection_type === 'internal') { setFromConnection({ type: 'internal', name: '내부 DB' }); // 내부 DB 테이블 목록 로드 BatchAPI.getTablesFromConnection({ type: 'internal', name: '내부 DB' }).then(tables => { console.log("📋 FROM 테이블 목록:", tables); setFromTables(tables); // 컬럼 정보도 로드 if (firstMapping.from_table_name) { BatchAPI.getTableColumns({ type: 'internal', name: '내부 DB' }, firstMapping.from_table_name).then(columns => { console.log("📊 FROM 컬럼 목록:", columns); setFromColumns(columns); }); } }); } else if (firstMapping.from_connection_id) { const fromConn = connections.find(c => c.id === firstMapping.from_connection_id); if (fromConn) { setFromConnection(fromConn); // 외부 DB 테이블 목록 로드 BatchAPI.getTablesFromConnection(fromConn).then(tables => { console.log("📋 FROM 테이블 목록:", tables); setFromTables(tables); // 컬럼 정보도 로드 if (firstMapping.from_table_name) { BatchAPI.getTableColumns(fromConn, firstMapping.from_table_name).then(columns => { console.log("📊 FROM 컬럼 목록:", columns); setFromColumns(columns); }); } }); } } // TO 연결 정보 설정 if (firstMapping.to_connection_type === 'internal') { setToConnection({ type: 'internal', name: '내부 DB' }); // 내부 DB 테이블 목록 로드 BatchAPI.getTablesFromConnection({ type: 'internal', name: '내부 DB' }).then(tables => { console.log("📋 TO 테이블 목록:", tables); setToTables(tables); // 컬럼 정보도 로드 if (firstMapping.to_table_name) { BatchAPI.getTableColumns({ type: 'internal', name: '내부 DB' }, firstMapping.to_table_name).then(columns => { console.log("📊 TO 컬럼 목록:", columns); setToColumns(columns); }); } }); } else if (firstMapping.to_connection_id) { const toConn = connections.find(c => c.id === firstMapping.to_connection_id); if (toConn) { setToConnection(toConn); // 외부 DB 테이블 목록 로드 BatchAPI.getTablesFromConnection(toConn).then(tables => { console.log("📋 TO 테이블 목록:", tables); setToTables(tables); // 컬럼 정보도 로드 if (firstMapping.to_table_name) { BatchAPI.getTableColumns(toConn, firstMapping.to_table_name).then(columns => { console.log("📊 TO 컬럼 목록:", columns); setToColumns(columns); }); } }); } } } }, [batchConfig, connections]); // 배치 설정 조회 const loadBatchConfig = async () => { try { setLoading(true); console.log("🔍 배치 설정 조회 시작:", batchId); const config = await BatchAPI.getBatchConfig(batchId); console.log("📋 조회된 배치 설정:", config); setBatchConfig(config); setBatchName(config.batch_name); setCronSchedule(config.cron_schedule); setDescription(config.description || ""); setIsActive(config.is_active || "Y"); setSaveMode((config as any).save_mode || "INSERT"); setConflictKey((config as any).conflict_key || ""); setAuthServiceName((config as any).auth_service_name || ""); if (config.batch_mappings && config.batch_mappings.length > 0) { console.log("📊 매핑 정보:", config.batch_mappings); console.log("📊 매핑 개수:", config.batch_mappings.length); config.batch_mappings.forEach((mapping, idx) => { console.log(`📊 매핑 #${idx + 1}:`, { from: `${mapping.from_column_name} (${mapping.from_column_type})`, to: `${mapping.to_column_name} (${mapping.to_column_type})`, type: mapping.mapping_type }); }); setMappings(config.batch_mappings); // 첫 번째 매핑에서 연결 및 테이블 정보 추출 const firstMapping = config.batch_mappings[0]; setFromTable(firstMapping.from_table_name); setToTable(firstMapping.to_table_name); // 배치 타입 감지 const detectedBatchType = detectBatchType(firstMapping); setBatchType(detectedBatchType); console.log("🎯 감지된 배치 타입:", detectedBatchType); // FROM 연결 정보 설정 if (firstMapping.from_connection_type === 'internal') { setFromConnection({ type: 'internal', name: '내부 DB' }); } else if (firstMapping.from_connection_id) { // 외부 연결은 connections 로드 후 설정 setTimeout(() => { const fromConn = connections.find(c => c.id === firstMapping.from_connection_id); if (fromConn) { setFromConnection(fromConn); } }, 100); } // TO 연결 정보 설정 if (firstMapping.to_connection_type === 'internal') { setToConnection({ type: 'internal', name: '내부 DB' }); } else if (firstMapping.to_connection_id) { // 외부 연결은 connections 로드 후 설정 setTimeout(() => { const toConn = connections.find(c => c.id === firstMapping.to_connection_id); if (toConn) { setToConnection(toConn); } }, 100); } console.log("🔗 테이블 정보 설정:", { fromTable: firstMapping.from_table_name, toTable: firstMapping.to_table_name, fromConnectionType: firstMapping.from_connection_type, toConnectionType: firstMapping.to_connection_type }); } } catch (error) { console.error("❌ 배치 설정 조회 오류:", error); toast.error("배치 설정을 불러오는데 실패했습니다."); } finally { setLoading(false); } }; // 연결 정보 조회 const loadConnections = async () => { try { const connectionList = await BatchAPI.getConnections(); setConnections(connectionList); } catch (error) { console.error("연결 정보 조회 오류:", error); toast.error("연결 정보를 불러오는데 실패했습니다."); } }; // FROM 연결 변경 시 const handleFromConnectionChange = async (connectionId: string) => { const connection = connections.find(c => c.id?.toString() === connectionId) || (connectionId === 'internal' ? { type: 'internal' as const, name: '내부 DB' } : null); if (connection) { setFromConnection(connection); try { const tables = await BatchAPI.getTablesFromConnection(connection); setFromTables(tables); setFromTable(""); setFromColumns([]); } catch (error) { console.error("테이블 목록 조회 오류:", error); toast.error("테이블 목록을 불러오는데 실패했습니다."); } } }; // TO 연결 변경 시 const handleToConnectionChange = async (connectionId: string) => { const connection = connections.find(c => c.id?.toString() === connectionId) || (connectionId === 'internal' ? { type: 'internal' as const, name: '내부 DB' } : null); if (connection) { setToConnection(connection); try { const tables = await BatchAPI.getTablesFromConnection(connection); setToTables(tables); setToTable(""); setToColumns([]); } catch (error) { console.error("테이블 목록 조회 오류:", error); toast.error("테이블 목록을 불러오는데 실패했습니다."); } } }; // FROM 테이블 변경 시 const handleFromTableChange = async (tableName: string) => { setFromTable(tableName); if (fromConnection && tableName) { try { const columns = await BatchAPI.getTableColumns(fromConnection, tableName); setFromColumns(columns); } catch (error) { console.error("컬럼 정보 조회 오류:", error); toast.error("컬럼 정보를 불러오는데 실패했습니다."); } } }; // TO 테이블 변경 시 const handleToTableChange = async (tableName: string) => { setToTable(tableName); if (toConnection && tableName) { try { const columns = await BatchAPI.getTableColumns(toConnection, tableName); setToColumns(columns); } catch (error) { console.error("컬럼 정보 조회 오류:", error); toast.error("컬럼 정보를 불러오는데 실패했습니다."); } } }; // 매핑 추가 const addMapping = () => { const newMapping: BatchMapping = { from_connection_type: fromConnection?.type === 'internal' ? 'internal' : 'external', from_connection_id: fromConnection?.type === 'internal' ? undefined : fromConnection?.id, from_table_name: fromTable, from_column_name: '', from_column_type: '', to_connection_type: toConnection?.type === 'internal' ? 'internal' : 'external', to_connection_id: toConnection?.type === 'internal' ? undefined : toConnection?.id, to_table_name: toTable, to_column_name: '', to_column_type: '', mapping_type: 'direct', mapping_order: mappings.length + 1 }; setMappings([...mappings, newMapping]); }; // REST API → DB 매핑 추가 const addRestapiToDbMapping = () => { if (!batchConfig || !batchConfig.batch_mappings || batchConfig.batch_mappings.length === 0) { return; } const first = batchConfig.batch_mappings[0] as any; const newMapping: BatchMapping = { // FROM: REST API (기존 설정 그대로 복사) from_connection_type: "restapi" as any, from_connection_id: first.from_connection_id, from_table_name: first.from_table_name, from_column_name: "", from_column_type: "", // TO: DB (기존 설정 그대로 복사) to_connection_type: first.to_connection_type as any, to_connection_id: first.to_connection_id, to_table_name: first.to_table_name, to_column_name: "", to_column_type: "", mapping_type: (first.mapping_type as any) || "direct", mapping_order: mappings.length + 1, }; setMappings((prev) => [...prev, newMapping]); }; // REST API 데이터 미리보기 (수정 화면용) const previewRestApiData = async () => { if (!mappings || mappings.length === 0) { toast.error("미리보기할 REST API 매핑이 없습니다."); return; } const first: any = mappings[0]; if (!first.from_api_url || !first.from_table_name) { toast.error("API URL과 엔드포인트 정보가 없습니다."); return; } try { const method = (first.from_api_method as "GET" | "POST" | "PUT" | "DELETE") || "GET"; const paramInfo = first.from_api_param_type && first.from_api_param_name && first.from_api_param_value ? { paramType: first.from_api_param_type as "url" | "query", paramName: first.from_api_param_name as string, paramValue: first.from_api_param_value as string, paramSource: (first.from_api_param_source as "static" | "dynamic") || "static", } : undefined; const result = await BatchManagementAPI.previewRestApiData( first.from_api_url, first.from_api_key || "", first.from_table_name, method, paramInfo, first.from_api_body || undefined ); setApiPreviewData(result.samples || []); toast.success( `API 데이터 미리보기 완료! ${result.fields.length}개 필드, ${result.samples.length}개 레코드` ); } catch (error) { console.error("REST API 미리보기 오류:", error); toast.error("API 데이터 미리보기에 실패했습니다."); } }; // 매핑 삭제 const removeMapping = (index: number) => { const updatedMappings = mappings.filter((_, i) => i !== index); setMappings(updatedMappings); }; // 매핑 업데이트 const updateMapping = (index: number, field: keyof BatchMapping, value: any) => { setMappings(prevMappings => { const updatedMappings = [...prevMappings]; updatedMappings[index] = { ...updatedMappings[index], [field]: value }; return updatedMappings; }); }; // 배치 설정 저장 const saveBatchConfig = async () => { if (!batchName || !cronSchedule || mappings.length === 0) { toast.error("필수 항목을 모두 입력해주세요."); return; } try { setLoading(true); await BatchAPI.updateBatchConfig(batchId, { batchName, description, cronSchedule, isActive, mappings, saveMode, conflictKey: saveMode === "UPSERT" ? conflictKey : undefined, authServiceName: authServiceName || undefined }); toast.success("배치 설정이 성공적으로 수정되었습니다."); router.push("/admin/batchmng"); } catch (error) { console.error("배치 설정 수정 실패:", error); toast.error("배치 설정 수정에 실패했습니다."); } finally { setLoading(false); } }; if (loading && !batchConfig) { return (
배치 설정을 불러오는 중...
); } return (

배치 설정 수정

{/* 기본 정보 */} 기본 정보
setBatchName(e.target.value)} placeholder="배치명을 입력하세요" />
setCronSchedule(e.target.value)} placeholder="0 12 * * *" />