기타 수정
This commit is contained in:
parent
b02e9610ea
commit
37fac630b9
|
|
@ -623,13 +623,16 @@ PUT /api/external-call-configs/:id
|
|||
- [x] 노드 간 드래그앤드롭 연결 기능
|
||||
- [x] 줌, 팬, 미니맵 등 React Flow 기본 기능
|
||||
|
||||
### Phase 2: 관계 설정 기능 (2주) - 🚧 **진행 중 (70% 완료)**
|
||||
### Phase 2: 관계 설정 기능 (2주) - 🚧 **진행 중 (85% 완료)**
|
||||
|
||||
- [x] 연결 설정 모달 UI 구현
|
||||
- [x] 1:1, 1:N, N:1, N:N 관계 타입 선택 UI
|
||||
- [x] 단순 키값, 데이터 저장, 외부 호출 연결 종류 UI
|
||||
- [x] 컬럼-to-컬럼 연결 시스템 (클릭 기반)
|
||||
- [x] 선택된 컬럼 정보 표시 및 순서 보장
|
||||
- [x] 드래그 다중 선택 기능 (부분 터치 선택 지원)
|
||||
- [x] 테이블 기반 시스템으로 전환 (화면 → 테이블)
|
||||
- [x] 코드 정리 및 최적화 (불필요한 props 제거)
|
||||
- [ ] 연결 생성 로직 구현 (모달에서 실제 엣지 생성)
|
||||
- [ ] 생성된 연결의 시각적 표시 (React Flow 엣지)
|
||||
- [ ] 연결 데이터 백엔드 저장 API 연동
|
||||
|
|
@ -698,13 +701,15 @@ PUT /api/external-call-configs/:id
|
|||
**주요 개선사항:**
|
||||
|
||||
1. **스크롤 충돌 해결**: 노드 내부 스크롤과 React Flow 줌/팬 기능 분리
|
||||
2. **노드 리사이징**: NodeResizer를 통한 노드 크기 조정 및 내용 반영
|
||||
2. **테이블 기반 시스템**: 화면 기반에서 테이블 기반으로 완전 전환
|
||||
3. **컬럼-to-컬럼 연결**: 드래그앤드롭 대신 클릭 기반 컬럼 선택 방식
|
||||
4. **2개 테이블 제한**: 최대 2개 테이블에서만 컬럼 선택 가능
|
||||
5. **선택 순서 보장**: 사이드바와 모달에서 컬럼 선택 순서 정확히 반영
|
||||
6. **실제 데이터 연동**: 테이블 관리 시스템의 실제 테이블/컬럼 데이터 사용
|
||||
7. **사용자 경험**: react-hot-toast를 통한 친화적인 알림 시스템
|
||||
8. **React 안정성**: 렌더링 중 상태 변경 문제 해결
|
||||
9. **드래그 다중 선택**: 부분 터치로도 노드 선택 가능한 고급 선택 기능
|
||||
10. **코드 최적화**: 불필요한 props 및 컴포넌트 제거로 성능 향상
|
||||
|
||||
**다음 단계:** Phase 2 - 실제 연결 생성 및 시각적 표시 기능 구현
|
||||
|
||||
|
|
|
|||
|
|
@ -4,18 +4,19 @@ import React from "react";
|
|||
import { Toaster } from "react-hot-toast";
|
||||
import { DataFlowDesigner } from "@/components/dataflow/DataFlowDesigner";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { TableRelationship } from "@/lib/api/dataflow";
|
||||
|
||||
export default function DataFlowPage() {
|
||||
const { user } = useAuth();
|
||||
|
||||
const handleSave = (relationships: any[]) => {
|
||||
const handleSave = (relationships: TableRelationship[]) => {
|
||||
console.log("저장된 관계:", relationships);
|
||||
// TODO: API 호출로 관계 저장
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-gray-50">
|
||||
<DataFlowDesigner companyCode={user?.companyCode || "COMP001"} onSave={handleSave} />
|
||||
<DataFlowDesigner companyCode={user?.company_code || "COMP001"} onSave={handleSave} />
|
||||
<Toaster />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import {
|
|||
Edge,
|
||||
Controls,
|
||||
Background,
|
||||
MiniMap,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
BackgroundVariant,
|
||||
SelectionMode,
|
||||
} from "@xyflow/react";
|
||||
import "@xyflow/react/dist/style.css";
|
||||
import { TableNode } from "./TableNode";
|
||||
|
|
@ -435,10 +435,9 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode,
|
|||
<div key={`selected-${tableName}-${index}`}>
|
||||
<div className="w-full min-w-0 rounded-lg border border-blue-300 bg-white p-3">
|
||||
<div className="mb-2 flex flex-wrap items-center gap-2">
|
||||
<div className="flex-shrink-0 rounded bg-blue-600 px-2 py-1 text-xs font-medium text-white">
|
||||
<div className="flex-shrink-0 rounded px-2 py-1 text-xs font-medium text-blue-600">
|
||||
{displayName}
|
||||
</div>
|
||||
<div className="flex-shrink-0 text-xs text-gray-500">{tableName}</div>
|
||||
</div>
|
||||
<div className="flex w-full min-w-0 flex-wrap gap-1">
|
||||
{columns.map((column, columnIndex) => (
|
||||
|
|
@ -466,9 +465,9 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode,
|
|||
<button
|
||||
onClick={openConnectionModal}
|
||||
disabled={!canCreateConnection()}
|
||||
className={`rounded px-3 py-1 text-xs font-medium transition-colors ${
|
||||
className={`w-full rounded px-3 py-1 text-xs font-medium transition-colors ${
|
||||
canCreateConnection()
|
||||
? "bg-blue-600 text-white hover:bg-blue-700"
|
||||
? "cursor-pointer bg-blue-600 text-white hover:bg-blue-700"
|
||||
: "cursor-not-allowed bg-gray-300 text-gray-500"
|
||||
}`}
|
||||
>
|
||||
|
|
@ -479,7 +478,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode,
|
|||
setSelectedColumns({});
|
||||
setSelectionOrder([]);
|
||||
}}
|
||||
className="rounded bg-gray-200 px-3 py-1 text-xs font-medium text-gray-600 hover:bg-gray-300"
|
||||
className="w-full cursor-pointer rounded bg-gray-200 px-3 py-1 text-xs font-medium text-gray-600 hover:bg-gray-300"
|
||||
>
|
||||
선택 초기화
|
||||
</button>
|
||||
|
|
@ -506,19 +505,14 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode,
|
|||
panOnScroll={false}
|
||||
zoomOnScroll={true}
|
||||
zoomOnPinch={true}
|
||||
panOnDrag={true}
|
||||
panOnDrag={[1, 2]}
|
||||
selectionOnDrag={true}
|
||||
multiSelectionKeyCode={null}
|
||||
selectNodesOnDrag={false}
|
||||
selectionMode={SelectionMode.Partial}
|
||||
>
|
||||
<Controls />
|
||||
<MiniMap
|
||||
nodeColor={(node) => {
|
||||
switch (node.type) {
|
||||
case "tableNode":
|
||||
return "#3B82F6";
|
||||
default:
|
||||
return "#6B7280";
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#E5E7EB" />
|
||||
</ReactFlow>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Handle, Position, NodeResizer } from "@xyflow/react";
|
||||
|
||||
interface TableColumn {
|
||||
name: string;
|
||||
|
|
@ -21,66 +20,22 @@ interface TableNodeData {
|
|||
onColumnClick: (tableName: string, columnName: string) => void;
|
||||
onScrollAreaEnter?: () => void;
|
||||
onScrollAreaLeave?: () => void;
|
||||
selected?: boolean;
|
||||
selectedColumns?: string[]; // 선택된 컬럼 목록
|
||||
}
|
||||
|
||||
export const TableNode: React.FC<{ data: TableNodeData; selected?: boolean }> = ({ data, selected }) => {
|
||||
export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
|
||||
const { table, onColumnClick, onScrollAreaEnter, onScrollAreaLeave, selectedColumns = [] } = data;
|
||||
|
||||
// 컬럼 개수에 따른 높이 계산
|
||||
// 헤더: ~80px (제목 + 설명 + 패딩)
|
||||
const headerHeight = table.description ? 80 : 65;
|
||||
|
||||
// 컬럼 높이: 각 컬럼은 실제로 더 높음 (px-2 py-1 + 텍스트 2줄 + 설명 + space-y-1)
|
||||
// 설명이 있는 컬럼: ~45px, 없는 컬럼: ~35px, 간격: ~4px
|
||||
const avgColumnHeight = 45; // 여유있게 계산
|
||||
const idealColumnHeight = table.columns.length * avgColumnHeight;
|
||||
|
||||
// 컨테이너 패딩
|
||||
const padding = 20;
|
||||
|
||||
// 이상적인 높이 vs 최대 허용 높이 (너무 길면 스크롤)
|
||||
const idealHeight = headerHeight + idealColumnHeight + padding;
|
||||
const maxAllowedHeight = 800; // 최대 800px
|
||||
const calculatedHeight = Math.max(200, Math.min(idealHeight, maxAllowedHeight));
|
||||
|
||||
// 스크롤이 필요한지 판단
|
||||
const needsScroll = idealHeight > maxAllowedHeight;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex min-w-[280px] flex-col overflow-hidden rounded-lg border-2 border-gray-300 bg-white shadow-lg"
|
||||
style={{ height: `${calculatedHeight}px`, minHeight: `${calculatedHeight}px` }}
|
||||
>
|
||||
{/* NodeResizer for resizing functionality */}
|
||||
<NodeResizer
|
||||
color="#ff0071"
|
||||
isVisible={selected}
|
||||
minWidth={280}
|
||||
minHeight={calculatedHeight}
|
||||
keepAspectRatio={false}
|
||||
handleStyle={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
backgroundColor: "#ff0071",
|
||||
border: "1px solid white",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative flex min-w-[280px] flex-col overflow-hidden rounded-lg border-2 border-gray-300 bg-white shadow-lg">
|
||||
{/* 테이블 헤더 */}
|
||||
<div className="rounded-t-lg bg-blue-600 p-3 text-white">
|
||||
<div className="bg-blue-600 p-3 text-white">
|
||||
<h3 className="truncate text-sm font-semibold">{table.displayName}</h3>
|
||||
<p className="truncate text-xs opacity-90">{table.tableName}</p>
|
||||
{table.description && <p className="mt-1 truncate text-xs opacity-75">{table.description}</p>}
|
||||
</div>
|
||||
|
||||
{/* 컬럼 목록 */}
|
||||
<div
|
||||
className={`flex-1 p-2 ${needsScroll ? "overflow-y-auto" : "overflow-hidden"}`}
|
||||
onMouseEnter={onScrollAreaEnter}
|
||||
onMouseLeave={onScrollAreaLeave}
|
||||
>
|
||||
<div className="flex-1 overflow-hidden p-2" onMouseEnter={onScrollAreaEnter} onMouseLeave={onScrollAreaLeave}>
|
||||
<div className="space-y-1">
|
||||
{table.columns.map((column) => {
|
||||
const isSelected = selectedColumns.includes(column.name);
|
||||
|
|
@ -103,20 +58,6 @@ export const TableNode: React.FC<{ data: TableNodeData; selected?: boolean }> =
|
|||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* React Flow Handles */}
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
className="h-3 w-3 border-2 border-gray-400 bg-white"
|
||||
isConnectable={false}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
className="h-3 w-3 border-2 border-gray-400 bg-white"
|
||||
isConnectable={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue