diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 7abda41a..628fa592 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -67,12 +67,14 @@ export class TableManagementService { SELECT c.column_name as "columnName", COALESCE(cl.column_label, c.column_name) as "displayName", + c.data_type as "dataType", c.data_type as "dbType", COALESCE(cl.web_type, 'text') as "webType", COALESCE(cl.input_type, 'direct') as "inputType", COALESCE(cl.detail_settings, '') as "detailSettings", COALESCE(cl.description, '') as "description", c.is_nullable as "isNullable", + CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false END as "isPrimaryKey", c.column_default as "defaultValue", c.character_maximum_length as "maxLength", c.numeric_precision as "numericPrecision", @@ -85,6 +87,15 @@ export class TableManagementService { cl.is_visible as "isVisible" FROM information_schema.columns c LEFT JOIN column_labels cl ON c.table_name = cl.table_name AND c.column_name = cl.column_name + LEFT JOIN ( + SELECT kcu.column_name, kcu.table_name + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + WHERE tc.constraint_type = 'PRIMARY KEY' + AND tc.table_name = ${tableName} + ) pk ON c.column_name = pk.column_name AND c.table_name = pk.table_name WHERE c.table_name = ${tableName} ORDER BY c.ordinal_position `; diff --git a/backend-node/src/types/tableManagement.ts b/backend-node/src/types/tableManagement.ts index a8a65332..3469077f 100644 --- a/backend-node/src/types/tableManagement.ts +++ b/backend-node/src/types/tableManagement.ts @@ -10,12 +10,14 @@ export interface TableInfo { export interface ColumnTypeInfo { columnName: string; displayName: string; + dataType: string; // 추가: 데이터 타입 (dbType과 동일하지만 별도 필드) dbType: string; webType: string; inputType?: "direct" | "auto"; detailSettings: string; description: string; isNullable: string; + isPrimaryKey: boolean; // 추가: 기본키 여부 defaultValue?: string; maxLength?: number; numericPrecision?: number; diff --git a/docs/화면간_데이터_관계_설정_시스템_설계.md b/docs/화면간_데이터_관계_설정_시스템_설계.md index 3b7180da..f28845d4 100644 --- a/docs/화면간_데이터_관계_설정_시스템_설계.md +++ b/docs/화면간_데이터_관계_설정_시스템_설계.md @@ -1,4 +1,4 @@ -# 데이터 흐름 관리 시스템 설계 +# 테이블 간 데이터 관계 설정 시스템 설계 ## 📋 목차 @@ -12,15 +12,15 @@ ## 🎯 시스템 개요 -### 데이터 흐름 관리 시스템이란? +### 테이블 간 데이터 관계 설정 시스템이란? -데이터 흐름 관리 시스템은 회사별로 화면들 간의 데이터 흐름과 관계를 시각적으로 설계하고 관리할 수 있는 시스템입니다. React Flow 라이브러리를 활용하여 직관적인 노드 기반 인터페이스로 1:1, 1:N, N:1, N:N 관계를 지원하며, 다양한 연결 방식과 종류로 복합적인 데이터 흐름을 설계할 수 있습니다. +테이블 간 데이터 관계 설정 시스템은 회사별로 데이터베이스 테이블들 간의 데이터 관계를 시각적으로 설계하고 관리할 수 있는 시스템입니다. React Flow 라이브러리를 활용하여 직관적인 노드 기반 인터페이스로 1:1, 1:N, N:1, N:N 관계를 지원하며, 다양한 연결 방식과 종류로 복합적인 데이터 관계를 설계할 수 있습니다. ### 주요 특징 - **React Flow 기반 인터페이스**: 직관적인 노드와 엣지 기반 시각적 설계 -- **회사별 관계 관리**: 사용자 회사 코드에 따른 화면 관계 접근 제어 -- **시각적 관계 설계**: 드래그앤드롭으로 화면 노드 배치 및 필드 간 연결 +- **회사별 관계 관리**: 사용자 회사 코드에 따른 테이블 관계 접근 제어 +- **시각적 관계 설계**: 드래그앤드롭으로 테이블 노드 배치 및 컬럼 간 연결 - **다양한 관계 타입**: 1:1, 1:N, N:1, N:N 관계 지원 - **연결 종류별 세부 설정**: 단순 키값, 데이터 저장, 외부 호출 - **실시간 시뮬레이션**: 설계한 관계의 데이터 흐름 시뮬레이션 @@ -29,31 +29,31 @@ ### 지원하는 관계 타입 -- **1:1 (One to One)**: 한 화면의 필드와 다른 화면의 필드가 1:1로 연결 -- **1:N (One to Many)**: 한 화면의 필드가 여러 화면의 필드와 연결 -- **N:1 (Many to One)**: 여러 화면의 필드가 한 화면의 필드와 연결 -- **N:N (Many to Many)**: 여러 화면의 필드가 여러 화면의 필드와 연결 +- **1:1 (One to One)**: 한 테이블의 컬럼과 다른 테이블의 컬럼이 1:1로 연결 +- **1:N (One to Many)**: 한 테이블의 컬럼이 여러 테이블의 컬럼과 연결 +- **N:1 (Many to One)**: 여러 테이블의 컬럼이 한 테이블의 컬럼과 연결 +- **N:N (Many to Many)**: 여러 테이블의 컬럼이 여러 테이블의 컬럼과 연결 ### 지원하는 연결 종류 - **단순 키값 연결**: 중계 테이블을 통한 참조 관계 -- **데이터 저장**: 필드 매핑을 통한 데이터 저장 +- **데이터 저장**: 컬럼 매핑을 통한 데이터 저장 - **외부 호출**: API, 이메일, 웹훅 등을 통한 외부 시스템 연동 ## 🚀 핵심 기능 -### 1. React Flow 기반 화면 노드 관리 +### 1. React Flow 기반 테이블 노드 관리 -- **화면 추가**: 회사별 화면 목록에서 관계를 설정할 화면들을 React Flow 캔버스에 추가 -- **화면 배치**: 드래그앤드롭으로 화면 노드를 원하는 위치에 배치 -- **화면 이동**: React Flow의 내장 기능으로 화면 노드 자유롭게 이동 +- **테이블 추가**: 데이터베이스 테이블 목록에서 관계를 설정할 테이블들을 React Flow 캔버스에 추가 +- **테이블 배치**: 드래그앤드롭으로 테이블 노드를 원하는 위치에 배치 +- **테이블 이동**: React Flow의 내장 기능으로 테이블 노드 자유롭게 이동 - **노드 선택**: 단일 또는 다중 노드 선택 지원 - **자동 정렬**: React Flow의 레이아웃 알고리즘을 활용한 자동 정렬 -### 2. React Flow 기반 필드 간 연결 설정 +### 2. React Flow 기반 컬럼 간 연결 설정 -- **필드 선택**: 첫 번째 화면의 필드를 클릭하여 연결 시작 -- **대상 필드 선택**: 두 번째 화면의 필드를 클릭하여 연결 대상 지정 +- **컬럼 선택**: 첫 번째 테이블의 컬럼을 클릭하여 연결 시작 +- **대상 컬럼 선택**: 두 번째 테이블의 컬럼을 클릭하여 연결 대상 지정 - **드래그 연결**: React Flow의 핸들(Handle)을 드래그하여 시각적 연결 - **관계 타입 선택**: 1:1, 1:N, N:1, N:N 중 선택 - **연결 종류 선택**: 단순 키값, 데이터 저장, 외부 호출 중 선택 @@ -69,9 +69,9 @@ #### 데이터 저장 -- **필드 매핑**: 소스 필드와 대상 필드 매핑 설정 +- **컬럼 매핑**: 소스 컬럼과 대상 컬럼 매핑 설정 - **저장 조건**: 데이터 저장 조건 정의 -- **데이터 변환**: 필드 값 변환 규칙 +- **데이터 변환**: 컬럼 값 변환 규칙 #### 외부 호출 @@ -83,7 +83,7 @@ ### 4. React Flow 기반 시각적 관계 관리 -- **엣지 렌더링**: React Flow의 커스텀 엣지로 화면 간 관계를 시각적으로 표현 +- **엣지 렌더링**: React Flow의 커스텀 엣지로 테이블 간 관계를 시각적으로 표현 - **관계 타입별 스타일링**: 연결 종류에 따른 색상, 선 스타일, 라벨 구분 - **인터랙티브 캔버스**: 줌, 팬, 미니맵을 통한 대규모 다이어그램 탐색 - **실시간 시뮬레이션**: 데이터 흐름 애니메이션 및 시뮬레이션 @@ -99,17 +99,17 @@ ## 🗄️ 데이터베이스 설계 -### 1. 화면 관계 테이블 +### 1. 테이블 관계 테이블 ```sql --- 화면 간 관계 정의 -CREATE TABLE screen_relationships ( +-- 테이블 간 관계 정의 +CREATE TABLE table_relationships ( relationship_id SERIAL PRIMARY KEY, relationship_name VARCHAR(200) NOT NULL, - from_screen_id INTEGER NOT NULL, - from_field_name VARCHAR(100) NOT NULL, - to_screen_id INTEGER NOT NULL, - to_field_name VARCHAR(100) NOT NULL, + from_table_name VARCHAR(100) NOT NULL, + from_column_name VARCHAR(100) NOT NULL, + to_table_name VARCHAR(100) NOT NULL, + to_column_name VARCHAR(100) NOT NULL, relationship_type VARCHAR(20) NOT NULL, -- 'one-to-one', 'one-to-many', 'many-to-one', 'many-to-many' connection_type VARCHAR(20) NOT NULL, -- 'simple-key', 'data-save', 'external-call' company_code VARCHAR(50) NOT NULL, @@ -118,17 +118,14 @@ CREATE TABLE screen_relationships ( created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_by VARCHAR(50), updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_by VARCHAR(50), - - -- 외래키 제약조건 - CONSTRAINT fk_screen_relationships_from_screen - FOREIGN KEY (from_screen_id) REFERENCES screen_definitions(screen_id), - CONSTRAINT fk_screen_relationships_to_screen - FOREIGN KEY (to_screen_id) REFERENCES screen_definitions(screen_id) + updated_by VARCHAR(50) ); -- 회사 코드 인덱스 -CREATE INDEX idx_screen_relationships_company_code ON screen_relationships(company_code); +CREATE INDEX idx_table_relationships_company_code ON table_relationships(company_code); +-- 테이블명 인덱스 +CREATE INDEX idx_table_relationships_from_table ON table_relationships(from_table_name); +CREATE INDEX idx_table_relationships_to_table ON table_relationships(to_table_name); ``` ### 2. 중계 테이블 관리 @@ -148,7 +145,7 @@ CREATE TABLE bridge_tables ( -- 외래키 제약조건 CONSTRAINT fk_bridge_tables_relationship - FOREIGN KEY (relationship_id) REFERENCES screen_relationships(relationship_id) + FOREIGN KEY (relationship_id) REFERENCES table_relationships(relationship_id) ); -- 회사 코드 인덱스 @@ -170,16 +167,14 @@ CREATE TABLE external_call_configs ( -- 외래키 제약조건 CONSTRAINT fk_external_call_configs_relationship - FOREIGN KEY (relationship_id) REFERENCES screen_relationships(relationship_id) + FOREIGN KEY (relationship_id) REFERENCES table_relationships(relationship_id) ); ``` ### 4. 테이블 간 연계 관계 ``` -screen_definitions (화면 정의) - ↓ (1:N) -screen_relationships (화면 관계) +table_relationships (테이블 관계) ↓ (1:N) bridge_tables (중계 테이블) ↓ (1:N) @@ -209,7 +204,7 @@ import "reactflow/dist/style.css"; interface DataFlowDesignerProps { companyCode: string; - onSave?: (relationships: ScreenRelationship[]) => void; + onSave?: (relationships: TableRelationship[]) => void; } export const DataFlowDesigner: React.FC = ({ @@ -236,12 +231,12 @@ export const DataFlowDesigner: React.FC = ({
{/* 사이드바 */}
- @@ -279,47 +274,49 @@ export const DataFlowDesigner: React.FC = ({ }; ``` -### 2. React Flow 화면 노드 컴포넌트 +### 2. React Flow 테이블 노드 컴포넌트 ```typescript -// ScreenNode.tsx +// TableNode.tsx import { Handle, Position } from "reactflow"; -interface ScreenNodeData { - screen: ScreenDefinition; - onFieldClick: (screenId: string, fieldName: string) => void; +interface TableNodeData { + table: TableDefinition; + onColumnClick: (tableName: string, columnName: string) => void; } -export const ScreenNode: React.FC<{ data: ScreenNodeData }> = ({ data }) => { - const { screen, onFieldClick } = data; +export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => { + const { table, onColumnClick } = data; return (
{/* 노드 헤더 */}
-
{screen.screenName}
-
{screen.screenCode}
-
테이블: {screen.tableName}
+
{table.tableName}
+
테이블
+
컬럼: {table.columns.length}개
- {/* 필드 목록 */} + {/* 컬럼 목록 */}
- 필드 목록 ({screen.fields.length}개) + 컬럼 목록 ({table.columns.length}개)
- {screen.fields.map((field) => ( + {table.columns.map((column) => (
onFieldClick(screen.screenId, field.name)} + onClick={() => onColumnClick(table.tableName, column.name)} >
-
{field.name}
-
{field.description}
+
{column.name}
+
+ {column.description} +
- {field.type} + {column.type}
))} @@ -343,7 +340,7 @@ export const ScreenNode: React.FC<{ data: ScreenNodeData }> = ({ data }) => { // 노드 타입 정의 export const nodeTypes = { - screenNode: ScreenNode, + tableNode: TableNode, }; ``` @@ -521,33 +518,33 @@ export const edgeTypes = { ## 🌐 API 설계 -### 1. 화면 관계 관리 API +### 1. 테이블 관계 관리 API ```typescript -// 화면 관계 생성 -POST /api/screen-relationships +// 테이블 관계 생성 +POST /api/table-relationships Body: { relationshipName: string; - fromScreenId: number; - fromFieldName: string; - toScreenId: number; - toFieldName: string; + fromTableName: string; + fromColumnName: string; + toTableName: string; + toColumnName: string; relationshipType: 'one-to-one' | 'one-to-many' | 'many-to-one' | 'many-to-many'; connectionType: 'simple-key' | 'data-save' | 'external-call'; settings: ConnectionSettings; } -// 화면 관계 목록 조회 (회사별) -GET /api/screen-relationships?companyCode=COMP001 +// 테이블 관계 목록 조회 (회사별) +GET /api/table-relationships?companyCode=COMP001 -// 화면 관계 수정 -PUT /api/screen-relationships/:id +// 테이블 관계 수정 +PUT /api/table-relationships/:id -// 화면 관계 삭제 -DELETE /api/screen-relationships/:id +// 테이블 관계 삭제 +DELETE /api/table-relationships/:id // 관계 시뮬레이션 -POST /api/screen-relationships/:id/simulate +POST /api/table-relationships/:id/simulate ``` ### 2. 중계 테이블 관리 API @@ -591,15 +588,15 @@ PUT /api/external-call-configs/:id ### 1. 기본 관계 설정 -1. **화면 추가**: 회사별 화면 목록에서 관계를 설정할 화면들을 캔버스에 추가 -2. **필드 선택**: 첫 번째 화면의 필드를 클릭하여 연결 시작 -3. **대상 필드 선택**: 두 번째 화면의 필드를 클릭하여 연결 대상 지정 +1. **테이블 추가**: 데이터베이스 테이블 목록에서 관계를 설정할 테이블들을 캔버스에 추가 +2. **컬럼 선택**: 첫 번째 테이블의 컬럼을 클릭하여 연결 시작 +3. **대상 컬럼 선택**: 두 번째 테이블의 컬럼을 클릭하여 연결 대상 지정 4. **관계 설정**: 관계 타입과 연결 종류를 선택하고 세부 설정 구성 5. **연결 생성**: 설정 완료 후 연결 생성 ### 2. 복합 데이터 흐름 설계 -1. **다중 화면 배치**: 관련된 여러 화면을 캔버스에 배치 +1. **다중 테이블 배치**: 관련된 여러 테이블을 캔버스에 배치 2. **다양한 연결 타입**: 단순 키값, 데이터 저장, 외부 호출을 조합 3. **중계 테이블 활용**: N:N 관계에서 중계 테이블 자동 생성 4. **시각적 검증**: 연결선과 색상으로 관계 유형 구분 @@ -617,7 +614,7 @@ PUT /api/external-call-configs/:id - [x] React Flow 라이브러리 설치 및 설정 (@xyflow/react 12.8.4) - [x] 기본 노드와 엣지 컴포넌트 구현 -- [x] 화면 노드 컴포넌트 구현 (ScreenNode.tsx) +- [x] 테이블 노드 컴포넌트 구현 (TableNode.tsx) - [x] 기본 연결선 그리기 (CustomEdge.tsx) - [x] 메인 데이터 흐름 관리 컴포넌트 구현 (DataFlowDesigner.tsx) - [x] /admin/dataflow 페이지 생성 @@ -631,8 +628,8 @@ PUT /api/external-call-configs/:id - [x] 연결 설정 모달 UI 구현 - [x] 1:1, 1:N, N:1, N:N 관계 타입 선택 UI - [x] 단순 키값, 데이터 저장, 외부 호출 연결 종류 UI -- [x] 필드-to-필드 연결 시스템 (클릭 기반) -- [x] 선택된 필드 정보 표시 및 순서 보장 +- [x] 컬럼-to-컬럼 연결 시스템 (클릭 기반) +- [x] 선택된 컬럼 정보 표시 및 순서 보장 - [ ] 연결 생성 로직 구현 (모달에서 실제 엣지 생성) - [ ] 생성된 연결의 시각적 표시 (React Flow 엣지) - [ ] 연결 데이터 백엔드 저장 API 연동 @@ -668,7 +665,7 @@ PUT /api/external-call-configs/:id ## 🎯 결론 -**데이터 흐름 관리 시스템**을 통해 ERP 시스템의 화면들 간 데이터 흐름을 시각적으로 설계하고 관리할 수 있습니다. React Flow 라이브러리를 활용한 직관적인 노드 기반 인터페이스와 회사별 권한 관리, 기존 화면관리 시스템과의 완벽한 연동을 통해 체계적인 데이터 관계 관리가 가능합니다. +**테이블 간 데이터 관계 설정 시스템**을 통해 ERP 시스템의 테이블들 간 데이터 관계를 시각적으로 설계하고 관리할 수 있습니다. React Flow 라이브러리를 활용한 직관적인 노드 기반 인터페이스와 회사별 권한 관리, 기존 테이블관리 시스템과의 완벽한 연동을 통해 체계적인 데이터 관계 관리가 가능합니다. ## 📊 구현 현황 @@ -677,12 +674,12 @@ PUT /api/external-call-configs/:id **구현된 기능:** - React Flow 12.8.4 기반 시각적 캔버스 -- 화면 노드 컴포넌트 (필드 정보, 타입별 색상 구분, 노드 리사이징) +- 테이블 노드 컴포넌트 (컬럼 정보, 타입별 색상 구분, 노드 리사이징) - 커스텀 엣지 컴포넌트 (관계 타입별 스타일링) - 드래그앤드롭 노드 배치 및 연결 - 줌, 팬, 미니맵 등 고급 시각화 기능 (스크롤 충돌 해결) -- 실제 화면 데이터 연동 (테이블 관리 API 연결) -- 필드-to-필드 연결 시스템 (클릭 기반, 2개 화면 제한) +- 실제 테이블 데이터 연동 (테이블 관리 API 연결) +- 컬럼-to-컬럼 연결 시스템 (클릭 기반, 2개 테이블 제한) - 연결 설정 모달 (관계 타입, 연결 종류 선택 UI) - /admin/dataflow 경로 설정 - 메뉴 시스템 연동 완료 @@ -691,7 +688,7 @@ PUT /api/external-call-configs/:id **구현된 파일:** - `frontend/components/dataflow/DataFlowDesigner.tsx` - 메인 캔버스 컴포넌트 -- `frontend/components/dataflow/ScreenNode.tsx` - 화면 노드 컴포넌트 (NodeResizer 포함) +- `frontend/components/dataflow/TableNode.tsx` - 테이블 노드 컴포넌트 (NodeResizer 포함) - `frontend/components/dataflow/CustomEdge.tsx` - 커스텀 엣지 컴포넌트 - `frontend/components/dataflow/ConnectionSetupModal.tsx` - 연결 설정 모달 - `frontend/app/(main)/admin/dataflow/page.tsx` - 데이터 흐름 관리 페이지 @@ -702,10 +699,10 @@ PUT /api/external-call-configs/:id 1. **스크롤 충돌 해결**: 노드 내부 스크롤과 React Flow 줌/팬 기능 분리 2. **노드 리사이징**: NodeResizer를 통한 노드 크기 조정 및 내용 반영 -3. **필드-to-필드 연결**: 드래그앤드롭 대신 클릭 기반 필드 선택 방식 -4. **2개 화면 제한**: 최대 2개 화면에서만 필드 선택 가능 -5. **선택 순서 보장**: 사이드바와 모달에서 필드 선택 순서 정확히 반영 -6. **실제 데이터 연동**: 테이블 관리 시스템의 실제 화면/필드 데이터 사용 +3. **컬럼-to-컬럼 연결**: 드래그앤드롭 대신 클릭 기반 컬럼 선택 방식 +4. **2개 테이블 제한**: 최대 2개 테이블에서만 컬럼 선택 가능 +5. **선택 순서 보장**: 사이드바와 모달에서 컬럼 선택 순서 정확히 반영 +6. **실제 데이터 연동**: 테이블 관리 시스템의 실제 테이블/컬럼 데이터 사용 7. **사용자 경험**: react-hot-toast를 통한 친화적인 알림 시스템 8. **React 안정성**: 렌더링 중 상태 변경 문제 해결 @@ -713,9 +710,9 @@ PUT /api/external-call-configs/:id ### 주요 가치 -- **React Flow 기반 시각적 설계**: 복잡한 데이터 관계를 직관적인 노드와 엣지로 설계 +- **React Flow 기반 시각적 설계**: 복잡한 테이블 관계를 직관적인 노드와 엣지로 설계 - **인터랙티브 캔버스**: 줌, 팬, 미니맵 등 고급 시각화 기능 제공 -- **회사별 관리**: 각 회사별로 독립적인 관계 관리 +- **회사별 관리**: 각 회사별로 독립적인 테이블 관계 관리 - **다양한 연결 타입**: 업무 요구사항에 맞는 다양한 연결 방식 - **자동화**: 중계 테이블 자동 생성 및 외부 시스템 연동 - **확장성**: 새로운 연결 타입과 관계 유형 쉽게 추가 diff --git a/frontend/components/dataflow/ConnectionSetupModal.tsx b/frontend/components/dataflow/ConnectionSetupModal.tsx index 94c9cc7d..2d412309 100644 --- a/frontend/components/dataflow/ConnectionSetupModal.tsx +++ b/frontend/components/dataflow/ConnectionSetupModal.tsx @@ -15,23 +15,22 @@ import { ArrowRight, Database, Link } from "lucide-react"; interface ConnectionInfo { fromNode: { id: string; - screenName: string; tableName: string; + displayName: string; }; toNode: { id: string; - screenName: string; tableName: string; + displayName: string; }; - fromField?: string; - toField?: string; - selectedFieldsData?: { - [screenId: string]: { - screenName: string; - fields: string[]; + fromColumn?: string; + toColumn?: string; + selectedColumnsData?: { + [tableName: string]: { + displayName: string; + columns: string[]; }; }; - orderedScreenIds?: string[]; // 선택 순서 정보 } // 연결 설정 타입 @@ -39,8 +38,8 @@ interface ConnectionConfig { relationshipName: string; relationshipType: "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many"; connectionType: "simple-key" | "data-save" | "external-call"; - fromFieldName: string; - toFieldName: string; + fromColumnName: string; + toColumnName: string; settings?: Record; description?: string; } @@ -58,261 +57,221 @@ export const ConnectionSetupModal: React.FC = ({ onConfirm, onCancel, }) => { - const [relationshipName, setRelationshipName] = useState(""); - const [relationshipType, setRelationshipType] = useState("one-to-one"); - const [connectionType, setConnectionType] = useState("simple-key"); - const [fromFieldName, setFromFieldName] = useState(""); - const [toFieldName, setToFieldName] = useState(""); - const [description, setDescription] = useState(""); + const [config, setConfig] = useState({ + relationshipName: "", + relationshipType: "one-to-one", + connectionType: "simple-key", + fromColumnName: "", + toColumnName: "", + description: "", + }); - // 모달이 열릴 때마다 초기화 + // 모달이 열릴 때 기본값 설정 useEffect(() => { if (isOpen && connection) { - // 기본 관계명 생성 - const defaultName = `${connection.fromNode.screenName}_${connection.toNode.screenName}`; - setRelationshipName(defaultName); - setRelationshipType("one-to-one"); - setConnectionType("simple-key"); - // 시작/대상 필드는 비워둠 (다음 기능에서 사용) - setFromFieldName(""); - setToFieldName(""); - setDescription(""); + const fromTableName = connection.fromNode.displayName; + const toTableName = connection.toNode.displayName; + + setConfig({ + relationshipName: `${fromTableName} → ${toTableName}`, + relationshipType: "one-to-one", + connectionType: "simple-key", + fromColumnName: "", + toColumnName: "", + description: `${fromTableName}과 ${toTableName} 간의 데이터 관계`, + }); } }, [isOpen, connection]); const handleConfirm = () => { - if (!relationshipName.trim()) { - alert("관계명을 입력해주세요."); - return; - } - - const config: ConnectionConfig = { - relationshipName: relationshipName.trim(), - relationshipType, - connectionType, - fromFieldName, - toFieldName, - description: description.trim() || undefined, - }; - - onConfirm(config); - }; - - const getRelationshipTypeDescription = (type: string) => { - switch (type) { - case "one-to-one": - return "1:1 - 한 레코드가 다른 테이블의 한 레코드와 연결"; - case "one-to-many": - return "1:N - 한 레코드가 다른 테이블의 여러 레코드와 연결"; - case "many-to-one": - return "N:1 - 여러 레코드가 다른 테이블의 한 레코드와 연결"; - case "many-to-many": - return "N:N - 여러 레코드가 다른 테이블의 여러 레코드와 연결 (중계 테이블 생성)"; - default: - return ""; + if (config.relationshipName && config.fromColumnName && config.toColumnName) { + onConfirm(config); + handleCancel(); // 모달 닫기 } }; - const getConnectionTypeDescription = (type: string) => { - switch (type) { - case "simple-key": - return "단순 키값 연결 - 기본 참조 관계"; - case "data-save": - return "데이터 저장 - 필드 매핑을 통한 데이터 저장"; - case "external-call": - return "외부 호출 - API, 이메일, 웹훅 등을 통한 외부 시스템 연동"; - default: - return ""; - } + const handleCancel = () => { + setConfig({ + relationshipName: "", + relationshipType: "one-to-one", + connectionType: "simple-key", + fromColumnName: "", + toColumnName: "", + description: "", + }); + onCancel(); }; if (!connection) return null; + // 선택된 컬럼 데이터 가져오기 + const selectedColumnsData = connection.selectedColumnsData || {}; + const tableNames = Object.keys(selectedColumnsData); + const fromTable = tableNames[0]; + const toTable = tableNames[1]; + + const fromTableData = selectedColumnsData[fromTable]; + const toTableData = selectedColumnsData[toTable]; + return ( - + - + - 필드 연결 설정 + 테이블 간 컬럼 연결 설정
{/* 연결 정보 표시 */} - - - 연결 정보 - - - {connection.selectedFieldsData && connection.orderedScreenIds ? ( -
- {/* orderedScreenIds 순서대로 표시 */} - {connection.orderedScreenIds.map((screenId, index) => { - const screenData = connection.selectedFieldsData[screenId]; - if (!screenData) return null; - - return ( - -
-
-
- {screenData.screenName} -
-
ID: {screenId}
-
- - {index === 0 ? connection.fromNode.tableName : connection.toNode.tableName} -
-
-
- {screenData.fields.map((field) => ( - - {field} - - ))} -
-
- {/* 첫 번째 화면 다음에 화살표 표시 */} - {index === 0 && connection.orderedScreenIds.length > 1 && ( -
- -
- )} -
- ); - })} -
- ) : ( -
-
-
{connection.fromNode.screenName}
-
- - {connection.fromNode.tableName} -
-
- -
-
{connection.toNode.screenName}
-
- - {connection.toNode.tableName} -
+
+ {/* 시작 테이블 */} + + + + + 시작 테이블 + + + +
+
{fromTableData?.displayName || fromTable}
+
{fromTable}
+
+ {fromTableData?.columns.map((column, index) => ( + + {column} + + ))}
- )} -
-
+ + -
- {/* 기본 설정 */} -
-

기본 설정

- -
- - setRelationshipName(e.target.value)} - placeholder="관계를 설명하는 이름을 입력하세요" - /> -
- -
- - setFromFieldName(e.target.value)} - placeholder="시작 테이블의 필드명" - /> -
- -
- - setToFieldName(e.target.value)} - placeholder="대상 테이블의 필드명" - /> -
- -
- -