데이터 저장
This commit is contained in:
@@ -73,7 +73,7 @@ interface DataFlowDesignerProps {
|
||||
|
||||
// 내부에서 사용할 확장된 JsonRelationship 타입 (connectionType 포함)
|
||||
interface ExtendedJsonRelationship extends JsonRelationship {
|
||||
connectionType: string;
|
||||
connectionType: "simple-key" | "data-save" | "external-call";
|
||||
}
|
||||
|
||||
export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
@@ -111,7 +111,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
existingRelationship?: {
|
||||
relationshipName: string;
|
||||
connectionType: string;
|
||||
settings?: any;
|
||||
settings?: Record<string, unknown>;
|
||||
};
|
||||
} | null>(null);
|
||||
const [relationships, setRelationships] = useState<TableRelationship[]>([]); // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
@@ -136,7 +136,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
const [currentDiagramCategory, setCurrentDiagramCategory] = useState<string>("simple-key"); // 현재 관계도의 연결 종류
|
||||
const [selectedEdgeForEdit, setSelectedEdgeForEdit] = useState<Edge | null>(null); // 수정/삭제할 엣지
|
||||
const [showEdgeActions, setShowEdgeActions] = useState(false); // 엣지 액션 버튼 표시 상태
|
||||
const [edgeActionPosition, setEdgeActionPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); // 액션 버튼 위치
|
||||
const [edgeActionPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); // 액션 버튼 위치 (사용하지 않지만 기존 코드 호환성 유지)
|
||||
const [editingRelationshipId, setEditingRelationshipId] = useState<string | null>(null); // 현재 수정 중인 관계 ID
|
||||
const [showRelationshipListModal, setShowRelationshipListModal] = useState(false); // 관계 목록 모달 표시 상태
|
||||
const [selectedTablePairRelationships, setSelectedTablePairRelationships] = useState<ExtendedJsonRelationship[]>([]); // 선택된 테이블 쌍의 관계들
|
||||
@@ -225,15 +225,28 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
console.log("📋 관계 목록:", relationships);
|
||||
console.log("📊 테이블 목록:", tableNames);
|
||||
|
||||
// 🔥 수정: category 배열에서 각 관계의 connectionType 복원
|
||||
const categoryMap = new Map<string, string>();
|
||||
if (Array.isArray(jsonDiagram.category)) {
|
||||
jsonDiagram.category.forEach((cat: { id: string; category: string }) => {
|
||||
if (cat.id && cat.category) {
|
||||
categoryMap.set(cat.id, cat.category);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 기존 데이터에서 relationshipName이 없는 경우 기본값 설정
|
||||
// category를 각 관계의 connectionType으로 복원
|
||||
const normalizedRelationships: ExtendedJsonRelationship[] = relationships.map((rel: JsonRelationship) => ({
|
||||
...rel,
|
||||
relationshipName: rel.relationshipName || `${rel.fromTable} → ${rel.toTable}`, // 기본값 설정
|
||||
connectionType: jsonDiagram.category || "simple-key", // 관계도의 category를 각 관계의 connectionType으로 복원
|
||||
connectionType: (rel.connectionType || categoryMap.get(rel.id) || "simple-key") as
|
||||
| "simple-key"
|
||||
| "data-save"
|
||||
| "external-call", // category 배열에서 복원
|
||||
}));
|
||||
|
||||
// 메모리에 관계 저장 (기존 관계도 편집 시)
|
||||
console.log("🔥 정규화된 관계들:", normalizedRelationships);
|
||||
setTempRelationships(normalizedRelationships);
|
||||
setCurrentDiagramId(currentDiagramId);
|
||||
setCurrentDiagramCategory(jsonDiagram.category || "simple-key"); // 관계도의 연결 종류 설정
|
||||
@@ -341,15 +354,21 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
const relationshipEdges: Edge[] = [];
|
||||
const tableRelationshipCount: { [key: string]: number } = {}; // 테이블 쌍별 관계 개수
|
||||
|
||||
console.log("🔥 엣지 생성 시작 - 관계 개수:", normalizedRelationships.length);
|
||||
normalizedRelationships.forEach((rel: ExtendedJsonRelationship) => {
|
||||
console.log("🔥 관계 처리 중:", rel.id, rel.connectionType, rel.fromTable, "→", rel.toTable);
|
||||
const fromTable = rel.fromTable;
|
||||
const toTable = rel.toTable;
|
||||
const fromColumns = rel.fromColumns || [];
|
||||
const toColumns = rel.toColumns || [];
|
||||
|
||||
// 🔥 수정: 컬럼 정보가 없어도 엣지는 생성 (data-save 연결 등에서는 컬럼이 없을 수 있음)
|
||||
if (fromColumns.length === 0 || toColumns.length === 0) {
|
||||
console.warn("⚠️ 컬럼 정보가 없습니다:", { fromColumns, toColumns });
|
||||
return;
|
||||
console.warn("⚠️ 컬럼 정보가 없지만 엣지는 생성합니다:", {
|
||||
fromColumns,
|
||||
toColumns,
|
||||
connectionType: rel.connectionType,
|
||||
});
|
||||
}
|
||||
|
||||
// 테이블 쌍 키 생성 (양방향 동일하게 처리)
|
||||
@@ -404,6 +423,15 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
|
||||
console.log("🔗 생성된 관계 에지 수:", relationshipEdges.length);
|
||||
console.log("📍 관계 에지 상세:", relationshipEdges);
|
||||
console.log(
|
||||
"🔥 최종 엣지 설정 전 확인:",
|
||||
relationshipEdges.map((e) => ({
|
||||
id: e.id,
|
||||
source: e.source,
|
||||
target: e.target,
|
||||
connectionType: e.data?.connectionType,
|
||||
})),
|
||||
);
|
||||
setEdges(relationshipEdges);
|
||||
toast.success("관계도를 불러왔습니다.", { id: "load-diagram" });
|
||||
} catch (error) {
|
||||
@@ -708,7 +736,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
toTable,
|
||||
fromColumns,
|
||||
toColumns,
|
||||
connectionType: relationship.connection_type,
|
||||
connectionType: relationship.connection_type as "simple-key" | "data-save" | "external-call",
|
||||
settings: relationship.settings || {},
|
||||
};
|
||||
|
||||
@@ -723,6 +751,9 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
setTempRelationships((prev) => [...prev, newRelationship]);
|
||||
setHasUnsavedChanges(true);
|
||||
|
||||
console.log("🔥 새 관계 생성:", newRelationship);
|
||||
console.log("🔥 연결 타입:", newRelationship.connectionType);
|
||||
|
||||
// 첫 번째 관계가 추가되면 관계도의 category를 해당 connectionType으로 설정
|
||||
if (tempRelationships.length === 0) {
|
||||
setCurrentDiagramCategory(relationship.connection_type);
|
||||
@@ -743,18 +774,19 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
data: {
|
||||
relationshipId: newRelationship.id,
|
||||
relationshipName: newRelationship.relationshipName,
|
||||
connectionType: relationship.connection_type,
|
||||
connectionType: newRelationship.connectionType, // 🔥 수정: newRelationship 사용
|
||||
fromTable,
|
||||
toTable,
|
||||
fromColumns,
|
||||
toColumns,
|
||||
details: {
|
||||
connectionInfo: `${fromTable}(${fromColumns.join(", ")}) → ${toTable}(${toColumns.join(", ")})`,
|
||||
connectionType: relationship.connection_type,
|
||||
connectionType: newRelationship.connectionType, // 🔥 수정: newRelationship 사용
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
console.log("🔥 새 엣지 생성:", newEdge);
|
||||
setEdges((eds) => [...eds, newEdge]);
|
||||
setPendingConnection(null);
|
||||
|
||||
@@ -764,7 +796,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
console.log("메모리에 관계 생성 완료:", newRelationship);
|
||||
toast.success("관계가 생성되었습니다. 저장 버튼을 눌러 관계도를 저장하세요.");
|
||||
},
|
||||
[pendingConnection, setEdges],
|
||||
[pendingConnection, setEdges, editingRelationshipId, tempRelationships.length],
|
||||
);
|
||||
|
||||
// 연결 설정 취소
|
||||
@@ -811,26 +843,114 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
console.log("📋 연결된 테이블 목록:", connectedTables);
|
||||
console.log("🔗 관계 개수:", tempRelationships.length);
|
||||
|
||||
// 관계도의 주요 연결 타입 결정 (첫 번째 관계의 connectionType 사용)
|
||||
const primaryConnectionType = tempRelationships.length > 0 ? tempRelationships[0].connectionType : "simple-key";
|
||||
// 🔥 주요 연결 타입 변수 제거 (더 이상 사용하지 않음)
|
||||
|
||||
// connectionType을 관계에서 제거하고 관계도 레벨로 이동
|
||||
const relationshipsWithoutConnectionType = tempRelationships.map((rel) => {
|
||||
const { connectionType, ...relationshipWithoutType } = rel;
|
||||
return relationshipWithoutType;
|
||||
// 🔥 수정: relationships는 핵심 관계 정보만 포함, settings 전체 제거
|
||||
const cleanRelationships = tempRelationships.map((rel) => {
|
||||
// 🔥 settings 전체를 제거하고 핵심 정보만 유지
|
||||
const cleanRel: JsonRelationship = {
|
||||
id: rel.id,
|
||||
fromTable: rel.fromTable,
|
||||
toTable: rel.toTable,
|
||||
relationshipName: rel.relationshipName,
|
||||
connectionType: rel.connectionType,
|
||||
// simple-key가 아닌 경우 컬럼 정보 제거
|
||||
fromColumns: rel.connectionType === "simple-key" ? rel.fromColumns : [],
|
||||
toColumns: rel.connectionType === "simple-key" ? rel.toColumns : [],
|
||||
};
|
||||
|
||||
return cleanRel;
|
||||
});
|
||||
|
||||
// 저장 요청 데이터 생성
|
||||
const createRequest: CreateDiagramRequest = {
|
||||
diagram_name: diagramName,
|
||||
relationships: {
|
||||
relationships: relationshipsWithoutConnectionType,
|
||||
relationships: cleanRelationships as JsonRelationship[],
|
||||
tables: connectedTables,
|
||||
},
|
||||
node_positions: nodePositions,
|
||||
category: primaryConnectionType, // connectionType을 관계도 레벨의 category로 이동
|
||||
// 🔥 수정: 각 관계별 category 정보를 배열로 저장
|
||||
category: tempRelationships.map((rel) => ({
|
||||
id: rel.id,
|
||||
category: rel.connectionType,
|
||||
})),
|
||||
// 🔥 각 관계별 control 정보를 배열로 저장 (전체 실행 조건)
|
||||
control: tempRelationships
|
||||
.filter((rel) => rel.connectionType === "data-save")
|
||||
.map((rel) => {
|
||||
console.log("🔍 Control 데이터 추출 중:", {
|
||||
id: rel.id,
|
||||
settings: rel.settings,
|
||||
control: rel.settings?.control,
|
||||
settingsKeys: Object.keys(rel.settings || {}),
|
||||
});
|
||||
|
||||
const controlData = rel.settings?.control as {
|
||||
triggerType?: "insert" | "update" | "delete";
|
||||
conditionTree?: Array<{
|
||||
field: string;
|
||||
operator_type: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
|
||||
value: unknown;
|
||||
logicalOperator?: "AND" | "OR";
|
||||
}>;
|
||||
};
|
||||
|
||||
console.log("🔍 추출된 controlData:", controlData);
|
||||
console.log("🔍 conditionTree:", controlData?.conditionTree);
|
||||
|
||||
return {
|
||||
id: rel.id, // relationships의 id와 동일
|
||||
triggerType: (controlData?.triggerType as "insert" | "update" | "delete") || "insert",
|
||||
// 🔥 실제 저장된 conditionTree에서 조건 추출
|
||||
conditions: (controlData?.conditionTree || []).map((cond) => ({
|
||||
field: cond.field,
|
||||
operator: cond.operator_type,
|
||||
value: cond.value,
|
||||
logicalOperator: cond.logicalOperator || "AND",
|
||||
})),
|
||||
};
|
||||
}),
|
||||
// 🔥 각 관계별 plan 정보를 배열로 저장 (저장 액션)
|
||||
plan: tempRelationships
|
||||
.filter((rel) => rel.connectionType === "data-save")
|
||||
.map((rel) => ({
|
||||
id: rel.id, // relationships의 id와 동일
|
||||
sourceTable: rel.fromTable,
|
||||
// 🔥 실제 사용자가 설정한 액션들 사용
|
||||
actions:
|
||||
(rel.settings?.actions as Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
actionType: "insert" | "update" | "delete" | "upsert";
|
||||
fieldMappings: Array<{
|
||||
sourceTable?: string;
|
||||
sourceField: string;
|
||||
targetTable?: string;
|
||||
targetField: string;
|
||||
defaultValue?: string;
|
||||
transformFunction?: string;
|
||||
}>;
|
||||
conditions?: Array<{
|
||||
id: string;
|
||||
type: string;
|
||||
field: string;
|
||||
operator_type: string;
|
||||
value: unknown;
|
||||
logicalOperator?: string;
|
||||
}>;
|
||||
}>) || [],
|
||||
})),
|
||||
};
|
||||
|
||||
// 🔍 디버깅: tempRelationships 구조 확인
|
||||
console.log("🔍 tempRelationships 전체 구조:", JSON.stringify(tempRelationships, null, 2));
|
||||
tempRelationships.forEach((rel, index) => {
|
||||
console.log(`🔍 관계 ${index + 1} settings:`, rel.settings);
|
||||
console.log(`🔍 관계 ${index + 1} settings.control:`, rel.settings?.control);
|
||||
console.log(`🔍 관계 ${index + 1} settings.actions:`, rel.settings?.actions);
|
||||
});
|
||||
|
||||
console.log("🚀 API 요청 데이터:", JSON.stringify(createRequest, null, 2));
|
||||
|
||||
let savedDiagram;
|
||||
@@ -1181,35 +1301,121 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
{/* 관계 목록 */}
|
||||
<div className="p-3">
|
||||
<div className="max-h-96 space-y-2 overflow-y-auto">
|
||||
{selectedTablePairRelationships.map((relationship, index) => (
|
||||
{selectedTablePairRelationships.map((relationship) => (
|
||||
<div
|
||||
key={relationship.id}
|
||||
onClick={() => {
|
||||
// 관계 선택 시 수정 모드로 전환
|
||||
setEditingRelationshipId(relationship.id);
|
||||
|
||||
// 관련 컬럼 하이라이트
|
||||
const newSelectedColumns: { [tableName: string]: string[] } = {};
|
||||
if (relationship.fromTable && relationship.fromColumns) {
|
||||
newSelectedColumns[relationship.fromTable] = [...relationship.fromColumns];
|
||||
}
|
||||
if (relationship.toTable && relationship.toColumns) {
|
||||
newSelectedColumns[relationship.toTable] = [...relationship.toColumns];
|
||||
}
|
||||
setSelectedColumns(newSelectedColumns);
|
||||
|
||||
// 모달 닫기
|
||||
setShowRelationshipListModal(false);
|
||||
}}
|
||||
className="cursor-pointer rounded-lg border border-gray-200 p-3 transition-all hover:border-blue-300 hover:bg-blue-50"
|
||||
className="rounded-lg border border-gray-200 p-3 transition-all hover:border-blue-300 hover:bg-blue-50"
|
||||
>
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<h4 className="text-sm font-medium text-gray-900">
|
||||
{relationship.fromTable} → {relationship.toTable}
|
||||
</h4>
|
||||
<svg className="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
<div className="flex items-center gap-1">
|
||||
{/* 편집 버튼 */}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 관계 선택 시 수정 모드로 전환
|
||||
setEditingRelationshipId(relationship.id);
|
||||
|
||||
// 관련 컬럼 하이라이트
|
||||
const newSelectedColumns: { [tableName: string]: string[] } = {};
|
||||
if (relationship.fromTable && relationship.fromColumns) {
|
||||
newSelectedColumns[relationship.fromTable] = [...relationship.fromColumns];
|
||||
}
|
||||
if (relationship.toTable && relationship.toColumns) {
|
||||
newSelectedColumns[relationship.toTable] = [...relationship.toColumns];
|
||||
}
|
||||
setSelectedColumns(newSelectedColumns);
|
||||
|
||||
// 🔥 수정: 연결 설정 모달 열기
|
||||
const fromTable = nodes.find(
|
||||
(node) => node.data?.table?.tableName === relationship.fromTable,
|
||||
);
|
||||
const toTable = nodes.find(
|
||||
(node) => node.data?.table?.tableName === relationship.toTable,
|
||||
);
|
||||
|
||||
if (fromTable && toTable) {
|
||||
setPendingConnection({
|
||||
fromNode: {
|
||||
id: fromTable.id,
|
||||
tableName: relationship.fromTable,
|
||||
displayName: fromTable.data?.table?.displayName || relationship.fromTable,
|
||||
},
|
||||
toNode: {
|
||||
id: toTable.id,
|
||||
tableName: relationship.toTable,
|
||||
displayName: toTable.data?.table?.displayName || relationship.toTable,
|
||||
},
|
||||
selectedColumnsData: {
|
||||
[relationship.fromTable]: {
|
||||
displayName: fromTable.data?.table?.displayName || relationship.fromTable,
|
||||
columns: relationship.fromColumns || [],
|
||||
},
|
||||
[relationship.toTable]: {
|
||||
displayName: toTable.data?.table?.displayName || relationship.toTable,
|
||||
columns: relationship.toColumns || [],
|
||||
},
|
||||
},
|
||||
existingRelationship: {
|
||||
relationshipName: relationship.relationshipName,
|
||||
connectionType: relationship.connectionType,
|
||||
settings: relationship.settings || {},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 모달 닫기
|
||||
setShowRelationshipListModal(false);
|
||||
}}
|
||||
className="flex h-6 w-6 items-center justify-center rounded text-gray-400 hover:bg-blue-100 hover:text-blue-600"
|
||||
title="관계 편집"
|
||||
>
|
||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{/* 삭제 버튼 */}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 관계 삭제
|
||||
setTempRelationships((prev) => prev.filter((rel) => rel.id !== relationship.id));
|
||||
setEdges((prev) =>
|
||||
prev.filter((edge) => edge.data?.relationshipId !== relationship.id),
|
||||
);
|
||||
setHasUnsavedChanges(true);
|
||||
|
||||
// 선택된 컬럼 초기화
|
||||
setSelectedColumns({});
|
||||
|
||||
// 편집 모드 해제
|
||||
if (editingRelationshipId === relationship.id) {
|
||||
setEditingRelationshipId(null);
|
||||
}
|
||||
|
||||
// 모달 닫기
|
||||
setShowRelationshipListModal(false);
|
||||
}}
|
||||
className="flex h-6 w-6 items-center justify-center rounded text-gray-400 hover:bg-red-100 hover:text-red-600"
|
||||
title="관계 삭제"
|
||||
>
|
||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 text-xs text-gray-600">
|
||||
<p>타입: {relationship.connectionType}</p>
|
||||
@@ -1474,7 +1680,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
isOpen={showSaveModal}
|
||||
onClose={handleCloseSaveModal}
|
||||
onSave={handleSaveDiagram}
|
||||
relationships={tempRelationships}
|
||||
relationships={tempRelationships as JsonRelationship[]} // 타입 단언 추가
|
||||
defaultName={
|
||||
diagramId && diagramId > 0 && currentDiagramName
|
||||
? currentDiagramName // 편집 모드: 기존 관계도 이름
|
||||
|
||||
Reference in New Issue
Block a user