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() { {/* 데이터플로우 디자이너 */}
; @@ -170,23 +170,92 @@ 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) { + const settings = existingRel.settings; - // 데이터 저장 기본값 설정 (빈 배열로 시작) - setDataSaveSettings({ - actions: [], - }); + 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: any) => ({ + id: action.id || `action-${Date.now()}`, + name: action.name || "새 액션", + actionType: action.actionType || "insert", + conditions: Array.isArray(action.conditions) ? action.conditions : [], + fieldMappings: Array.isArray(action.fieldMappings) + ? action.fieldMappings.map((mapping: any) => ({ + sourceTable: mapping.sourceTable || "", + sourceField: mapping.sourceField || "", + targetTable: mapping.targetTable || "", + targetField: mapping.targetField || "", + defaultValue: mapping.defaultValue || "", + transformFunction: mapping.transformFunction || "", + })) + : [], + splitConfig: action.splitConfig + ? { + sourceField: action.splitConfig.sourceField || "", + delimiter: action.splitConfig.delimiter || ",", + targetField: action.splitConfig.targetField || "", + } + : undefined, + })), + }); + + // 전체 실행 조건 로드 - 안전하게 처리 + if (settings.control) { + const controlSettings = settings.control as { conditionTree?: ConditionNode[] }; + if (Array.isArray(controlSettings.conditionTree)) { + // 기존 조건이 없을 때만 로드 (사용자가 추가한 조건 보존) + setConditions((prevConditions) => { + if (prevConditions.length === 0) { + return controlSettings.conditionTree || []; + } + return prevConditions; + }); + } else { + // 기존 조건이 없을 때만 초기화 + setConditions((prevConditions) => (prevConditions.length === 0 ? [] : prevConditions)); + } + } else { + // 기존 조건이 없을 때만 초기화 + setConditions((prevConditions) => (prevConditions.length === 0 ? [] : prevConditions)); + } + } 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) || "{}", + }); + } + } else { + // 기본값 설정 + setSimpleKeySettings({ + notes: `${fromDisplayName}과 ${toDisplayName} 간의 키값 연결`, + }); + + setDataSaveSettings({ + actions: [], + }); + } // 🔥 필드 선택 상태 초기화 setSelectedFromColumns([]); @@ -286,8 +355,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 +398,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; } } @@ -433,8 +502,11 @@ export const ConnectionSetupModal: React.FC = ({ operator_type: "=", value: "", dataType: "string", - logicalOperator: "AND", // 기본값으로 AND 설정 + // 첫 번째 조건이 아니고, 바로 앞이 group-start가 아니면 logicalOperator 추가 + ...(conditions.length > 0 && + conditions[conditions.length - 1]?.type !== "group-start" && { logicalOperator: "AND" }), }; + setConditions([...conditions, newCondition]); }; @@ -448,7 +520,8 @@ export const ConnectionSetupModal: React.FC = ({ type: "group-start", groupId, groupLevel, - logicalOperator: conditions.length > 0 ? "AND" : undefined, + // 첫 번째 그룹이 아니면 logicalOperator 추가 + ...(conditions.length > 0 && { logicalOperator: "AND" }), }; setConditions([...conditions, groupStart]); @@ -626,7 +699,7 @@ export const ConnectionSetupModal: React.FC = ({ const renderActionCondition = (condition: ConditionNode, condIndex: number, actionIndex: number) => { // 그룹 시작 렌더링 if (condition.type === "group-start") { - return ( + return (
{/* 그룹 시작 앞의 논리 연산자 */} {condIndex > 0 && ( @@ -667,8 +740,8 @@ export const ConnectionSetupModal: React.FC = ({ > -
-
+ + ); } @@ -696,13 +769,13 @@ export const ConnectionSetupModal: React.FC = ({ > - - ); + + ); } // 일반 조건 렌더링 (기존 로직 간소화) - return ( + return (
{/* 그룹 내 첫 번째 조건이 아닐 때만 논리 연산자 표시 */} {condIndex > 0 && dataSaveSettings.actions[actionIndex].conditions![condIndex - 1]?.type !== "group-start" && ( @@ -776,7 +849,7 @@ export const ConnectionSetupModal: React.FC = ({ if (dataType.includes("timestamp") || dataType.includes("datetime") || dataType.includes("date")) { return ( - { @@ -789,7 +862,7 @@ export const ConnectionSetupModal: React.FC = ({ ); } else if (dataType.includes("time")) { return ( - { @@ -880,9 +953,9 @@ export const ConnectionSetupModal: React.FC = ({ className="h-6 w-6 p-0" > - -
- + + + ); }; @@ -893,7 +966,7 @@ export const ConnectionSetupModal: React.FC = ({
전체 실행 조건 (언제 이 연결이 동작할지) -
+ {/* 실행 조건 설정 */}
@@ -910,7 +983,7 @@ export const ConnectionSetupModal: React.FC = ({ -
+ {/* 조건 목록 */} @@ -922,352 +995,379 @@ export const ConnectionSetupModal: React.FC = ({ 조건이 없으면 항상 실행됩니다. ) : ( - conditions.map((condition, index) => { - // 그룹 시작 렌더링 - if (condition.type === "group-start") { + + {conditions.map((condition, index) => { + // 그룹 시작 렌더링 + if (condition.type === "group-start") { + return ( +
+ {/* 그룹 시작 앞의 논리 연산자 - 이전 요소가 group-end가 아닌 경우에만 표시 */} + {index > 0 && conditions[index - 1]?.type !== "group-end" && ( + + )} + {/* 그룹 레벨에 따른 들여쓰기 */} +
+ ( + 그룹 시작 + +
+
+ ); + } + + // 그룹 끝 렌더링 + if (condition.type === "group-end") { + return ( +
+
+ ) + 그룹 끝 + +
+ + {/* 그룹 끝 다음에 다른 조건이나 그룹이 있으면 논리 연산자 표시 */} + {index < conditions.length - 1 && ( + + )} +
+ ); + } + + // 일반 조건 렌더링 return (
- {/* 그룹 시작 앞의 논리 연산자 */} - {index > 0 && ( + {/* 일반 조건 앞의 논리 연산자 - 이전 요소가 group-end가 아닌 경우에만 표시 */} + {index > 0 && + conditions[index - 1]?.type !== "group-start" && + conditions[index - 1]?.type !== "group-end" && ( + + )} + + {/* 그룹 레벨에 따른 들여쓰기와 조건 필드들 */} +
+ {/* 조건 필드 선택 */} + + {/* 연산자 선택 */} + - )} - {/* 그룹 레벨에 따른 들여쓰기 */} -
- ( - 그룹 시작 - -
-
- ); - } - // 그룹 끝 렌더링 - if (condition.type === "group-end") { - return ( -
-
- ) - 그룹 끝 + {/* 데이터 타입에 따른 동적 입력 컴포넌트 */} + {(() => { + 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" + /> + ); + } + })()} + + {/* 삭제 버튼 */} -
+
); - } - - // 일반 조건 렌더링 - 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 ( -
- {/* 테이블 및 컬럼 선택 */} -
-
테이블 및 컬럼 선택
+ 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} - - )) - ) : ( - 선택된 컬럼 없음 + +
+ + {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 ? "컬럼을 불러오는 중..." : "테이블을 먼저 선택해주세요"} +
)}
- -
- {selectedToColumns.length > 0 ? ( - selectedToColumns.map((column) => ( - - {column} - - )) - ) : ( - 선택된 컬럼 없음 + +
+ {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} + + )) + ) : ( + 선택된 컬럼 없음 + )} +
+
+
+ )} +
{/* 단순 키값 연결 설정 */}
@@ -1276,7 +1376,7 @@ export const ConnectionSetupModal: React.FC = ({ 단순 키값 연결 설정
-
+
@@ -1318,7 +1418,7 @@ export const ConnectionSetupModal: React.FC = ({ conditions: [], splitConfig: { sourceField: "", - delimiter: ",", + delimiter: "", targetField: "", }, }; @@ -1334,16 +1434,16 @@ export const ConnectionSetupModal: React.FC = ({
- {dataSaveSettings.actions.length === 0 ? ( + {(dataSaveSettings.actions || []).length === 0 ? (
저장 액션을 추가하여 데이터를 어떻게 저장할지 설정하세요.
) : (
- {dataSaveSettings.actions.map((action, actionIndex) => ( + {(dataSaveSettings.actions || []).map((action, actionIndex) => (
- { const newActions = [...dataSaveSettings.actions]; @@ -1471,9 +1571,11 @@ export const ConnectionSetupModal: React.FC = ({
{action.conditions && action.conditions.length > 0 && (
- {action.conditions.map((condition, condIndex) => - renderActionCondition(condition, condIndex, actionIndex), - )} + {action.conditions.map((condition, condIndex) => ( +
+ {renderActionCondition(condition, condIndex, actionIndex)} +
+ ))}
)}
@@ -1549,13 +1651,13 @@ export const ConnectionSetupModal: React.FC = ({
{ const newActions = [...dataSaveSettings.actions]; if (!newActions[actionIndex].splitConfig) { newActions[actionIndex].splitConfig = { sourceField: "", - delimiter: ",", + delimiter: "", targetField: "", }; } @@ -1627,7 +1729,10 @@ export const ConnectionSetupModal: React.FC = ({
{action.fieldMappings.map((mapping, mappingIndex) => ( -
+
{/* 컴팩트한 매핑 표시 */}
{/* 소스 */} @@ -1850,31 +1955,31 @@ export const ConnectionSetupModal: React.FC = ({ value={externalCallSettings.apiUrl} onChange={(e) => setExternalCallSettings({ ...externalCallSettings, apiUrl: e.target.value })} placeholder="https://api.example.com/webhook" - className="text-sm" - /> -
+ className="text-sm" + /> +
-
+
- -
+ + +
@@ -1222,7 +1376,7 @@ export const DataFlowDesigner: React.FC = ({
메모리 관계: - {tempRelationships.length}개 + {(tempRelationships || []).length}개
관계도 ID: @@ -1313,7 +1467,7 @@ export const DataFlowDesigner: React.FC = ({
{/* 편집 버튼 */}