diff --git a/backend-node/src/config/database.ts b/backend-node/src/config/database.ts index 2d13d368..6dec398b 100644 --- a/backend-node/src/config/database.ts +++ b/backend-node/src/config/database.ts @@ -15,7 +15,6 @@ const prisma = new PrismaClient({ async function testConnection() { try { await prisma.$connect(); - console.log("✅ 데이터베이스 연결 성공"); } catch (error) { console.error("❌ 데이터베이스 연결 실패:", error); process.exit(1); diff --git a/backend-node/src/controllers/authController.ts b/backend-node/src/controllers/authController.ts index c78b4918..ba9dcdc1 100644 --- a/backend-node/src/controllers/authController.ts +++ b/backend-node/src/controllers/authController.ts @@ -181,14 +181,6 @@ export class AuthController { return; } - // DB에서 조회한 원본 사용자 정보 로그 - console.log("🔍 DB에서 조회한 사용자 정보:", { - userId: dbUserInfo.userId, - companyCode: dbUserInfo.companyCode, - deptCode: dbUserInfo.deptCode, - dbUserInfoKeys: Object.keys(dbUserInfo), - }); - // 프론트엔드 호환성을 위해 더 많은 사용자 정보 반환 const userInfoResponse: any = { userId: dbUserInfo.userId, @@ -206,13 +198,6 @@ export class AuthController { dbUserInfo.userType === "ADMIN" || dbUserInfo.userId === "plm_admin", }; - console.log("📤 프론트엔드로 전송할 사용자 정보:", { - companyCode: userInfoResponse.companyCode, - company_code: userInfoResponse.company_code, - deptCode: userInfoResponse.deptCode, - responseKeys: Object.keys(userInfoResponse), - }); - res.status(200).json({ success: true, message: "사용자 정보 조회 성공", diff --git a/backend-node/src/controllers/componentStandardController.ts b/backend-node/src/controllers/componentStandardController.ts index dc264870..19830856 100644 --- a/backend-node/src/controllers/componentStandardController.ts +++ b/backend-node/src/controllers/componentStandardController.ts @@ -414,10 +414,6 @@ class ComponentStandardController { req.user?.companyCode ); - console.log( - `🔍 중복 체크 결과: component_code=${component_code}, company_code=${req.user?.companyCode}, isDuplicate=${isDuplicate}` - ); - res.status(200).json({ success: true, data: { isDuplicate, component_code }, diff --git a/backend-node/src/controllers/dataflowDiagramController.ts b/backend-node/src/controllers/dataflowDiagramController.ts index 647a4317..7e955e78 100644 --- a/backend-node/src/controllers/dataflowDiagramController.ts +++ b/backend-node/src/controllers/dataflowDiagramController.ts @@ -125,20 +125,6 @@ export const createDataflowDiagram = async (req: Request, res: Response) => { }); } - // 🔍 백엔드에서 받은 실제 데이터 로깅 - console.log( - "🔍 백엔드에서 받은 control 데이터:", - JSON.stringify(control, null, 2) - ); - console.log( - "🔍 백엔드에서 받은 plan 데이터:", - JSON.stringify(plan, null, 2) - ); - console.log( - "🔍 백엔드에서 받은 category 데이터:", - JSON.stringify(category, null, 2) - ); - const newDiagram = await createDataflowDiagramService({ diagram_name, relationships, diff --git a/backend-node/src/controllers/dynamicFormController.ts b/backend-node/src/controllers/dynamicFormController.ts index b3cb79da..85f74533 100644 --- a/backend-node/src/controllers/dynamicFormController.ts +++ b/backend-node/src/controllers/dynamicFormController.ts @@ -11,14 +11,6 @@ export const saveFormData = async ( const { companyCode, userId } = req.user as any; const { screenId, tableName, data } = req.body; - console.log("💾 폼 데이터 저장 요청:", { - userId, - companyCode, - screenId, - tableName, - data, - }); - // 필수 필드 검증 if (!screenId || !tableName || !data) { return res.status(400).json({ @@ -49,8 +41,6 @@ export const saveFormData = async ( formDataWithMeta ); - console.log("✅ 폼 데이터 저장 성공:", result); - res.json({ success: true, data: result, @@ -75,14 +65,6 @@ export const updateFormData = async ( const { companyCode, userId } = req.user as any; const { tableName, data } = req.body; - console.log("🔄 폼 데이터 업데이트 요청:", { - id, - userId, - companyCode, - tableName, - data, - }); - if (!tableName || !data) { return res.status(400).json({ success: false, @@ -103,8 +85,6 @@ export const updateFormData = async ( formDataWithMeta ); - console.log("✅ 폼 데이터 업데이트 성공:", result); - res.json({ success: true, data: result, @@ -129,8 +109,6 @@ export const deleteFormData = async ( const { companyCode } = req.user as any; const { tableName } = req.body; - console.log("🗑️ 폼 데이터 삭제 요청:", { id, companyCode, tableName }); - if (!tableName) { return res.status(400).json({ success: false, @@ -140,8 +118,6 @@ export const deleteFormData = async ( await dynamicFormService.deleteFormData(parseInt(id), tableName); - console.log("✅ 폼 데이터 삭제 성공"); - res.json({ success: true, message: "데이터가 성공적으로 삭제되었습니다.", @@ -164,8 +140,6 @@ export const getFormData = async ( const { id } = req.params; const { companyCode } = req.user as any; - console.log("📄 폼 데이터 단건 조회 요청:", { id, companyCode }); - const data = await dynamicFormService.getFormData(parseInt(id)); if (!data) { @@ -175,8 +149,6 @@ export const getFormData = async ( }); } - console.log("✅ 폼 데이터 단건 조회 성공"); - res.json({ success: true, data: data, @@ -206,16 +178,6 @@ export const getFormDataList = async ( sortOrder = "desc", } = req.query; - console.log("📋 폼 데이터 목록 조회 요청:", { - screenId, - companyCode, - page, - size, - search, - sortBy, - sortOrder, - }); - const result = await dynamicFormService.getFormDataList( parseInt(screenId as string), { @@ -227,8 +189,6 @@ export const getFormDataList = async ( } ); - console.log("✅ 폼 데이터 목록 조회 성공"); - res.json({ success: true, data: result, @@ -250,8 +210,6 @@ export const validateFormData = async ( try { const { tableName, data } = req.body; - console.log("✅ 폼 데이터 검증 요청:", { tableName, data }); - if (!tableName || !data) { return res.status(400).json({ success: false, @@ -264,8 +222,6 @@ export const validateFormData = async ( data ); - console.log("✅ 폼 데이터 검증 성공:", validationResult); - res.json({ success: true, data: validationResult, @@ -287,12 +243,8 @@ export const getTableColumns = async ( try { const { tableName } = req.params; - console.log("📊 테이블 컬럼 정보 조회 요청:", { tableName }); - const columns = await dynamicFormService.getTableColumns(tableName); - console.log("✅ 테이블 컬럼 정보 조회 성공"); - res.json({ success: true, data: { diff --git a/backend-node/src/controllers/fileController.ts b/backend-node/src/controllers/fileController.ts index c1d185b9..09bae070 100644 --- a/backend-node/src/controllers/fileController.ts +++ b/backend-node/src/controllers/fileController.ts @@ -43,7 +43,6 @@ const storage = multer.diskStorage({ if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } - console.log(`📁 임시 업로드 디렉토리: ${tempDir}`); cb(null, tempDir); }, filename: (req, file, cb) => { @@ -51,7 +50,6 @@ const storage = multer.diskStorage({ const timestamp = Date.now(); const sanitizedName = file.originalname.replace(/[^a-zA-Z0-9.-]/g, "_"); const savedFileName = `${timestamp}_${sanitizedName}`; - console.log(`📄 저장 파일명: ${savedFileName}`); cb(null, savedFileName); }, }); @@ -64,18 +62,12 @@ const upload = multer({ fileFilter: (req, file, cb) => { // 프론트엔드에서 전송된 accept 정보 확인 const acceptHeader = req.body?.accept; - console.log("🔍 파일 타입 검증:", { - fileName: file.originalname, - mimeType: file.mimetype, - acceptFromFrontend: acceptHeader, - }); // 프론트엔드에서 */* 또는 * 허용한 경우 모든 파일 허용 if ( acceptHeader && (acceptHeader.includes("*/*") || acceptHeader.includes("*")) ) { - console.log("✅ 와일드카드 허용: 모든 파일 타입 허용"); cb(null, true); return; } @@ -97,10 +89,8 @@ const upload = multer({ ]; if (defaultAllowedTypes.includes(file.mimetype)) { - console.log("✅ 기본 허용 파일 타입:", file.mimetype); cb(null, true); } else { - console.log("❌ 허용되지 않는 파일 타입:", file.mimetype); cb(new Error("허용되지 않는 파일 타입입니다.")); } }, @@ -114,23 +104,6 @@ export const uploadFiles = async ( res: Response ): Promise => { try { - console.log("📤 파일 업로드 요청 수신:", { - body: req.body, - companyCode: req.body.companyCode, - writer: req.body.writer, - docType: req.body.docType, - user: req.user - ? { - userId: req.user.userId, - companyCode: req.user.companyCode, - deptCode: req.user.deptCode, - } - : "no user", - files: req.files - ? (req.files as Express.Multer.File[]).map((f) => f.originalname) - : "none", - }); - if (!req.files || (req.files as Express.Multer.File[]).length === 0) { res.status(400).json({ success: false, @@ -141,13 +114,6 @@ export const uploadFiles = async ( const files = req.files as Express.Multer.File[]; - // 파라미터 확인 및 로깅 - console.log("📤 파일 업로드 요청 수신:", { - filesCount: files?.length || 0, - bodyKeys: Object.keys(req.body), - fullBody: req.body, // 전체 body 내용 확인 - }); - const { docType = "DOCUMENT", docTypeName = "일반 문서", @@ -177,26 +143,8 @@ export const uploadFiles = async ( } else { finalTargetObjid = `${linkedTable}:${recordId}`; } - - console.log("🔗 자동 연결 활성화:", { - linkedTable, - linkedField, - recordId, - columnName, - isVirtualFileColumn, - generatedTargetObjid: finalTargetObjid, - }); } - console.log("🔍 사용자 정보 결정:", { - bodyCompanyCode: req.body.companyCode, - userCompanyCode: (req.user as any)?.companyCode, - finalCompanyCode: companyCode, - bodyWriter: req.body.writer, - userWriter: (req.user as any)?.userId, - finalWriter: writer, - }); - const savedFiles = []; for (const file of files) { @@ -218,23 +166,11 @@ export const uploadFiles = async ( const relativePath = `/${actualCompanyCode}/${dateFolder}/${file.filename}`; const fullFilePath = `/uploads${relativePath}`; - console.log("📂 파일 경로 설정:", { - companyCode, - filename: file.filename, - relativePath, - fullFilePath, - }); - // 임시 파일을 최종 위치로 이동 const tempFilePath = file.path; // Multer가 저장한 임시 파일 경로 const finalUploadDir = getCompanyUploadDir(companyCode, dateFolder); const finalFilePath = path.join(finalUploadDir, file.filename); - console.log("📦 파일 이동:", { - from: tempFilePath, - to: finalFilePath, - }); - // 파일 이동 fs.renameSync(tempFilePath, finalFilePath); @@ -261,13 +197,6 @@ export const uploadFiles = async ( }, }); - console.log("💾 파일 정보 DB 저장 완료:", { - objid: fileRecord.objid.toString(), - saved_file_name: fileRecord.saved_file_name, - real_file_name: fileRecord.real_file_name, - file_size: fileRecord.file_size?.toString(), - }); - savedFiles.push({ objid: fileRecord.objid.toString(), savedFileName: fileRecord.saved_file_name, @@ -284,13 +213,6 @@ export const uploadFiles = async ( regdate: fileRecord.regdate?.toISOString(), status: fileRecord.status, }); - - console.log("✅ 파일 저장 결과:", { - objid: fileRecord.objid.toString(), - company_code: companyCode, - file_path: fileRecord.file_path, - writer: fileRecord.writer, - }); } res.json({ @@ -319,8 +241,6 @@ export const deleteFile = async ( const { objid } = req.params; const { writer = "system" } = req.body; - console.log("🗑️ 파일 삭제 요청:", { objid, writer }); - // 파일 상태를 DELETED로 변경 (논리적 삭제) const deletedFile = await prisma.attach_file_info.update({ where: { @@ -331,11 +251,6 @@ export const deleteFile = async ( }, }); - console.log("✅ 파일 삭제 완료 (논리적):", { - objid: deletedFile.objid.toString(), - status: deletedFile.status, - }); - res.json({ success: true, message: "파일이 삭제되었습니다.", @@ -360,21 +275,9 @@ export const getLinkedFiles = async ( try { const { tableName, recordId } = req.params; - console.log("📎 연결된 파일 조회 요청:", { - tableName, - recordId, - }); - // target_objid 생성 (테이블명:레코드ID 형식) const baseTargetObjid = `${tableName}:${recordId}`; - console.log("🔍 파일 조회 쿼리:", { - tableName, - recordId, - baseTargetObjid, - queryPattern: `${baseTargetObjid}%`, - }); - // 기본 target_objid와 파일 컬럼 패턴 모두 조회 (tableName:recordId% 패턴) const files = await prisma.attach_file_info.findMany({ where: { @@ -388,11 +291,6 @@ export const getLinkedFiles = async ( }, }); - console.log("📁 조회된 파일 목록:", { - foundFiles: files.length, - targetObjids: files.map((f) => f.target_objid), - }); - const fileList = files.map((file: any) => ({ objid: file.objid.toString(), savedFileName: file.saved_file_name, @@ -409,11 +307,6 @@ export const getLinkedFiles = async ( status: file.status, })); - console.log("✅ 연결된 파일 조회 완료:", { - baseTargetObjid, - fileCount: fileList.length, - }); - res.json({ success: true, files: fileList, @@ -440,12 +333,6 @@ export const getFileList = async ( try { const { targetObjid, docType, companyCode } = req.query; - console.log("📋 파일 목록 조회 요청:", { - targetObjid, - docType, - companyCode, - }); - const where: any = { status: "ACTIVE", }; @@ -506,8 +393,6 @@ export const previewFile = async ( const { objid } = req.params; const { serverFilename } = req.query; - console.log("👁️ 파일 미리보기 요청:", { objid, serverFilename }); - const fileRecord = await prisma.attach_file_info.findUnique({ where: { objid: parseInt(objid), @@ -539,13 +424,6 @@ export const previewFile = async ( ); const filePath = path.join(companyUploadDir, fileName); - console.log("👁️ 파일 미리보기 경로 확인:", { - stored_file_path: fileRecord.file_path, - company_code: companyCode, - company_upload_dir: companyUploadDir, - final_file_path: filePath, - }); - if (!fs.existsSync(filePath)) { console.error("❌ 파일 없음:", filePath); res.status(404).json({ @@ -599,12 +477,6 @@ export const previewFile = async ( // 파일 스트림으로 전송 const fileStream = fs.createReadStream(filePath); fileStream.pipe(res); - - console.log("✅ 파일 미리보기 완료:", { - objid, - fileName: fileRecord.real_file_name, - mimeType, - }); } catch (error) { console.error("파일 미리보기 오류:", error); res.status(500).json({ @@ -624,8 +496,6 @@ export const downloadFile = async ( try { const { objid } = req.params; - console.log("📥 파일 다운로드 요청:", { objid }); - const fileRecord = await prisma.attach_file_info.findUnique({ where: { objid: parseInt(objid), @@ -658,13 +528,6 @@ export const downloadFile = async ( ); const filePath = path.join(companyUploadDir, fileName); - console.log("📥 파일 다운로드 경로 확인:", { - stored_file_path: fileRecord.file_path, - company_code: companyCode, - company_upload_dir: companyUploadDir, - final_file_path: filePath, - }); - if (!fs.existsSync(filePath)) { console.error("❌ 파일 없음:", filePath); res.status(404).json({ @@ -684,11 +547,6 @@ export const downloadFile = async ( // 파일 스트림 전송 const fileStream = fs.createReadStream(filePath); fileStream.pipe(res); - - console.log("✅ 파일 다운로드 시작:", { - objid: fileRecord.objid.toString(), - real_file_name: fileRecord.real_file_name, - }); } catch (error) { console.error("파일 다운로드 오류:", error); res.status(500).json({ diff --git a/backend-node/src/controllers/screenManagementController.ts b/backend-node/src/controllers/screenManagementController.ts index 904a262d..7130777c 100644 --- a/backend-node/src/controllers/screenManagementController.ts +++ b/backend-node/src/controllers/screenManagementController.ts @@ -344,7 +344,6 @@ export const getTableInfo = async ( return; } - console.log(`=== 테이블 정보 조회 API 호출: ${tableName} ===`); const tableInfo = await screenManagementService.getTableInfo( tableName, companyCode diff --git a/backend-node/src/services/eventTriggerService.ts b/backend-node/src/services/eventTriggerService.ts index 6a26fcb5..758455c6 100644 --- a/backend-node/src/services/eventTriggerService.ts +++ b/backend-node/src/services/eventTriggerService.ts @@ -8,7 +8,7 @@ interface ConditionNode { id: string; // 고유 ID type: "condition" | "group-start" | "group-end"; field?: string; - operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE"; + operator?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE"; value?: any; dataType?: string; logicalOperator?: "AND" | "OR"; // 다음 조건과의 논리 연산자 @@ -430,15 +430,15 @@ export class EventTriggerService { condition: ConditionNode, data: Record ): boolean { - const { field, operator_type, value } = condition; + const { field, operator, value } = condition; - if (!field || !operator_type) { + if (!field || !operator) { return false; } const fieldValue = data[field]; - switch (operator_type) { + switch (operator) { case "=": return fieldValue == value; case "!=": @@ -628,9 +628,9 @@ export class EventTriggerService { if ( conditions.type === "condition" && conditions.field && - conditions.operator_type + conditions.operator ) { - return `${conditions.field} ${conditions.operator_type} '${conditions.value}'`; + return `${conditions.field} ${conditions.operator} '${conditions.value}'`; } return "1=1"; // 기본값 diff --git a/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx b/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx index ede20c29..fd25cb96 100644 --- a/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx +++ b/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx @@ -6,7 +6,6 @@ import { ArrowLeft } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DataFlowDesigner } from "@/components/dataflow/DataFlowDesigner"; import { DataFlowAPI } from "@/lib/api/dataflow"; -import { toast } from "sonner"; export default function DataFlowEditPage() { const params = useParams(); @@ -80,6 +79,7 @@ export default function DataFlowEditPage() { {/* 데이터플로우 디자이너 */}
; - }; -} - -// 연결 설정 타입 -interface ConnectionConfig { - relationshipName: string; - connectionType: "simple-key" | "data-save" | "external-call"; - fromColumnName: string; - toColumnName: string; - settings?: Record; -} - -// 단순 키값 연결 설정 -interface SimpleKeySettings { - notes: string; -} - -// 데이터 저장 설정 -interface DataSaveSettings { - actions: Array<{ - id: string; - name: string; - actionType: "insert" | "update" | "delete" | "upsert"; - conditions?: ConditionNode[]; - fieldMappings: Array<{ - sourceTable?: string; - sourceField: string; - targetTable?: string; - targetField: string; - defaultValue?: string; - transformFunction?: string; - }>; - splitConfig?: { - sourceField: string; // 분할할 소스 필드 - delimiter: string; // 구분자 (예: ",") - targetField: string; // 분할된 값이 들어갈 필드 - }; - }>; -} - -// 외부 호출 설정 -interface ExternalCallSettings { - callType: "rest-api" | "email" | "webhook" | "ftp" | "queue"; - apiUrl?: string; - httpMethod?: "GET" | "POST" | "PUT" | "DELETE"; - headers?: string; - bodyTemplate?: string; -} - -interface ConnectionSetupModalProps { - isOpen: boolean; - connection: ConnectionInfo | null; - companyCode: string; - onConfirm: (relationship: TableRelationship) => void; - onCancel: () => void; -} - export const ConnectionSetupModal: React.FC = ({ isOpen, connection, @@ -125,7 +55,7 @@ export const ConnectionSetupModal: React.FC = ({ bodyTemplate: "{}", }); - // 테이블 및 컬럼 선택을 위한 새로운 상태들 + // 테이블 및 컬럼 선택을 위한 상태들 const [availableTables, setAvailableTables] = useState([]); const [selectedFromTable, setSelectedFromTable] = useState(""); const [selectedToTable, setSelectedToTable] = useState(""); @@ -133,11 +63,80 @@ export const ConnectionSetupModal: React.FC = ({ const [toTableColumns, setToTableColumns] = useState([]); const [selectedFromColumns, setSelectedFromColumns] = useState([]); const [selectedToColumns, setSelectedToColumns] = useState([]); - // 필요시 로드하는 테이블 컬럼 캐시 const [tableColumnsCache, setTableColumnsCache] = useState<{ [tableName: string]: ColumnInfo[] }>({}); - // 조건부 연결을 위한 새로운 상태들 - const [conditions, setConditions] = useState([]); + // 조건 관리 훅 사용 + const { + conditions, + setConditions, + addCondition, + addGroupStart, + addGroupEnd, + updateCondition, + removeCondition, + getCurrentGroupLevel, + } = useConditionManager(); + + // 기존 설정 로드 함수 + const loadExistingSettings = useCallback( + (settings: Record, connectionType: string) => { + if (connectionType === "simple-key" && settings.notes) { + setSimpleKeySettings({ + notes: settings.notes as string, + }); + } else if (connectionType === "data-save" && settings.actions) { + // data-save 설정 로드 - 안전하게 처리 + const actionsData = Array.isArray(settings.actions) ? settings.actions : []; + setDataSaveSettings({ + actions: actionsData.map((action: Record) => ({ + id: (action.id as string) || `action-${Date.now()}`, + name: (action.name as string) || "새 액션", + actionType: (action.actionType as "insert" | "update" | "delete" | "upsert") || "insert", + conditions: Array.isArray(action.conditions) + ? (action.conditions as ConditionNode[]).map((condition) => ({ + ...condition, + operator: condition.operator || "=", // 기본값 보장 + })) + : [], + fieldMappings: Array.isArray(action.fieldMappings) + ? action.fieldMappings.map((mapping: Record) => ({ + sourceTable: (mapping.sourceTable as string) || "", + sourceField: (mapping.sourceField as string) || "", + targetTable: (mapping.targetTable as string) || "", + targetField: (mapping.targetField as string) || "", + defaultValue: (mapping.defaultValue as string) || "", + transformFunction: (mapping.transformFunction as string) || "", + })) + : [], + splitConfig: action.splitConfig + ? { + sourceField: ((action.splitConfig as Record).sourceField as string) || "", + delimiter: ((action.splitConfig as Record).delimiter as string) || ",", + targetField: ((action.splitConfig as Record).targetField as string) || "", + } + : undefined, + })), + }); + + // 전체 실행 조건 로드 + if (settings.control) { + const controlSettings = settings.control as { conditionTree?: ConditionNode[] }; + if (Array.isArray(controlSettings.conditionTree)) { + setConditions(controlSettings.conditionTree || []); + } + } + } else if (connectionType === "external-call") { + setExternalCallSettings({ + callType: (settings.callType as "rest-api" | "webhook") || "rest-api", + apiUrl: (settings.apiUrl as string) || "", + httpMethod: (settings.httpMethod as "GET" | "POST" | "PUT" | "DELETE") || "POST", + headers: (settings.headers as string) || "{}", + bodyTemplate: (settings.bodyTemplate as string) || "{}", + }); + } + }, + [setConditions, setSimpleKeySettings, setDataSaveSettings, setExternalCallSettings], + ); // 테이블 목록 로드 useEffect(() => { @@ -170,37 +169,32 @@ export const ConnectionSetupModal: React.FC = ({ // 기존 관계 정보가 있으면 사용, 없으면 기본값 설정 const existingRel = connection.existingRelationship; + const connectionType = + (existingRel?.connectionType as "simple-key" | "data-save" | "external-call") || "simple-key"; + setConfig({ relationshipName: existingRel?.relationshipName || `${fromDisplayName} → ${toDisplayName}`, - connectionType: (existingRel?.connectionType as "simple-key" | "data-save" | "external-call") || "simple-key", + connectionType, fromColumnName: "", toColumnName: "", settings: existingRel?.settings || {}, }); - // 단순 키값 연결 기본값 설정 - setSimpleKeySettings({ - notes: `${fromDisplayName}과 ${toDisplayName} 간의 키값 연결`, - }); + // 기존 설정 데이터 로드 + if (existingRel?.settings) { + loadExistingSettings(existingRel.settings, connectionType); + } else { + // 기본값 설정 + setSimpleKeySettings({ + notes: `${fromDisplayName}과 ${toDisplayName} 간의 키값 연결`, + }); + setDataSaveSettings({ actions: [] }); + } - // 데이터 저장 기본값 설정 (빈 배열로 시작) - setDataSaveSettings({ - actions: [], - }); - - // 🔥 필드 선택 상태 초기화 + // 필드 선택 상태 초기화 setSelectedFromColumns([]); setSelectedToColumns([]); - // 외부 호출 기본값 설정 - setExternalCallSettings({ - callType: "rest-api", - apiUrl: "https://api.example.com/webhook", - httpMethod: "POST", - headers: "{}", - bodyTemplate: "{}", - }); - // 선택된 컬럼 정보가 있다면 설정 if (connection.selectedColumnsData) { const fromColumns = connection.selectedColumnsData[fromTableName]?.columns || []; @@ -216,7 +210,7 @@ export const ConnectionSetupModal: React.FC = ({ })); } } - }, [isOpen, connection]); + }, [isOpen, connection, setConditions, loadExistingSettings]); // From 테이블 선택 시 컬럼 로드 useEffect(() => { @@ -286,8 +280,8 @@ export const ConnectionSetupModal: React.FC = ({ const tablesToLoad = new Set(); // 필드 매핑에서 사용되는 모든 테이블 수집 - dataSaveSettings.actions.forEach((action) => { - action.fieldMappings.forEach((mapping) => { + dataSaveSettings.actions?.forEach((action) => { + action.fieldMappings?.forEach((mapping) => { if (mapping.sourceTable && !tableColumnsCache[mapping.sourceTable]) { tablesToLoad.add(mapping.sourceTable); } @@ -329,9 +323,9 @@ export const ConnectionSetupModal: React.FC = ({ // 단순 키값 연결일 때만 컬럼 선택 검증 if (config.connectionType === "simple-key") { - if (selectedFromColumns.length === 0 || selectedToColumns.length === 0) { - toast.error("선택된 컬럼이 없습니다. From과 To 테이블에서 각각 최소 1개 이상의 컬럼을 선택해주세요."); - return; + if (selectedFromColumns.length === 0 || selectedToColumns.length === 0) { + toast.error("선택된 컬럼이 없습니다. From과 To 테이블에서 각각 최소 1개 이상의 컬럼을 선택해주세요."); + return; } } @@ -340,7 +334,7 @@ export const ConnectionSetupModal: React.FC = ({ const toTableName = selectedToTable || connection.toNode.tableName; // 조건부 연결 설정 데이터 준비 - const conditionalSettings = isConditionalConnection() + const conditionalSettings = isConditionalConnection(config.connectionType) ? { control: { triggerType: "insert", @@ -357,7 +351,15 @@ export const ConnectionSetupModal: React.FC = ({ id: action.id, actionType: action.actionType, enabled: true, - conditions: action.conditions, + conditions: + action.conditions?.map((condition) => { + // 모든 조건 타입에 대해 operator 필드 보장 + const baseCondition = { ...condition }; + if (condition.type === "condition") { + baseCondition.operator = condition.operator || "="; + } + return baseCondition; + }) || [], fieldMappings: action.fieldMappings.map((mapping) => ({ sourceTable: mapping.sourceTable, sourceField: mapping.sourceField, @@ -389,8 +391,6 @@ export const ConnectionSetupModal: React.FC = ({ settings: { ...settings, ...conditionalSettings, // 조건부 연결 설정 추가 - // 중복 제거: multiColumnMapping, isMultiColumn, columnCount, description 제거 - // 필요시 from_column_name, to_column_name에서 split으로 추출 가능 }, }; @@ -411,1503 +411,41 @@ export const ConnectionSetupModal: React.FC = ({ onCancel(); }; - if (!connection) return null; - - // 선택된 컬럼 데이터 가져오기 (현재 사용되지 않음 - 향후 확장을 위해 유지) - // const selectedColumnsData = connection.selectedColumnsData || {}; - - // 조건부 연결인지 확인하는 헬퍼 함수 - const isConditionalConnection = () => { - return config.connectionType === "data-save" || config.connectionType === "external-call"; - }; - - // 고유 ID 생성 헬퍼 - const generateId = () => `cond_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - - // 조건 관리 헬퍼 함수들 - const addCondition = () => { - const newCondition: ConditionNode = { - id: generateId(), - type: "condition", - field: "", - operator_type: "=", - value: "", - dataType: "string", - logicalOperator: "AND", // 기본값으로 AND 설정 - }; - setConditions([...conditions, newCondition]); - }; - - // 그룹 시작 추가 - const addGroupStart = () => { - const groupId = `group_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - const groupLevel = getNextGroupLevel(); - - const groupStart: ConditionNode = { - id: generateId(), - type: "group-start", - groupId, - groupLevel, - logicalOperator: conditions.length > 0 ? "AND" : undefined, - }; - - setConditions([...conditions, groupStart]); - }; - - // 그룹 끝 추가 - const addGroupEnd = () => { - // 가장 최근에 열린 그룹 찾기 - const openGroups = findOpenGroups(); - if (openGroups.length === 0) { - toast.error("닫을 그룹이 없습니다."); - return; - } - - const lastOpenGroup = openGroups[openGroups.length - 1]; - const groupEnd: ConditionNode = { - id: generateId(), - type: "group-end", - groupId: lastOpenGroup.groupId, - groupLevel: lastOpenGroup.groupLevel, - }; - - setConditions([...conditions, groupEnd]); - }; - - // 다음 그룹 레벨 계산 - const getNextGroupLevel = (): number => { - const openGroups = findOpenGroups(); - return openGroups.length; - }; - - // 열린 그룹 찾기 - const findOpenGroups = () => { - const openGroups: Array<{ groupId: string; groupLevel: number }> = []; - - for (const condition of conditions) { - if (condition.type === "group-start") { - openGroups.push({ - groupId: condition.groupId!, - groupLevel: condition.groupLevel!, - }); - } else if (condition.type === "group-end") { - // 해당 그룹 제거 - const groupIndex = openGroups.findIndex((g) => g.groupId === condition.groupId); - if (groupIndex !== -1) { - openGroups.splice(groupIndex, 1); - } - } - } - - return openGroups; - }; - - const updateCondition = (index: number, field: keyof ConditionNode, value: string) => { - const updatedConditions = [...conditions]; - updatedConditions[index] = { ...updatedConditions[index], [field]: value }; - setConditions(updatedConditions); - }; - - const removeCondition = (index: number) => { - const conditionToRemove = conditions[index]; - - // 그룹 시작/끝을 삭제하는 경우 해당 그룹 전체 삭제 - if (conditionToRemove.type === "group-start" || conditionToRemove.type === "group-end") { - removeGroup(conditionToRemove.groupId!); - } else { - const updatedConditions = conditions.filter((_, i) => i !== index); - setConditions(updatedConditions); - } - }; - - // 그룹 전체 삭제 - const removeGroup = (groupId: string) => { - const updatedConditions = conditions.filter((c) => c.groupId !== groupId); - setConditions(updatedConditions); - }; - - // 현재 조건의 그룹 레벨 계산 - const getCurrentGroupLevel = (conditionIndex: number): number => { - let level = 0; - for (let i = 0; i < conditionIndex; i++) { - const condition = conditions[i]; - if (condition.type === "group-start") { - level++; - } else if (condition.type === "group-end") { - level--; - } - } - return level; - }; - - // 액션별 조건 그룹 관리 함수들 - const addActionGroupStart = (actionIndex: number) => { - const groupId = `action_group_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - const currentConditions = dataSaveSettings.actions[actionIndex].conditions || []; - const groupLevel = getActionNextGroupLevel(currentConditions); - - const groupStart: ConditionNode = { - id: generateId(), - type: "group-start", - groupId, - groupLevel, - logicalOperator: currentConditions.length > 0 ? "AND" : undefined, - }; - - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions = [...currentConditions, groupStart]; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }; - - const addActionGroupEnd = (actionIndex: number) => { - const currentConditions = dataSaveSettings.actions[actionIndex].conditions || []; - const openGroups = findActionOpenGroups(currentConditions); - - if (openGroups.length === 0) { - toast.error("닫을 그룹이 없습니다."); - return; - } - - const lastOpenGroup = openGroups[openGroups.length - 1]; - const groupEnd: ConditionNode = { - id: generateId(), - type: "group-end", - groupId: lastOpenGroup.groupId, - groupLevel: lastOpenGroup.groupLevel, - }; - - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions = [...currentConditions, groupEnd]; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }; - - // 액션별 다음 그룹 레벨 계산 - const getActionNextGroupLevel = (conditions: ConditionNode[]): number => { - const openGroups = findActionOpenGroups(conditions); - return openGroups.length; - }; - - // 액션별 열린 그룹 찾기 - const findActionOpenGroups = (conditions: ConditionNode[]) => { - const openGroups: Array<{ groupId: string; groupLevel: number }> = []; - - for (const condition of conditions) { - if (condition.type === "group-start") { - openGroups.push({ - groupId: condition.groupId!, - groupLevel: condition.groupLevel!, - }); - } else if (condition.type === "group-end") { - const groupIndex = openGroups.findIndex((g) => g.groupId === condition.groupId); - if (groupIndex !== -1) { - openGroups.splice(groupIndex, 1); - } - } - } - - return openGroups; - }; - - // 액션별 현재 조건의 그룹 레벨 계산 - const getActionCurrentGroupLevel = (conditions: ConditionNode[], conditionIndex: number): number => { - let level = 0; - for (let i = 0; i < conditionIndex; i++) { - const condition = conditions[i]; - if (condition.type === "group-start") { - level++; - } else if (condition.type === "group-end") { - level--; - } - } - return level; - }; - - // 액션별 조건 렌더링 함수 - const renderActionCondition = (condition: ConditionNode, condIndex: number, actionIndex: number) => { - // 그룹 시작 렌더링 - if (condition.type === "group-start") { - return ( -
- {/* 그룹 시작 앞의 논리 연산자 */} - {condIndex > 0 && ( - - )} -
- ( - 그룹 시작 - -
-
- ); - } - - // 그룹 끝 렌더링 - if (condition.type === "group-end") { - return ( -
-
- ) - 그룹 끝 - -
-
- ); - } - - // 일반 조건 렌더링 (기존 로직 간소화) - return ( -
- {/* 그룹 내 첫 번째 조건이 아닐 때만 논리 연산자 표시 */} - {condIndex > 0 && dataSaveSettings.actions[actionIndex].conditions![condIndex - 1]?.type !== "group-start" && ( - - )} -
- - - {/* 데이터 타입에 따른 동적 입력 컴포넌트 */} - {(() => { - const selectedColumn = fromTableColumns.find((col) => col.columnName === condition.field); - const dataType = selectedColumn?.dataType?.toLowerCase() || "string"; - - if (dataType.includes("timestamp") || dataType.includes("datetime") || dataType.includes("date")) { - return ( - { - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions![condIndex].value = e.target.value; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }} - className="h-6 flex-1 text-xs" - /> - ); - } else if (dataType.includes("time")) { - return ( - { - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions![condIndex].value = e.target.value; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }} - className="h-6 flex-1 text-xs" - /> - ); - } else if (dataType.includes("date")) { - return ( - { - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions![condIndex].value = e.target.value; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }} - className="h-6 flex-1 text-xs" - /> - ); - } else if ( - dataType.includes("int") || - dataType.includes("numeric") || - dataType.includes("decimal") || - dataType.includes("float") || - dataType.includes("double") - ) { - return ( - { - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions![condIndex].value = e.target.value; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }} - className="h-6 flex-1 text-xs" - /> - ); - } else if (dataType.includes("bool")) { - return ( - - ); - } else { - return ( - { - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions![condIndex].value = e.target.value; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }} - className="h-6 flex-1 text-xs" - /> - ); - } - })()} - -
-
- ); - }; - - // 조건부 연결 설정 UI 렌더링 - const renderConditionalSettings = () => { - return ( -
-
- - 전체 실행 조건 (언제 이 연결이 동작할지) -
- - {/* 실행 조건 설정 */} -
-
- -
- - - -
-
- - {/* 조건 목록 */} -
- {conditions.length === 0 ? ( -
- 조건을 추가하면 해당 조건을 만족할 때만 실행됩니다. -
- 조건이 없으면 항상 실행됩니다. -
- ) : ( - conditions.map((condition, index) => { - // 그룹 시작 렌더링 - if (condition.type === "group-start") { - return ( -
- {/* 그룹 시작 앞의 논리 연산자 */} - {index > 0 && ( - - )} - {/* 그룹 레벨에 따른 들여쓰기 */} -
- ( - 그룹 시작 - -
-
- ); - } - - // 그룹 끝 렌더링 - if (condition.type === "group-end") { - return ( -
-
- ) - 그룹 끝 - -
-
- ); - } - - // 일반 조건 렌더링 - return ( -
- {/* 그룹 내 첫 번째 조건이 아닐 때만 논리 연산자 표시 */} - {index > 0 && conditions[index - 1]?.type !== "group-start" && ( - - )} - - {/* 그룹 레벨에 따른 들여쓰기와 조건 필드들 */} -
- {/* 조건 필드 선택 */} - - - {/* 연산자 선택 */} - - - {/* 데이터 타입에 따른 동적 입력 컴포넌트 */} - {(() => { - const selectedColumn = fromTableColumns.find((col) => col.columnName === condition.field); - const dataType = selectedColumn?.dataType?.toLowerCase() || "string"; - - if ( - dataType.includes("timestamp") || - dataType.includes("datetime") || - dataType.includes("date") - ) { - return ( - updateCondition(index, "value", e.target.value)} - className="h-8 flex-1 text-xs" - /> - ); - } else if (dataType.includes("time")) { - return ( - updateCondition(index, "value", e.target.value)} - className="h-8 flex-1 text-xs" - /> - ); - } else if (dataType.includes("date")) { - return ( - updateCondition(index, "value", e.target.value)} - className="h-8 flex-1 text-xs" - /> - ); - } else if ( - dataType.includes("int") || - dataType.includes("numeric") || - dataType.includes("decimal") || - dataType.includes("float") || - dataType.includes("double") - ) { - return ( - updateCondition(index, "value", e.target.value)} - className="h-8 flex-1 text-xs" - /> - ); - } else if (dataType.includes("bool")) { - return ( - - ); - } else { - return ( - updateCondition(index, "value", e.target.value)} - className="h-8 flex-1 text-xs" - /> - ); - } - })()} - - {/* 삭제 버튼 */} - -
-
- ); - }) - )} -
-
-
- ); - }; - // 연결 종류별 설정 패널 렌더링 const renderConnectionTypeSettings = () => { switch (config.connectionType) { case "simple-key": - return ( -
- {/* 테이블 및 컬럼 선택 */} -
-
테이블 및 컬럼 선택
- - {/* 현재 선택된 테이블 표시 */} -
-
- -
- - {availableTables.find((t) => t.tableName === selectedFromTable)?.displayName || selectedFromTable} - - ({selectedFromTable}) -
-
- -
- -
- - {availableTables.find((t) => t.tableName === selectedToTable)?.displayName || selectedToTable} - - ({selectedToTable}) -
-
-
- - {/* 컬럼 선택 */} -
-
- -
- {fromTableColumns.map((column) => ( - - ))} - {fromTableColumns.length === 0 && ( -
- {selectedFromTable ? "컬럼을 불러오는 중..." : "테이블을 먼저 선택해주세요"} -
- )} -
-
- -
- -
- {toTableColumns.map((column) => ( - - ))} - {toTableColumns.length === 0 && ( -
- {selectedToTable ? "컬럼을 불러오는 중..." : "테이블을 먼저 선택해주세요"} -
- )} -
-
-
- - {/* 선택된 컬럼 미리보기 */} - {(selectedFromColumns.length > 0 || selectedToColumns.length > 0) && ( -
-
- -
- {selectedFromColumns.length > 0 ? ( - selectedFromColumns.map((column) => ( - - {column} - - )) - ) : ( - 선택된 컬럼 없음 - )} -
-
- -
- -
- {selectedToColumns.length > 0 ? ( - selectedToColumns.map((column) => ( - - {column} - - )) - ) : ( - 선택된 컬럼 없음 - )} -
-
-
- )} -
- - {/* 단순 키값 연결 설정 */} -
-
- - 단순 키값 연결 설정 -
-
-
- -