관계 수정 , 삭제 구현
This commit is contained in:
@@ -121,6 +121,10 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
const [showSaveModal, setShowSaveModal] = useState(false); // 저장 모달 표시 상태
|
||||
const [isSaving, setIsSaving] = useState(false); // 저장 중 상태
|
||||
const [currentDiagramName, setCurrentDiagramName] = useState<string>(""); // 현재 편집 중인 관계도 이름
|
||||
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 [editingRelationshipId, setEditingRelationshipId] = useState<string | null>(null); // 현재 수정 중인 관계 ID
|
||||
const toastShownRef = useRef(false); // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
|
||||
// 편집 모드일 때 관계도 이름 로드
|
||||
@@ -436,7 +440,13 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
if (selectedEdgeInfo) {
|
||||
setSelectedEdgeInfo(null);
|
||||
}
|
||||
}, [selectedEdgeInfo]);
|
||||
if (showEdgeActions) {
|
||||
setShowEdgeActions(false);
|
||||
setSelectedEdgeForEdit(null);
|
||||
}
|
||||
// 컬럼 선택 해제
|
||||
setSelectedColumns({});
|
||||
}, [selectedEdgeInfo, showEdgeActions]);
|
||||
|
||||
// 빈 onConnect 함수 (드래그 연결 비활성화)
|
||||
const onConnect = useCallback(() => {
|
||||
@@ -444,7 +454,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
return;
|
||||
}, []);
|
||||
|
||||
// 엣지 클릭 시 연결 정보 표시
|
||||
// 엣지 클릭 시 연결 정보 표시 및 관련 컬럼 하이라이트
|
||||
const onEdgeClick = useCallback((event: React.MouseEvent, edge: Edge) => {
|
||||
event.stopPropagation();
|
||||
const edgeData = edge.data as {
|
||||
@@ -462,7 +472,9 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
connectionType: string;
|
||||
};
|
||||
};
|
||||
|
||||
if (edgeData) {
|
||||
// 엣지 정보 설정
|
||||
setSelectedEdgeInfo({
|
||||
relationshipId: edgeData.relationshipId,
|
||||
relationshipName: edgeData.relationshipName || "관계",
|
||||
@@ -474,6 +486,26 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
connectionType: edgeData.connectionType,
|
||||
connectionInfo: edgeData.details?.connectionInfo || `${edgeData.fromTable} → ${edgeData.toTable}`,
|
||||
});
|
||||
|
||||
// 관련 컬럼 하이라이트
|
||||
const newSelectedColumns: { [tableName: string]: string[] } = {};
|
||||
|
||||
// fromTable의 컬럼들 선택
|
||||
if (edgeData.fromTable && edgeData.fromColumns) {
|
||||
newSelectedColumns[edgeData.fromTable] = [...edgeData.fromColumns];
|
||||
}
|
||||
|
||||
// toTable의 컬럼들 선택
|
||||
if (edgeData.toTable && edgeData.toColumns) {
|
||||
newSelectedColumns[edgeData.toTable] = [...edgeData.toColumns];
|
||||
}
|
||||
|
||||
setSelectedColumns(newSelectedColumns);
|
||||
|
||||
// 액션 버튼 표시
|
||||
setSelectedEdgeForEdit(edge);
|
||||
setEdgeActionPosition({ x: event.clientX, y: event.clientY });
|
||||
setShowEdgeActions(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -637,15 +669,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
[handleColumnClick, selectedColumns, setNodes],
|
||||
);
|
||||
|
||||
// 노드 전체 삭제
|
||||
const clearNodes = useCallback(() => {
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
setSelectedColumns({});
|
||||
setSelectionOrder([]);
|
||||
setSelectedNodes([]);
|
||||
setCurrentDiagramId(null); // 현재 diagram_id도 초기화
|
||||
}, [setNodes, setEdges]);
|
||||
// 기존 clearNodes 함수 제거 (중복 방지)
|
||||
|
||||
// 현재 추가된 테이블명 목록 가져오기
|
||||
const getSelectedTableNames = useCallback(() => {
|
||||
@@ -671,7 +695,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
|
||||
// JSON 형태의 관계 객체 생성
|
||||
const newRelationship: JsonRelationship = {
|
||||
id: generateUniqueId("rel", Date.now()),
|
||||
id: editingRelationshipId || generateUniqueId("rel", Date.now()), // 수정 모드면 기존 ID 사용
|
||||
fromTable,
|
||||
toTable,
|
||||
fromColumns,
|
||||
@@ -681,6 +705,13 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
settings: relationship.settings || {},
|
||||
};
|
||||
|
||||
// 수정 모드인 경우 기존 관계를 교체
|
||||
if (editingRelationshipId) {
|
||||
setTempRelationships((prev) => prev.filter((rel) => rel.id !== editingRelationshipId));
|
||||
setEdges((prev) => prev.filter((edge) => edge.data?.relationshipId !== editingRelationshipId));
|
||||
setEditingRelationshipId(null); // 수정 모드 해제
|
||||
}
|
||||
|
||||
// 메모리에 관계 추가
|
||||
setTempRelationships((prev) => [...prev, newRelationship]);
|
||||
setHasUnsavedChanges(true);
|
||||
@@ -730,7 +761,11 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
// 연결 설정 취소
|
||||
const handleCancelConnection = useCallback(() => {
|
||||
setPendingConnection(null);
|
||||
}, []);
|
||||
// 수정 모드였다면 해제
|
||||
if (editingRelationshipId) {
|
||||
setEditingRelationshipId(null);
|
||||
}
|
||||
}, [editingRelationshipId]);
|
||||
|
||||
// 관계도 저장 함수
|
||||
const handleSaveDiagram = useCallback(
|
||||
@@ -796,12 +831,9 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
|
||||
// 저장 모달 열기
|
||||
const handleOpenSaveModal = useCallback(() => {
|
||||
if (tempRelationships.length === 0) {
|
||||
toast.error("저장할 관계가 없습니다. 먼저 테이블을 연결해주세요.");
|
||||
return;
|
||||
}
|
||||
// 관계가 0개여도 저장 가능하도록 수정
|
||||
setShowSaveModal(true);
|
||||
}, [tempRelationships.length]);
|
||||
}, []);
|
||||
|
||||
// 저장 모달 닫기
|
||||
const handleCloseSaveModal = useCallback(() => {
|
||||
@@ -810,6 +842,158 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
}
|
||||
}, [isSaving]);
|
||||
|
||||
// 고립된 노드 제거 함수
|
||||
const removeOrphanedNodes = useCallback(
|
||||
(updatedRelationships: JsonRelationship[], showMessage = true) => {
|
||||
setNodes((currentNodes) => {
|
||||
// 현재 관계에서 사용되는 테이블들 추출
|
||||
const usedTables = new Set<string>();
|
||||
updatedRelationships.forEach((rel) => {
|
||||
usedTables.add(rel.fromTable);
|
||||
usedTables.add(rel.toTable);
|
||||
});
|
||||
|
||||
// 사용되지 않는 노드들 찾기
|
||||
const orphanedNodes = currentNodes.filter((node) => {
|
||||
const tableName = node.data.table.tableName;
|
||||
return !usedTables.has(tableName);
|
||||
});
|
||||
|
||||
// 연결된 노드들만 유지
|
||||
const connectedNodes = currentNodes.filter((node) => {
|
||||
const tableName = node.data.table.tableName;
|
||||
return usedTables.has(tableName);
|
||||
});
|
||||
|
||||
if (orphanedNodes.length > 0 && showMessage) {
|
||||
const orphanedTableNames = orphanedNodes.map((node) => node.data.table.displayName).join(", ");
|
||||
toast(`${orphanedNodes.length}개의 연결되지 않은 테이블 노드가 제거되었습니다: ${orphanedTableNames}`, {
|
||||
duration: 4000,
|
||||
});
|
||||
}
|
||||
|
||||
return connectedNodes;
|
||||
});
|
||||
},
|
||||
[setNodes],
|
||||
);
|
||||
|
||||
// 엣지 삭제 핸들러
|
||||
const handleDeleteEdge = useCallback(() => {
|
||||
if (!selectedEdgeForEdit) return;
|
||||
|
||||
const edgeData = selectedEdgeForEdit.data as {
|
||||
relationshipId: string;
|
||||
fromTable: string;
|
||||
toTable: string;
|
||||
};
|
||||
|
||||
// tempRelationships에서 해당 관계 제거
|
||||
const updatedRelationships = tempRelationships.filter((rel) => rel.id !== edgeData.relationshipId);
|
||||
setTempRelationships(updatedRelationships);
|
||||
|
||||
// 엣지 제거
|
||||
setEdges((prev) => prev.filter((edge) => edge.id !== selectedEdgeForEdit.id));
|
||||
|
||||
// 고립된 노드 제거
|
||||
removeOrphanedNodes(updatedRelationships);
|
||||
|
||||
// 상태 초기화
|
||||
setShowEdgeActions(false);
|
||||
setSelectedEdgeForEdit(null);
|
||||
setSelectedEdgeInfo(null);
|
||||
setSelectedColumns({});
|
||||
setHasUnsavedChanges(true);
|
||||
|
||||
toast.success("관계가 삭제되었습니다.");
|
||||
}, [selectedEdgeForEdit, tempRelationships, setEdges, removeOrphanedNodes]);
|
||||
|
||||
// 엣지 수정 핸들러 (수정 모드 전환)
|
||||
const handleEditEdge = useCallback(() => {
|
||||
if (!selectedEdgeForEdit) return;
|
||||
|
||||
const edgeData = selectedEdgeForEdit.data as {
|
||||
relationshipId: string;
|
||||
relationshipName: string;
|
||||
fromTable: string;
|
||||
toTable: string;
|
||||
fromColumns: string[];
|
||||
toColumns: string[];
|
||||
relationshipType: string;
|
||||
connectionType: string;
|
||||
};
|
||||
|
||||
// 기존 관계 찾기
|
||||
const existingRelationship = tempRelationships.find((rel) => rel.id === edgeData.relationshipId);
|
||||
if (!existingRelationship) {
|
||||
toast.error("수정할 관계를 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 수정 모드로 전환 (관계는 제거하지 않음)
|
||||
setEditingRelationshipId(edgeData.relationshipId);
|
||||
|
||||
// 기존 관계를 기반으로 연결 정보 구성
|
||||
const fromNode = nodes.find((node) => node.data.table.tableName === edgeData.fromTable);
|
||||
const toNode = nodes.find((node) => node.data.table.tableName === edgeData.toTable);
|
||||
|
||||
if (!fromNode || !toNode) {
|
||||
toast.error("연결된 테이블을 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const connectionInfo = {
|
||||
fromNode: {
|
||||
id: fromNode.id,
|
||||
tableName: fromNode.data.table.tableName,
|
||||
displayName: fromNode.data.table.displayName,
|
||||
},
|
||||
toNode: {
|
||||
id: toNode.id,
|
||||
tableName: toNode.data.table.tableName,
|
||||
displayName: toNode.data.table.displayName,
|
||||
},
|
||||
selectedColumnsData: {
|
||||
[edgeData.fromTable]: {
|
||||
displayName: fromNode.data.table.displayName,
|
||||
columns: edgeData.fromColumns,
|
||||
},
|
||||
[edgeData.toTable]: {
|
||||
displayName: toNode.data.table.displayName,
|
||||
columns: edgeData.toColumns,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// ConnectionSetupModal을 위한 연결 정보 설정
|
||||
setPendingConnection(connectionInfo);
|
||||
|
||||
// 상태 초기화
|
||||
setShowEdgeActions(false);
|
||||
setSelectedEdgeForEdit(null);
|
||||
setSelectedEdgeInfo(null);
|
||||
|
||||
toast("관계 수정 모드입니다. 원하는 대로 설정을 변경하고 확인을 눌러주세요.", {
|
||||
duration: 3000,
|
||||
});
|
||||
}, [selectedEdgeForEdit, tempRelationships, nodes]);
|
||||
|
||||
// 전체 삭제 핸들러
|
||||
const clearNodes = useCallback(() => {
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
setTempRelationships([]);
|
||||
setSelectedColumns({});
|
||||
setSelectedNodes([]);
|
||||
setPendingConnection(null);
|
||||
setSelectedEdgeInfo(null);
|
||||
setShowEdgeActions(false);
|
||||
setSelectedEdgeForEdit(null);
|
||||
setHasUnsavedChanges(true);
|
||||
|
||||
toast.success("모든 테이블과 관계가 삭제되었습니다.");
|
||||
}, [setNodes, setEdges]);
|
||||
|
||||
return (
|
||||
<div className="data-flow-designer h-screen bg-gray-100">
|
||||
<div className="flex h-full">
|
||||
@@ -827,19 +1011,26 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
|
||||
{/* 컨트롤 버튼들 */}
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
onClick={() => removeOrphanedNodes(tempRelationships)}
|
||||
className="w-full rounded-lg bg-orange-500 p-3 font-medium text-white transition-colors hover:bg-orange-600"
|
||||
disabled={nodes.length === 0}
|
||||
>
|
||||
🧹 고립된 노드 정리
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={clearNodes}
|
||||
className="w-full rounded-lg bg-red-500 p-3 font-medium text-white transition-colors hover:bg-red-600"
|
||||
>
|
||||
전체 삭제
|
||||
🗑️ 전체 삭제
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleOpenSaveModal}
|
||||
disabled={tempRelationships.length === 0}
|
||||
className={`w-full rounded-lg p-3 font-medium text-white transition-colors ${
|
||||
tempRelationships.length > 0 ? "bg-green-500 hover:bg-green-600" : "cursor-not-allowed bg-gray-400"
|
||||
} ${hasUnsavedChanges ? "animate-pulse" : ""}`}
|
||||
className={`w-full rounded-lg bg-green-500 p-3 font-medium text-white transition-colors hover:bg-green-600 ${
|
||||
hasUnsavedChanges ? "animate-pulse" : ""
|
||||
}`}
|
||||
>
|
||||
💾 관계도 저장 {tempRelationships.length > 0 && `(${tempRelationships.length})`}
|
||||
</button>
|
||||
@@ -1033,6 +1224,30 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
onCancel={handleCancelConnection}
|
||||
/>
|
||||
|
||||
{/* 엣지 액션 버튼 */}
|
||||
{showEdgeActions && selectedEdgeForEdit && (
|
||||
<div
|
||||
className="fixed z-50 flex gap-2 rounded-lg border bg-white p-2 shadow-lg"
|
||||
style={{
|
||||
left: edgeActionPosition.x - 80,
|
||||
top: edgeActionPosition.y - 60,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={handleEditEdge}
|
||||
className="flex items-center gap-1 rounded bg-blue-500 px-3 py-1 text-xs text-white hover:bg-blue-600"
|
||||
>
|
||||
✏️ 수정
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDeleteEdge}
|
||||
className="flex items-center gap-1 rounded bg-red-500 px-3 py-1 text-xs text-white hover:bg-red-600"
|
||||
>
|
||||
🗑️ 삭제
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 관계도 저장 모달 */}
|
||||
<SaveDiagramModal
|
||||
isOpen={showSaveModal}
|
||||
|
||||
Reference in New Issue
Block a user