데이터 저장

This commit is contained in:
2025-09-15 20:07:28 +09:00
parent 6a04ae450d
commit 2c677c2fb8
7 changed files with 636 additions and 253 deletions

View File

@@ -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 // 편집 모드: 기존 관계도 이름