ERP-node/frontend/components/dataflow/ConnectionSetupModal.tsx

725 lines
27 KiB
TypeScript
Raw Normal View History

2025-09-05 16:19:31 +09:00
"use client";
2025-09-16 15:43:18 +09:00
import React, { useState, useEffect, useCallback } from "react";
2025-09-05 16:19:31 +09:00
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import {
AlertDialog,
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
2025-09-05 16:19:31 +09:00
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Link, CheckCircle } from "lucide-react";
2025-09-12 09:58:49 +09:00
import { DataFlowAPI, TableRelationship, TableInfo, ColumnInfo, ConditionNode } from "@/lib/api/dataflow";
2025-09-16 15:43:18 +09:00
import {
ConnectionConfig,
SimpleKeySettings,
DataSaveSettings,
2025-09-17 17:14:59 +09:00
SimpleExternalCallSettings,
2025-09-16 15:43:18 +09:00
ConnectionSetupModalProps,
} from "@/types/connectionTypes";
import { isConditionalConnection } from "@/utils/connectionUtils";
import { useConditionManager } from "@/hooks/useConditionManager";
import { ConditionalSettings } from "./condition/ConditionalSettings";
import { ConnectionTypeSelector } from "./connection/ConnectionTypeSelector";
import { SimpleKeySettings as SimpleKeySettingsComponent } from "./connection/SimpleKeySettings";
import { DataSaveSettings as DataSaveSettingsComponent } from "./connection/DataSaveSettings";
2025-09-17 17:14:59 +09:00
import { SimpleExternalCallSettings as ExternalCallSettingsComponent } from "./connection/SimpleExternalCallSettings";
import { toast } from "sonner";
2025-09-05 16:19:31 +09:00
export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
isOpen,
connection,
2025-09-08 16:46:53 +09:00
companyCode,
2025-09-05 16:19:31 +09:00
onConfirm,
onCancel,
}) => {
2025-09-05 18:00:18 +09:00
const [config, setConfig] = useState<ConnectionConfig>({
relationshipName: "",
connectionType: "simple-key",
fromColumnName: "",
toColumnName: "",
2025-09-08 16:46:53 +09:00
settings: {},
});
// 연결 종류별 설정 상태
const [simpleKeySettings, setSimpleKeySettings] = useState<SimpleKeySettings>({
notes: "",
});
const [dataSaveSettings, setDataSaveSettings] = useState<DataSaveSettings>({
actions: [],
2025-09-08 16:46:53 +09:00
});
2025-09-17 17:14:59 +09:00
const [externalCallSettings, setExternalCallSettings] = useState<SimpleExternalCallSettings>({
message: "",
2025-09-05 18:00:18 +09:00
});
2025-09-05 16:19:31 +09:00
2025-09-16 15:43:18 +09:00
// 테이블 및 컬럼 선택을 위한 상태들
2025-09-10 17:25:41 +09:00
const [availableTables, setAvailableTables] = useState<TableInfo[]>([]);
const [selectedFromTable, setSelectedFromTable] = useState<string>("");
const [selectedToTable, setSelectedToTable] = useState<string>("");
const [fromTableColumns, setFromTableColumns] = useState<ColumnInfo[]>([]);
const [toTableColumns, setToTableColumns] = useState<ColumnInfo[]>([]);
const [selectedFromColumns, setSelectedFromColumns] = useState<string[]>([]);
const [selectedToColumns, setSelectedToColumns] = useState<string[]>([]);
const [tableColumnsCache, setTableColumnsCache] = useState<{ [tableName: string]: ColumnInfo[] }>({});
const [showSuccessModal, setShowSuccessModal] = useState(false);
const [createdConnectionName, setCreatedConnectionName] = useState("");
const [pendingRelationshipData, setPendingRelationshipData] = useState<TableRelationship | null>(null);
2025-09-10 17:25:41 +09:00
2025-09-16 15:43:18 +09:00
// 조건 관리 훅 사용
const {
conditions,
setConditions,
addCondition,
addGroupStart,
addGroupEnd,
updateCondition,
removeCondition,
getCurrentGroupLevel,
} = useConditionManager();
// 기존 설정 로드 함수
const loadExistingSettings = useCallback(
(settings: Record<string, unknown>, connectionType: string) => {
if (connectionType === "simple-key" && settings.notes) {
setSimpleKeySettings({
notes: settings.notes as string,
});
} else if (connectionType === "data-save") {
// data-save 설정 로드 - 안전하게 처리 (다양한 구조 지원)
let actionsData: Record<string, unknown>[] = [];
const settingsRecord = settings as Record<string, unknown>;
if (Array.isArray(settingsRecord.actions)) {
// 직접 actions 배열이 있는 경우
actionsData = settingsRecord.actions as Record<string, unknown>[];
} else if (settingsRecord.plan && typeof settingsRecord.plan === "object" && settingsRecord.plan !== null) {
// plan 객체 안에 actions가 있는 경우
const planRecord = settingsRecord.plan as Record<string, unknown>;
if (Array.isArray(planRecord.actions)) {
actionsData = planRecord.actions as Record<string, unknown>[];
}
} else if (Array.isArray(settings)) {
// settings 자체가 actions 배열인 경우
actionsData = settings as Record<string, unknown>[];
}
2025-09-16 15:43:18 +09:00
setDataSaveSettings({
actions: actionsData.map((action: Record<string, unknown>) => ({
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<string, unknown>) => ({
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<string, unknown>).sourceField as string) || "",
delimiter: ((action.splitConfig as Record<string, unknown>).delimiter as string) || ",",
targetField: ((action.splitConfig as Record<string, unknown>).targetField as string) || "",
}
: undefined,
})),
});
// control 설정도 로드 (전체 실행 조건)
if (settingsRecord.control && typeof settingsRecord.control === "object" && settingsRecord.control !== null) {
const controlRecord = settingsRecord.control as Record<string, unknown>;
if (Array.isArray(controlRecord.conditionTree)) {
const conditionTree = controlRecord.conditionTree as ConditionNode[];
setConditions(
conditionTree.map((condition) => ({
...condition,
operator: condition.operator || "=", // 기본값 보장
})),
);
}
2025-09-16 15:43:18 +09:00
}
} else if (connectionType === "external-call") {
2025-09-17 17:14:59 +09:00
// 외부 호출 설정은 plan에서 로드
const settingsRecord = settings as Record<string, unknown>;
let externalCallData: Record<string, unknown> = {};
if (settingsRecord.plan && typeof settingsRecord.plan === "object" && settingsRecord.plan !== null) {
const planRecord = settingsRecord.plan as Record<string, unknown>;
if (planRecord.externalCall && typeof planRecord.externalCall === "object") {
externalCallData = planRecord.externalCall as Record<string, unknown>;
}
}
2025-09-16 15:43:18 +09:00
setExternalCallSettings({
2025-09-17 17:14:59 +09:00
configId: (externalCallData.configId as number) || undefined,
configName: (externalCallData.configName as string) || undefined,
message: (externalCallData.message as string) || "",
2025-09-16 15:43:18 +09:00
});
}
},
[setConditions, setSimpleKeySettings, setDataSaveSettings, setExternalCallSettings],
);
2025-09-12 09:58:49 +09:00
2025-09-10 17:25:41 +09:00
// 테이블 목록 로드
useEffect(() => {
const loadTables = async () => {
try {
const tables = await DataFlowAPI.getTables();
setAvailableTables(tables);
} catch (error) {
console.error("테이블 목록 로드 실패:", error);
toast.error("테이블 목록을 불러오는데 실패했습니다.");
}
};
if (isOpen) {
loadTables();
}
}, [isOpen]);
2025-09-05 18:00:18 +09:00
// 모달이 열릴 때 기본값 설정
2025-09-05 16:19:31 +09:00
useEffect(() => {
if (isOpen && connection) {
// 모달이 열릴 때마다 캐시 초기화 (라벨 업데이트 반영)
setTableColumnsCache({});
2025-09-10 17:25:41 +09:00
const fromTableName = connection.fromNode.tableName;
const toTableName = connection.toNode.tableName;
const fromDisplayName = connection.fromNode.displayName;
const toDisplayName = connection.toNode.displayName;
// 테이블 선택 설정
setSelectedFromTable(fromTableName);
setSelectedToTable(toTableName);
2025-09-05 18:00:18 +09:00
2025-09-11 10:45:16 +09:00
// 기존 관계 정보가 있으면 사용, 없으면 기본값 설정
const existingRel = connection.existingRelationship;
2025-09-16 12:37:57 +09:00
const connectionType =
(existingRel?.connectionType as "simple-key" | "data-save" | "external-call") || "simple-key";
2025-09-05 18:00:18 +09:00
setConfig({
2025-09-11 10:45:16 +09:00
relationshipName: existingRel?.relationshipName || `${fromDisplayName}${toDisplayName}`,
2025-09-16 12:37:57 +09:00
connectionType,
2025-09-05 18:00:18 +09:00
fromColumnName: "",
toColumnName: "",
2025-09-11 10:45:16 +09:00
settings: existingRel?.settings || {},
2025-09-08 16:46:53 +09:00
});
2025-09-16 15:43:18 +09:00
// 기존 설정 데이터 로드
2025-09-16 12:37:57 +09:00
if (existingRel?.settings) {
2025-09-16 15:43:18 +09:00
loadExistingSettings(existingRel.settings, connectionType);
2025-09-16 12:37:57 +09:00
} else {
// 기본값 설정
setSimpleKeySettings({
notes: existingRel?.note || `${fromDisplayName}${toDisplayName} 간의 키값 연결`,
2025-09-16 12:37:57 +09:00
});
2025-09-16 15:43:18 +09:00
setDataSaveSettings({ actions: [] });
2025-09-16 12:37:57 +09:00
}
2025-09-08 16:46:53 +09:00
2025-09-16 15:43:18 +09:00
// 필드 선택 상태 초기화
2025-09-15 20:07:28 +09:00
setSelectedFromColumns([]);
setSelectedToColumns([]);
2025-09-10 17:25:41 +09:00
// 선택된 컬럼 정보가 있다면 설정
if (connection.selectedColumnsData) {
const fromColumns = connection.selectedColumnsData[fromTableName]?.columns || [];
const toColumns = connection.selectedColumnsData[toTableName]?.columns || [];
setSelectedFromColumns(fromColumns);
setSelectedToColumns(toColumns);
setConfig((prev) => ({
...prev,
fromColumnName: fromColumns.join(", "),
toColumnName: toColumns.join(", "),
}));
}
2025-09-05 16:19:31 +09:00
}
2025-09-16 15:43:18 +09:00
}, [isOpen, connection, setConditions, loadExistingSettings]);
2025-09-05 16:19:31 +09:00
2025-09-10 17:25:41 +09:00
// From 테이블 선택 시 컬럼 로드
useEffect(() => {
const loadFromColumns = async () => {
if (selectedFromTable) {
try {
const columns = await DataFlowAPI.getTableColumns(selectedFromTable);
setFromTableColumns(columns);
} catch (error) {
console.error("From 테이블 컬럼 로드 실패:", error);
toast.error("From 테이블 컬럼을 불러오는데 실패했습니다.");
}
}
};
loadFromColumns();
}, [selectedFromTable]);
// To 테이블 선택 시 컬럼 로드
useEffect(() => {
const loadToColumns = async () => {
if (selectedToTable) {
try {
const columns = await DataFlowAPI.getTableColumns(selectedToTable);
setToTableColumns(columns);
} catch (error) {
console.error("To 테이블 컬럼 로드 실패:", error);
toast.error("To 테이블 컬럼을 불러오는데 실패했습니다.");
}
}
};
loadToColumns();
}, [selectedToTable]);
// 선택된 컬럼들이 변경될 때 config 업데이트
useEffect(() => {
setConfig((prev) => ({
...prev,
fromColumnName: selectedFromColumns.join(", "),
toColumnName: selectedToColumns.join(", "),
}));
}, [selectedFromColumns, selectedToColumns]);
// 테이블 컬럼 로드 함수 (캐시 활용)
const loadTableColumns = async (tableName: string, forceReload = false): Promise<ColumnInfo[]> => {
if (tableColumnsCache[tableName] && !forceReload) {
return tableColumnsCache[tableName];
}
try {
const columns = await DataFlowAPI.getTableColumns(tableName);
setTableColumnsCache((prev) => ({
...prev,
[tableName]: columns,
}));
return columns;
} catch (error) {
console.error(`${tableName} 컬럼 로드 실패:`, error);
return [];
}
};
// 테이블 선택 시 컬럼 로드
useEffect(() => {
const loadColumns = async () => {
const tablesToLoad = new Set<string>();
// 필드 매핑에서 사용되는 모든 테이블 수집
2025-09-16 15:43:18 +09:00
dataSaveSettings.actions?.forEach((action) => {
action.fieldMappings?.forEach((mapping) => {
if (mapping.sourceTable && !tableColumnsCache[mapping.sourceTable]) {
tablesToLoad.add(mapping.sourceTable);
}
if (mapping.targetTable && !tableColumnsCache[mapping.targetTable]) {
tablesToLoad.add(mapping.targetTable);
}
});
});
// 필요한 테이블들의 컬럼만 로드
for (const tableName of tablesToLoad) {
await loadTableColumns(tableName);
}
};
loadColumns();
}, [dataSaveSettings.actions, tableColumnsCache]); // eslint-disable-line react-hooks/exhaustive-deps
const handleConfirm = () => {
2025-09-08 16:46:53 +09:00
if (!config.relationshipName || !connection) {
toast.error("필수 정보를 모두 입력해주세요.");
return;
}
// 연결 종류별 설정을 준비
let settings = {};
2025-09-17 17:14:59 +09:00
let plan = {}; // plan 변수 선언
switch (config.connectionType) {
case "simple-key":
settings = simpleKeySettings;
break;
case "data-save":
settings = dataSaveSettings;
// INSERT가 아닌 액션 타입에 대한 실행조건 필수 검증
for (const action of dataSaveSettings.actions) {
if (action.actionType !== "insert") {
if (!action.conditions || action.conditions.length === 0) {
toast.error(
`${action.actionType.toUpperCase()} 액션은 실행조건이 필수입니다. '${action.name}' 액션에 실행조건을 추가해주세요.`,
);
return;
}
// 실제 조건이 있는지 확인 (group-start, group-end만 있는 경우 제외)
const hasValidConditions = action.conditions.some((condition) => {
if (condition.type !== "condition") return false;
if (!condition.field || !condition.operator) return false;
// value가 null, undefined, 빈 문자열이면 유효하지 않음
const value = condition.value;
if (value === null || value === undefined || value === "") return false;
return true;
});
if (!hasValidConditions) {
toast.error(
`${action.actionType.toUpperCase()} 액션은 완전한 실행조건이 필요합니다. '${action.name}' 액션에 필드, 연산자, 값을 모두 설정해주세요.`,
);
return;
}
}
}
break;
case "external-call":
2025-09-17 17:14:59 +09:00
// 외부 호출은 plan에 저장
plan = {
externalCall: {
configId: externalCallSettings.configId,
configName: externalCallSettings.configName,
message: externalCallSettings.message,
},
};
settings = {}; // 외부 호출은 settings에 저장하지 않음
break;
}
2025-09-08 16:46:53 +09:00
2025-09-15 15:12:02 +09:00
// 단순 키값 연결일 때만 컬럼 선택 검증
if (config.connectionType === "simple-key") {
2025-09-16 12:37:57 +09:00
if (selectedFromColumns.length === 0 || selectedToColumns.length === 0) {
toast.error("선택된 컬럼이 없습니다. From과 To 테이블에서 각각 최소 1개 이상의 컬럼을 선택해주세요.");
return;
2025-09-15 15:12:02 +09:00
}
2025-09-05 16:19:31 +09:00
}
2025-09-10 17:25:41 +09:00
// 선택된 테이블과 컬럼 정보 사용
const fromTableName = selectedFromTable || connection.fromNode.tableName;
const toTableName = selectedToTable || connection.toNode.tableName;
2025-09-12 09:58:49 +09:00
// 조건부 연결 설정 데이터 준비
2025-09-16 15:43:18 +09:00
const conditionalSettings = isConditionalConnection(config.connectionType)
2025-09-12 09:58:49 +09:00
? {
control: {
2025-09-12 11:33:54 +09:00
triggerType: "insert",
2025-09-15 11:17:46 +09:00
conditionTree: conditions.length > 0 ? conditions : null,
2025-09-12 09:58:49 +09:00
},
category: {
type: config.connectionType,
},
plan: {
sourceTable: fromTableName,
targetActions:
config.connectionType === "data-save"
? dataSaveSettings.actions.map((action) => ({
id: action.id,
actionType: action.actionType,
enabled: true,
2025-09-16 14:44:41 +09:00
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,
targetTable: mapping.targetTable,
targetField: mapping.targetField,
defaultValue: mapping.defaultValue,
transformFunction: mapping.transformFunction,
})),
splitConfig: action.splitConfig,
}))
: [],
2025-09-12 09:58:49 +09:00
},
}
: {};
2025-09-15 15:12:02 +09:00
// 컬럼 정보는 단순 키값 연결일 때만 사용
const finalFromColumns = config.connectionType === "simple-key" ? selectedFromColumns : [];
const finalToColumns = config.connectionType === "simple-key" ? selectedToColumns : [];
// 메모리 기반 시스템: 관계 데이터만 생성하여 부모로 전달
const relationshipData: TableRelationship = {
relationship_name: config.relationshipName,
2025-09-10 17:25:41 +09:00
from_table_name: fromTableName,
to_table_name: toTableName,
2025-09-15 15:12:02 +09:00
from_column_name: finalFromColumns.join(","), // 여러 컬럼을 콤마로 구분
to_column_name: finalToColumns.join(","), // 여러 컬럼을 콤마로 구분
2025-09-12 09:58:49 +09:00
connection_type: config.connectionType,
company_code: companyCode,
settings: {
...settings,
2025-09-12 09:58:49 +09:00
...conditionalSettings, // 조건부 연결 설정 추가
2025-09-17 17:14:59 +09:00
...plan, // 외부 호출 plan 추가
},
};
// 성공 모달 표시를 위한 상태 설정
setCreatedConnectionName(config.relationshipName);
setPendingRelationshipData(relationshipData);
setShowSuccessModal(true);
2025-09-05 16:19:31 +09:00
};
2025-09-05 18:00:18 +09:00
const handleCancel = () => {
setConfig({
relationshipName: "",
connectionType: "simple-key",
fromColumnName: "",
toColumnName: "",
});
onCancel();
2025-09-05 16:19:31 +09:00
};
const handleSuccessModalClose = () => {
setShowSuccessModal(false);
setCreatedConnectionName("");
// 저장된 관계 데이터를 부모에게 전달
if (pendingRelationshipData) {
onConfirm(pendingRelationshipData);
setPendingRelationshipData(null);
}
handleCancel(); // 원래 모달도 닫기
};
2025-09-08 16:46:53 +09:00
// 연결 종류별 설정 패널 렌더링
const renderConnectionTypeSettings = () => {
switch (config.connectionType) {
case "simple-key":
2025-09-16 12:37:57 +09:00
return (
2025-09-16 15:43:18 +09:00
<SimpleKeySettingsComponent
settings={simpleKeySettings}
onSettingsChange={setSimpleKeySettings}
availableTables={availableTables}
selectedFromTable={selectedFromTable}
selectedToTable={selectedToTable}
fromTableColumns={fromTableColumns}
toTableColumns={toTableColumns}
selectedFromColumns={selectedFromColumns}
selectedToColumns={selectedToColumns}
onFromColumnsChange={setSelectedFromColumns}
onToColumnsChange={setSelectedToColumns}
/>
2025-09-08 16:46:53 +09:00
);
case "data-save":
return (
2025-09-16 15:43:18 +09:00
<DataSaveSettingsComponent
settings={dataSaveSettings}
onSettingsChange={setDataSaveSettings}
availableTables={availableTables}
fromTableColumns={fromTableColumns}
toTableColumns={toTableColumns}
fromTableName={selectedFromTable}
toTableName={selectedToTable}
2025-09-16 15:43:18 +09:00
tableColumnsCache={tableColumnsCache}
/>
2025-09-08 16:46:53 +09:00
);
2025-09-05 16:19:31 +09:00
2025-09-08 16:46:53 +09:00
case "external-call":
return (
2025-09-16 15:43:18 +09:00
<ExternalCallSettingsComponent settings={externalCallSettings} onSettingsChange={setExternalCallSettings} />
2025-09-08 16:46:53 +09:00
);
2025-09-05 18:00:18 +09:00
2025-09-08 16:46:53 +09:00
default:
return null;
}
};
2025-09-16 15:43:18 +09:00
const isButtonDisabled = () => {
2025-09-16 17:50:01 +09:00
// 공통 검증: 관계 이름은 필수
const hasRelationshipName = !!config.relationshipName?.trim();
if (!hasRelationshipName) return true;
// 연결 타입별 검증
switch (config.connectionType) {
case "simple-key":
// 단순 키값 연결: From과 To 컬럼이 모두 선택되어야 함
const hasFromColumns = selectedFromColumns.length > 0;
const hasToColumns = selectedToColumns.length > 0;
return !hasFromColumns || !hasToColumns;
2025-09-16 15:43:18 +09:00
2025-09-16 17:50:01 +09:00
case "data-save":
// 데이터 저장: 액션과 필드 매핑이 완성되어야 함
const hasActions = dataSaveSettings.actions.length > 0;
// DELETE 액션은 필드 매핑이 필요 없음
const allActionsHaveMappings = dataSaveSettings.actions.every((action) => {
if (action.actionType === "delete") {
return true; // DELETE는 필드 매핑 불필요
}
return action.fieldMappings.length > 0;
});
const allMappingsComplete = dataSaveSettings.actions.every((action) => {
if (action.actionType === "delete") {
return true; // DELETE는 필드 매핑 검증 생략
}
// INSERT 액션의 경우 최소 하나의 매핑이 있으면 됨 (모든 컬럼 매핑 필수 조건 제거)
if (action.actionType === "insert") {
return true; // 필드 매핑이 있으면 충분함
}
return action.fieldMappings.every((mapping) => {
2025-09-16 17:50:01 +09:00
// 타겟은 항상 필요
if (!mapping.targetTable || !mapping.targetField) return false;
// 소스와 기본값 중 하나는 있어야 함
const hasSource = mapping.sourceTable && mapping.sourceField;
const hasDefault = mapping.defaultValue && mapping.defaultValue.trim();
// FROM 테이블이 비어있으면 기본값이 필요
if (!mapping.sourceTable) {
return !!hasDefault;
}
// FROM 테이블이 있으면 소스 매핑 완성 또는 기본값 필요
return hasSource || hasDefault;
});
});
// INSERT가 아닌 액션 타입에 대한 실행조건 필수 검증
const allRequiredConditionsMet = dataSaveSettings.actions.every((action) => {
if (action.actionType === "insert") {
return true; // INSERT는 조건 불필요
}
// INSERT가 아닌 액션은 유효한 조건이 있어야 함
if (!action.conditions || action.conditions.length === 0) {
return false;
}
// 실제 조건이 있는지 확인 (group-start, group-end만 있는 경우 제외)
const hasValidConditions = action.conditions.some((condition) => {
if (condition.type !== "condition") return false;
if (!condition.field || !condition.operator) return false;
// value가 null, undefined, 빈 문자열이면 유효하지 않음
const value = condition.value;
if (value === null || value === undefined || value === "") return false;
return true;
});
return hasValidConditions;
});
return !hasActions || !allActionsHaveMappings || !allMappingsComplete || !allRequiredConditionsMet;
2025-09-16 15:43:18 +09:00
2025-09-16 17:50:01 +09:00
case "external-call":
2025-09-17 17:14:59 +09:00
// 외부 호출: 설정 ID와 메시지가 있어야 함
return !externalCallSettings.configId || !externalCallSettings.message?.trim();
2025-09-16 17:50:01 +09:00
default:
return false;
}
2025-09-16 15:43:18 +09:00
};
if (!connection) return null;
2025-09-08 16:46:53 +09:00
return (
<>
<Dialog open={isOpen} onOpenChange={handleCancel}>
<DialogContent className="max-h-[80vh] max-w-3xl overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-lg">
<Link className="h-4 w-4" />
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
{/* 기본 연결 설정 */}
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="relationshipName"> </Label>
<Input
id="relationshipName"
value={config.relationshipName}
onChange={(e) => setConfig({ ...config, relationshipName: e.target.value })}
placeholder="employee_id_department_id_연결"
className="text-sm"
/>
</div>
2025-09-08 16:46:53 +09:00
</div>
{/* 연결 종류 선택 */}
<ConnectionTypeSelector config={config} onConfigChange={setConfig} />
{/* 조건부 연결을 위한 조건 설정 */}
{isConditionalConnection(config.connectionType) && (
<ConditionalSettings
conditions={conditions}
fromTableColumns={fromTableColumns}
fromTableName={selectedFromTable || connection.fromNode.tableName}
onAddCondition={addCondition}
onAddGroupStart={addGroupStart}
onAddGroupEnd={addGroupEnd}
onUpdateCondition={updateCondition}
onRemoveCondition={removeCondition}
getCurrentGroupLevel={getCurrentGroupLevel}
/>
)}
{/* 연결 종류별 상세 설정 */}
{renderConnectionTypeSettings()}
2025-09-08 16:46:53 +09:00
</div>
<DialogFooter>
<Button variant="outline" onClick={handleCancel}>
</Button>
<Button onClick={handleConfirm} disabled={isButtonDisabled()}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<AlertDialog open={showSuccessModal} onOpenChange={setShowSuccessModal}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2">
<CheckCircle className="h-5 w-5 text-green-500" />
</AlertDialogTitle>
<AlertDialogDescription className="text-base">
<span className="font-medium text-green-600">{createdConnectionName}</span> .
<br />
<span className="mt-2 block text-sm text-gray-500">
.
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction onClick={handleSuccessModalClose}></AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
2025-09-05 16:19:31 +09:00
);
};