엣지 클릭 시 연결 목록 조회로 변경

This commit is contained in:
hyeonsu 2025-09-15 16:15:00 +09:00
parent e459025d8a
commit 6a04ae450d
2 changed files with 145 additions and 49 deletions

View File

@ -43,6 +43,11 @@ export default function DataFlowEditPage() {
router.push("/admin/dataflow");
};
// 관계도 이름 업데이트 핸들러
const handleDiagramNameUpdate = (newDiagramName: string) => {
setDiagramName(newDiagramName);
};
if (!diagramId || !diagramName) {
return (
<div className="flex h-64 items-center justify-center">
@ -74,7 +79,12 @@ export default function DataFlowEditPage() {
{/* 데이터플로우 디자이너 */}
<div className="rounded-lg border border-gray-200 bg-white">
<DataFlowDesigner selectedDiagram={diagramName} diagramId={diagramId} onBackToList={handleBackToList} />
<DataFlowDesigner
selectedDiagram={diagramName}
diagramId={diagramId}
onBackToList={handleBackToList}
onDiagramNameUpdate={handleDiagramNameUpdate}
/>
</div>
</div>
);

View File

@ -66,6 +66,7 @@ interface DataFlowDesignerProps {
diagramId?: number;
relationshipId?: string; // 하위 호환성 유지
onBackToList?: () => void;
onDiagramNameUpdate?: (diagramName: string) => void; // 관계도 이름 업데이트 콜백 추가
}
// TableRelationship 타입은 dataflow.ts에서 import
@ -82,6 +83,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
onSave, // eslint-disable-line @typescript-eslint/no-unused-vars
selectedDiagram,
onBackToList, // eslint-disable-line @typescript-eslint/no-unused-vars
onDiagramNameUpdate, // 관계도 이름 업데이트 콜백
}) => {
const { user } = useAuth();
@ -136,6 +138,8 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
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 [showRelationshipListModal, setShowRelationshipListModal] = useState(false); // 관계 목록 모달 표시 상태
const [selectedTablePairRelationships, setSelectedTablePairRelationships] = useState<ExtendedJsonRelationship[]>([]); // 선택된 테이블 쌍의 관계들
const toastShownRef = useRef(false); // eslint-disable-line @typescript-eslint/no-unused-vars
// 편집 모드일 때 관계도 이름 로드
@ -333,8 +337,9 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
console.log("📍 테이블 노드 상세:", tableNodes);
setNodes(tableNodes);
// JSON 관계를 엣지로 변환하여 표시 (테이블 간 번들 연결)
// JSON 관계를 엣지로 변환하여 표시 (각 관계마다 개별 엣지 생성)
const relationshipEdges: Edge[] = [];
const tableRelationshipCount: { [key: string]: number } = {}; // 테이블 쌍별 관계 개수
normalizedRelationships.forEach((rel: ExtendedJsonRelationship) => {
const fromTable = rel.fromTable;
@ -347,9 +352,16 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
return;
}
// 테이블 간 하나의 번들 엣지 생성 (컬럼별 개별 엣지 대신)
// 테이블 쌍 키 생성 (양방향 동일하게 처리)
const tableKey = [fromTable, toTable].sort().join("-");
tableRelationshipCount[tableKey] = (tableRelationshipCount[tableKey] || 0) + 1;
const relationshipIndex = tableRelationshipCount[tableKey];
// 각 관계마다 고유한 엣지 생성 (곡선 오프셋으로 구분)
const curveOffset = (relationshipIndex - 1) * 30; // 30px씩 오프셋
relationshipEdges.push({
id: generateUniqueId("edge", currentDiagramId),
id: `edge-${rel.id}`, // 관계 ID를 기반으로 고유 ID 생성
source: `table-${fromTable}`,
target: `table-${toTable}`,
type: "smoothstep",
@ -359,6 +371,17 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
strokeWidth: 2,
strokeDasharray: "none",
},
// 여러 관계가 있을 때 곡선 오프셋 적용
...(relationshipIndex > 1 && {
style: {
stroke: "#3b82f6",
strokeWidth: 2,
strokeDasharray: "none",
},
pathOptions: {
offset: curveOffset,
},
}),
data: {
relationshipId: rel.id,
relationshipName: rel.relationshipName,
@ -367,6 +390,9 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
toTable: toTable,
fromColumns: fromColumns,
toColumns: toColumns,
// 테이블 쌍의 모든 관계 정보 (엣지 클릭 시 사용)
tableKey: tableKey,
relationshipIndex: relationshipIndex,
// 클릭 시 표시할 상세 정보
details: {
connectionInfo: `${fromTable}(${fromColumns.join(", ")}) → ${toTable}(${toColumns.join(", ")})`,
@ -486,56 +512,45 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
}, []);
// 엣지 클릭 시 연결 정보 표시 및 관련 컬럼 하이라이트
const onEdgeClick = useCallback((event: React.MouseEvent, edge: Edge) => {
event.stopPropagation();
const edgeData = edge.data as {
relationshipId: string;
relationshipName: string;
fromTable: string;
toTable: string;
fromColumns: string[];
toColumns: string[];
connectionType: string;
details?: {
connectionInfo: string;
const onEdgeClick = useCallback(
(event: React.MouseEvent, edge: Edge) => {
event.stopPropagation();
const edgeData = edge.data as {
relationshipId: string;
relationshipName: string;
fromTable: string;
toTable: string;
fromColumns: string[];
toColumns: string[];
connectionType: string;
tableKey: string;
relationshipIndex: number;
details?: {
connectionInfo: string;
connectionType: string;
};
};
};
if (edgeData) {
// 엣지 정보 설정
setSelectedEdgeInfo({
relationshipId: edgeData.relationshipId,
relationshipName: edgeData.relationshipName || "관계",
fromTable: edgeData.fromTable,
toTable: edgeData.toTable,
fromColumns: edgeData.fromColumns || [],
toColumns: edgeData.toColumns || [],
connectionType: edgeData.connectionType,
connectionInfo: edgeData.details?.connectionInfo || `${edgeData.fromTable}${edgeData.toTable}`,
});
if (edgeData) {
// 해당 테이블 쌍의 모든 관계 찾기
const fromTable = edgeData.fromTable;
const toTable = edgeData.toTable;
// 관련 컬럼 하이라이트
const newSelectedColumns: { [tableName: string]: string[] } = {};
const tablePairRelationships = tempRelationships.filter(
(rel) =>
(rel.fromTable === fromTable && rel.toTable === toTable) ||
(rel.fromTable === toTable && rel.toTable === fromTable),
);
// fromTable의 컬럼들 선택
if (edgeData.fromTable && edgeData.fromColumns) {
newSelectedColumns[edgeData.fromTable] = [...edgeData.fromColumns];
console.log(`🔗 ${fromTable}${toTable} 간의 관계:`, tablePairRelationships);
// 관계가 1개든 여러 개든 항상 관계 목록 모달 표시
setSelectedTablePairRelationships(tablePairRelationships);
setShowRelationshipListModal(true);
}
// toTable의 컬럼들 선택
if (edgeData.toTable && edgeData.toColumns) {
newSelectedColumns[edgeData.toTable] = [...edgeData.toColumns];
}
setSelectedColumns(newSelectedColumns);
// 액션 버튼 표시
setSelectedEdgeForEdit(edge);
setEdgeActionPosition({ x: event.clientX, y: event.clientY });
setShowEdgeActions(true);
}
}, []);
},
[tempRelationships],
);
// 엣지 마우스 엔터 시 색상 변경
const onEdgeMouseEnter = useCallback(
@ -845,6 +860,11 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
setShowSaveModal(false);
setCurrentDiagramId(savedDiagram.diagram_id);
// 관계도 이름 업데이트 (편집 모드일 때만)
if (diagramId && diagramId > 0 && onDiagramNameUpdate) {
onDiagramNameUpdate(diagramName);
}
console.log("관계도 저장 완료:", savedDiagram);
} catch (error) {
console.error("관계도 저장 실패:", error);
@ -853,7 +873,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
setIsSaving(false);
}
},
[tempRelationships, diagramId, companyCode, user?.userId, nodes],
[tempRelationships, diagramId, companyCode, user?.userId, nodes, onDiagramNameUpdate],
);
// 저장 모달 열기
@ -1136,6 +1156,72 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
>
<Controls />
<Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#E5E7EB" />
{/* 관계 목록 모달 - 캔버스 내부 우측 상단에 배치 */}
{showRelationshipListModal && (
<div className="pointer-events-auto absolute top-4 right-4 z-40 w-80 rounded-xl border border-blue-200 bg-white shadow-lg">
{/* 헤더 */}
<div className="flex items-center justify-between rounded-t-xl border-b border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-3">
<div className="flex items-center gap-2">
<div className="rounded-full bg-blue-100 p-1">
<span className="text-sm text-blue-600">🔗</span>
</div>
<div className="text-sm font-semibold text-gray-800"> </div>
</div>
<button
onClick={() => setShowRelationshipListModal(false)}
className="flex h-6 w-6 items-center justify-center rounded-full text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600"
>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* 관계 목록 */}
<div className="p-3">
<div className="max-h-96 space-y-2 overflow-y-auto">
{selectedTablePairRelationships.map((relationship, index) => (
<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"
>
<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>
<div className="space-y-1 text-xs text-gray-600">
<p>: {relationship.connectionType}</p>
<p>From: {relationship.fromTable}</p>
<p>To: {relationship.toTable}</p>
</div>
</div>
))}
</div>
</div>
</div>
)}
</ReactFlow>
{/* 선택된 테이블 노드 팝업 - 캔버스 좌측 상단 고정 (새 관계 생성 시에만 표시) */}