전체 조건 설정 로직 수정

This commit is contained in:
hyeonsu 2025-09-16 12:37:57 +09:00
parent b1814e6ab8
commit d18e78e8a0
4 changed files with 776 additions and 424 deletions

View File

@ -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() {
{/* 데이터플로우 디자이너 */}
<div className="rounded-lg border border-gray-200 bg-white">
<DataFlowDesigner
key={diagramId}
selectedDiagram={diagramName}
diagramId={diagramId}
onBackToList={handleBackToList}

View File

@ -170,23 +170,92 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
// 기존 관계 정보가 있으면 사용, 없으면 기본값 설정
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 || {},
});
// 단순 키값 연결 기본값 설정
// 🔥 기존 설정 데이터 로드
if (existingRel?.settings) {
const settings = existingRel.settings;
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<ConnectionSetupModalProps> = ({
const tablesToLoad = new Set<string>();
// 필드 매핑에서 사용되는 모든 테이블 수집
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);
}
@ -433,8 +502,11 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
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<ConnectionSetupModalProps> = ({
type: "group-start",
groupId,
groupLevel,
logicalOperator: conditions.length > 0 ? "AND" : undefined,
// 첫 번째 그룹이 아니면 logicalOperator 추가
...(conditions.length > 0 && { logicalOperator: "AND" }),
};
setConditions([...conditions, groupStart]);
@ -922,16 +995,17 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
.
</div>
) : (
conditions.map((condition, index) => {
<React.Fragment key="conditions-list">
{conditions.map((condition, index) => {
// 그룹 시작 렌더링
if (condition.type === "group-start") {
return (
<div key={condition.id} className="flex items-center gap-2">
{/* 그룹 시작 앞의 논리 연산자 */}
{index > 0 && (
{/* 그룹 시작 앞의 논리 연산자 - 이전 요소가 group-end가 아닌 경우에만 표시 */}
{index > 0 && conditions[index - 1]?.type !== "group-end" && (
<Select
value={conditions[index - 1]?.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") => updateCondition(index - 1, "logicalOperator", value)}
value={condition.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") => updateCondition(index, "logicalOperator", value)}
>
<SelectTrigger className="h-8 w-24 border-blue-200 bg-blue-50 text-xs">
<SelectValue />
@ -981,6 +1055,24 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
<Trash2 className="h-3 w-3" />
</Button>
</div>
{/* 그룹 끝 다음에 다른 조건이나 그룹이 있으면 논리 연산자 표시 */}
{index < conditions.length - 1 && (
<Select
value={conditions[index + 1]?.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") =>
updateCondition(index + 1, "logicalOperator", value)
}
>
<SelectTrigger className="h-8 w-24 border-blue-200 bg-blue-50 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="AND">AND</SelectItem>
<SelectItem value="OR">OR</SelectItem>
</SelectContent>
</Select>
)}
</div>
);
}
@ -988,11 +1080,13 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
// 일반 조건 렌더링
return (
<div key={condition.id} className="flex items-center gap-2">
{/* 그룹 내 첫 번째 조건이 아닐 때만 논리 연산자 표시 */}
{index > 0 && conditions[index - 1]?.type !== "group-start" && (
{/* 일반 조건 앞의 논리 연산자 - 이전 요소가 group-end가 아닌 경우에만 표시 */}
{index > 0 &&
conditions[index - 1]?.type !== "group-start" &&
conditions[index - 1]?.type !== "group-end" && (
<Select
value={conditions[index - 1]?.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") => updateCondition(index - 1, "logicalOperator", value)}
value={condition.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") => updateCondition(index, "logicalOperator", value)}
>
<SelectTrigger className="h-8 w-24 border-blue-200 bg-blue-50 text-xs">
<SelectValue />
@ -1125,13 +1219,19 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
})()}
{/* 삭제 버튼 */}
<Button size="sm" variant="ghost" onClick={() => removeCondition(index)} className="h-8 w-8 p-0">
<Button
size="sm"
variant="ghost"
onClick={() => removeCondition(index)}
className="h-8 w-8 p-0"
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
</div>
);
})
})}
</React.Fragment>
)}
</div>
</div>
@ -1318,7 +1418,7 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
conditions: [],
splitConfig: {
sourceField: "",
delimiter: ",",
delimiter: "",
targetField: "",
},
};
@ -1334,13 +1434,13 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
</Button>
</div>
{dataSaveSettings.actions.length === 0 ? (
{(dataSaveSettings.actions || []).length === 0 ? (
<div className="rounded-lg border border-dashed p-3 text-center text-xs text-gray-500">
.
</div>
) : (
<div className="space-y-3">
{dataSaveSettings.actions.map((action, actionIndex) => (
{(dataSaveSettings.actions || []).map((action, actionIndex) => (
<div key={action.id} className="rounded border bg-white p-3">
<div className="mb-3 flex items-center justify-between">
<Input
@ -1471,9 +1571,11 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
</div>
{action.conditions && action.conditions.length > 0 && (
<div className="space-y-2">
{action.conditions.map((condition, condIndex) =>
renderActionCondition(condition, condIndex, actionIndex),
)}
{action.conditions.map((condition, condIndex) => (
<div key={`action-${actionIndex}-condition-${condition.id}`}>
{renderActionCondition(condition, condIndex, actionIndex)}
</div>
))}
</div>
)}
</div>
@ -1549,13 +1651,13 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
<div>
<Label className="text-xs text-gray-500"></Label>
<Input
value={action.splitConfig?.delimiter || ","}
value={action.splitConfig?.delimiter || ""}
onChange={(e) => {
const newActions = [...dataSaveSettings.actions];
if (!newActions[actionIndex].splitConfig) {
newActions[actionIndex].splitConfig = {
sourceField: "",
delimiter: ",",
delimiter: "",
targetField: "",
};
}
@ -1627,7 +1729,10 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
</div>
<div className="space-y-3">
{action.fieldMappings.map((mapping, mappingIndex) => (
<div key={mappingIndex} className="rounded border bg-white p-2">
<div
key={`${action.id}-mapping-${mappingIndex}-${mapping.sourceField || "empty"}-${mapping.targetField || "empty"}`}
className="rounded border bg-white p-2"
>
{/* 컴팩트한 매핑 표시 */}
<div className="flex items-center gap-2 text-xs">
{/* 소스 */}

View File

@ -74,6 +74,56 @@ interface DataFlowDesignerProps {
// 내부에서 사용할 확장된 JsonRelationship 타입 (connectionType 포함)
interface ExtendedJsonRelationship extends JsonRelationship {
connectionType: "simple-key" | "data-save" | "external-call";
settings?: {
control?: {
triggerType?: "insert" | "update" | "delete";
conditionTree?: Array<{
id: string;
type: string;
field?: string;
operator_type?: string;
value?: unknown;
logicalOperator?: string;
groupId?: string;
groupLevel?: number;
}>;
};
actions?: Array<{
id: string;
name: string;
actionType: "insert" | "update" | "delete" | "upsert";
conditions?: Array<{
id: string;
type: string;
field?: string;
operator_type?: string;
value?: unknown;
logicalOperator?: string;
groupId?: string;
groupLevel?: number;
}>;
fieldMappings: Array<{
sourceTable?: string;
sourceField: string;
targetTable?: string;
targetField: string;
defaultValue?: string;
}>;
splitConfig?: {
sourceField: string;
delimiter: string;
targetField: string;
};
}>;
notes?: string;
apiCall?: {
url: string;
method: string;
headers: Array<{ key: string; value: string }>;
body: string;
successCriteria: string;
};
};
}
export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
@ -249,7 +299,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
console.log("🔥 정규화된 관계들:", normalizedRelationships);
setTempRelationships(normalizedRelationships);
setCurrentDiagramId(currentDiagramId);
setCurrentDiagramCategory(jsonDiagram.category || "simple-key"); // 관계도의 연결 종류 설정
setCurrentDiagramCategory("simple-key"); // 관계도의 연결 종류 설정 (기본값)
// 테이블 노드 생성을 위한 테이블 정보 로드
@ -755,7 +805,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
console.log("🔥 연결 타입:", newRelationship.connectionType);
// 첫 번째 관계가 추가되면 관계도의 category를 해당 connectionType으로 설정
if (tempRelationships.length === 0) {
if ((tempRelationships || []).length === 0) {
setCurrentDiagramCategory(relationship.connection_type);
}
@ -796,7 +846,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
console.log("메모리에 관계 생성 완료:", newRelationship);
toast.success("관계가 생성되었습니다. 저장 버튼을 눌러 관계도를 저장하세요.");
},
[pendingConnection, setEdges, editingRelationshipId, tempRelationships.length],
[pendingConnection, setEdges, editingRelationshipId, tempRelationships],
);
// 연결 설정 취소
@ -841,12 +891,12 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
console.log("🔍 저장할 노드 위치 정보:", nodePositions);
console.log("📊 현재 노드 개수:", nodes.length);
console.log("📋 연결된 테이블 목록:", connectedTables);
console.log("🔗 관계 개수:", tempRelationships.length);
console.log("🔗 관계 개수:", (tempRelationships || []).length);
// 🔥 주요 연결 타입 변수 제거 (더 이상 사용하지 않음)
// 🔥 수정: relationships는 핵심 관계 정보만 포함, settings 전체 제거
const cleanRelationships = tempRelationships.map((rel) => {
const cleanRelationships = (tempRelationships || []).map((rel) => {
// 🔥 settings 전체를 제거하고 핵심 정보만 유지
const cleanRel: JsonRelationship = {
id: rel.id,
@ -871,12 +921,12 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
},
node_positions: nodePositions,
// 🔥 수정: 각 관계별 category 정보를 배열로 저장
category: tempRelationships.map((rel) => ({
category: (tempRelationships || []).map((rel) => ({
id: rel.id,
category: rel.connectionType,
})),
// 🔥 각 관계별 control 정보를 배열로 저장 (전체 실행 조건)
control: tempRelationships
control: (tempRelationships || [])
.filter((rel) => rel.connectionType === "data-save")
.map((rel) => {
console.log("🔍 Control 데이터 추출 중:", {
@ -889,37 +939,72 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
const controlData = rel.settings?.control as {
triggerType?: "insert" | "update" | "delete";
conditionTree?: Array<{
field: string;
operator_type: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
value: unknown;
id: string;
type: string;
field?: string;
operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
value?: unknown;
logicalOperator?: "AND" | "OR";
groupId?: string;
groupLevel?: number;
}>;
};
console.log("🔍 추출된 controlData:", controlData);
console.log("🔍 conditionTree:", controlData?.conditionTree);
// 🔥 조건 필터링: 괄호 포함 모든 유효한 조건 유지
const validConditions = (controlData?.conditionTree || [])
.filter((cond) => {
// 괄호 조건은 항상 유지
if (cond.type === "group-start" || cond.type === "group-end") {
return true;
}
// 실제 조건은 field와 value가 있어야 함
return cond.type === "condition" && cond.field && cond.value !== undefined && cond.value !== "";
})
.map((cond, index) => {
// 괄호 조건 처리
if (cond.type === "group-start" || cond.type === "group-end") {
return {
id: rel.id, // relationships의 id와 동일
triggerType: (controlData?.triggerType as "insert" | "update" | "delete") || "insert",
// 🔥 실제 저장된 conditionTree에서 조건 추출
conditions: (controlData?.conditionTree || []).map((cond) => ({
id: cond.id,
type: cond.type,
...(cond.groupId && { groupId: cond.groupId }),
...(cond.groupLevel !== undefined && { groupLevel: cond.groupLevel }),
// 첫 번째가 아닐 때만 logicalOperator 포함
...(index > 0 && cond.logicalOperator && { logicalOperator: cond.logicalOperator }),
};
}
// 일반 조건 처리
return {
id: cond.id,
type: cond.type,
field: cond.field,
operator: cond.operator_type,
value: cond.value,
logicalOperator: cond.logicalOperator || "AND",
})),
dataType: "string", // 기본값
// 첫 번째 조건이 아닐 때만 logicalOperator 포함
...(index > 0 && cond.logicalOperator && { logicalOperator: cond.logicalOperator }),
};
});
return {
id: rel.id, // relationships의 id와 동일
triggerType: (controlData?.triggerType as "insert" | "update" | "delete") || "insert",
conditions: validConditions,
};
}),
// 🔥 각 관계별 plan 정보를 배열로 저장 (저장 액션)
plan: tempRelationships
plan: (tempRelationships || [])
.filter((rel) => rel.connectionType === "data-save")
.map((rel) => ({
id: rel.id, // relationships의 id와 동일
sourceTable: rel.fromTable,
// 🔥 실제 사용자가 설정한 액션들 사용
actions:
(rel.settings?.actions as Array<{
(
rel.settings?.actions as Array<{
id: string;
name: string;
actionType: "insert" | "update" | "delete" | "upsert";
@ -931,15 +1016,82 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
defaultValue?: string;
transformFunction?: string;
}>;
splitConfig?: {
sourceField: string;
delimiter: string;
targetField: string;
};
conditions?: Array<{
id: string;
type: string;
field: string;
operator_type: string;
value: unknown;
field?: string;
operator_type?: string;
value?: unknown;
logicalOperator?: string;
groupId?: string;
groupLevel?: number;
}>;
}>) || [],
}>
)?.map((action) => ({
...action,
// fieldMappings에서 불필요한 transformFunction 제거
fieldMappings: action.fieldMappings?.map((mapping) => ({
sourceTable: mapping.sourceTable,
sourceField: mapping.sourceField,
targetTable: mapping.targetTable,
targetField: mapping.targetField,
defaultValue: mapping.defaultValue,
// transformFunction 제거 - 불필요한 필드
})),
// splitConfig 처리 - 사용자가 설정하지 않은 경우 포함하지 않음
...(action.splitConfig &&
(action.splitConfig.sourceField ||
action.splitConfig.delimiter ||
action.splitConfig.targetField) && {
splitConfig: {
sourceField: action.splitConfig.sourceField || "",
delimiter: action.splitConfig.delimiter || "",
targetField: action.splitConfig.targetField || "",
},
}),
// 🔥 조건 처리: 괄호 포함 모든 유효한 조건 유지
...(action.conditions && {
conditions: action.conditions
.filter((cond) => {
// 괄호 조건은 항상 유지
if (cond.type === "group-start" || cond.type === "group-end") {
return true;
}
// 실제 조건은 field와 value가 있어야 함
return cond.type === "condition" && cond.field && cond.value !== undefined && cond.value !== "";
})
.map((cond, index) => {
// 괄호 조건 처리
if (cond.type === "group-start" || cond.type === "group-end") {
return {
id: cond.id,
type: cond.type,
...(cond.groupId && { groupId: cond.groupId }),
...(cond.groupLevel !== undefined && { groupLevel: cond.groupLevel }),
// 첫 번째가 아닐 때만 logicalOperator 포함
...(index > 0 && cond.logicalOperator && { logicalOperator: cond.logicalOperator }),
};
}
// 일반 조건 처리
return {
id: cond.id,
type: cond.type,
field: cond.field,
value: cond.value,
dataType: "string", // 기본값
operator_type: cond.operator_type,
// 첫 번째 조건이 아닐 때만 logicalOperator 포함
...(index > 0 && cond.logicalOperator && { logicalOperator: cond.logicalOperator }),
};
}),
}),
})) || [],
})),
};
@ -955,11 +1107,13 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
let savedDiagram;
// 편집 모드 vs 신규 생성 모드 구분
if (diagramId && diagramId > 0) {
// 편집 모드 vs 신규 생성 모드 구분 (currentDiagramId 우선 사용)
const effectiveDiagramId = currentDiagramId || diagramId;
if (effectiveDiagramId && effectiveDiagramId > 0) {
// 편집 모드: 기존 관계도 업데이트
savedDiagram = await DataFlowAPI.updateJsonDataFlowDiagram(
diagramId,
effectiveDiagramId,
createRequest,
companyCode,
user?.userId || "SYSTEM",
@ -981,7 +1135,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
setCurrentDiagramId(savedDiagram.diagram_id);
// 관계도 이름 업데이트 (편집 모드일 때만)
if (diagramId && diagramId > 0 && onDiagramNameUpdate) {
if (effectiveDiagramId && effectiveDiagramId > 0 && onDiagramNameUpdate) {
onDiagramNameUpdate(diagramName);
}
@ -993,7 +1147,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
setIsSaving(false);
}
},
[tempRelationships, diagramId, companyCode, user?.userId, nodes, onDiagramNameUpdate],
[tempRelationships, diagramId, currentDiagramId, companyCode, user?.userId, nodes, onDiagramNameUpdate],
);
// 저장 모달 열기
@ -1204,7 +1358,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
hasUnsavedChanges ? "animate-pulse" : ""
}`}
>
💾 {tempRelationships.length > 0 && `(${tempRelationships.length})`}
💾 {(tempRelationships || []).length > 0 && `(${(tempRelationships || []).length})`}
</button>
</div>
@ -1222,7 +1376,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
</div>
<div className="flex justify-between">
<span> :</span>
<span className="font-medium text-orange-600">{tempRelationships.length}</span>
<span className="font-medium text-orange-600">{(tempRelationships || []).length}</span>
</div>
<div className="flex justify-between">
<span> ID:</span>
@ -1313,7 +1467,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
<div className="flex items-center gap-1">
{/* 편집 버튼 */}
<button
onClick={(e) => {
onClick={async (e) => {
e.stopPropagation();
// 관계 선택 시 수정 모드로 전환
setEditingRelationshipId(relationship.id);
@ -1328,7 +1482,34 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
}
setSelectedColumns(newSelectedColumns);
// 🔥 수정: 연결 설정 모달 열기
// 🔥 수정: 데이터베이스에서 관계 설정 정보 로드
let relationshipSettings = {};
if (diagramId && diagramId > 0) {
try {
const jsonDiagram = await DataFlowAPI.getJsonDataFlowDiagramById(
diagramId,
companyCode,
);
if (jsonDiagram && relationship.connectionType === "data-save") {
const control = jsonDiagram.control?.find((c) => c.id === relationship.id);
const plan = jsonDiagram.plan?.find((p) => p.id === relationship.id);
relationshipSettings = {
control: control
? {
triggerType: control.triggerType,
conditionTree: control.conditions || [],
}
: undefined,
actions: plan ? plan.actions || [] : [],
};
}
} catch (error) {
console.error("관계 설정 정보 로드 실패:", error);
}
}
// 연결 설정 모달 열기
const fromTable = nodes.find(
(node) => node.data?.table?.tableName === relationship.fromTable,
);
@ -1361,7 +1542,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
existingRelationship: {
relationshipName: relationship.relationshipName,
connectionType: relationship.connectionType,
settings: relationship.settings || {},
settings: relationshipSettings,
},
});
}
@ -1559,6 +1740,11 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
{/* 연결 설정 모달 */}
<ConnectionSetupModal
key={
pendingConnection
? `${pendingConnection.fromNode?.tableName || "unknown"}-${pendingConnection.toNode?.tableName || "unknown"}`
: "connection-modal"
}
isOpen={!!pendingConnection}
connection={pendingConnection}
companyCode={companyCode}

View File

@ -195,7 +195,56 @@ export interface JsonDataFlowDiagram {
tables: string[];
};
node_positions?: NodePositions;
category?: string; // 연결 종류 ("simple-key", "data-save", "external-call")
category?: Array<{
id: string;
category: string;
}>;
control?: Array<{
id: string;
triggerType: "insert" | "update" | "delete";
conditions: Array<{
id?: string;
type?: string;
field?: string;
operator?: string;
value?: unknown;
logicalOperator?: string;
groupId?: string;
groupLevel?: number;
}>;
}>;
plan?: Array<{
id: string;
sourceTable: string;
actions: Array<{
id: string;
name: string;
actionType: "insert" | "update" | "delete" | "upsert";
conditions?: Array<{
id: string;
type: string;
field?: string;
operator_type?: string;
value?: unknown;
logicalOperator?: string;
groupId?: string;
groupLevel?: number;
}>;
fieldMappings: Array<{
sourceTable?: string;
sourceField: string;
targetTable?: string;
targetField: string;
defaultValue?: string;
transformFunction?: string;
}>;
splitConfig?: {
sourceField: string;
delimiter: string;
targetField: string;
};
}>;
}>;
company_code: string;
created_at?: string;
updated_at?: string;
@ -230,11 +279,16 @@ export interface CreateDiagramRequest {
control?: Array<{
id: string; // relationships의 id와 동일
triggerType: "insert" | "update" | "delete";
conditions?: Array<{
field: string;
operator: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
value: unknown;
conditions: Array<{
id?: string;
type?: string;
field?: string;
operator?: string;
value?: unknown;
dataType?: string;
logicalOperator?: "AND" | "OR";
groupId?: string;
groupLevel?: number;
}>;
}>;
// 🔥 저장 액션 - relationships의 id와 동일한 id 사용
@ -251,15 +305,22 @@ export interface CreateDiagramRequest {
targetTable?: string;
targetField: string;
defaultValue?: string;
transformFunction?: string;
}>;
splitConfig?: {
sourceField: string;
delimiter: string;
targetField: string;
};
conditions?: Array<{
id: string;
type: string;
field: string;
operator_type: string;
value: unknown;
field?: string;
operator_type?: string;
value?: unknown;
dataType?: string;
logicalOperator?: string;
groupId?: string;
groupLevel?: number;
}>;
}>;
}>;
@ -629,7 +690,7 @@ export class DataFlowAPI {
to_table_name: rel.toTable,
from_column_name: rel.fromColumns.join(","),
to_column_name: rel.toColumns.join(","),
connection_type: (jsonDiagram.category as "simple-key" | "data-save" | "external-call") || "simple-key", // 관계도의 category 사용
connection_type: rel.connectionType || "simple-key", // 각 관계의 connectionType 사용
company_code: companyCode, // 실제 사용자 회사 코드 사용
settings: rel.settings || {},
created_at: jsonDiagram.created_at,