2026-01-08 14:24:33 +09:00
|
|
|
# 화면 관계 시각화 기능 개선 보고서
|
|
|
|
|
|
|
|
|
|
## 개요
|
|
|
|
|
|
|
|
|
|
화면 그룹 관리에서 ReactFlow를 사용한 화면-테이블 관계 시각화 기능의 범용성 및 정확성 개선 작업을 수행했습니다.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 수정된 파일 목록
|
|
|
|
|
|
|
|
|
|
| 파일 경로 | 역할 |
|
|
|
|
|
|----------|------|
|
|
|
|
|
| `backend-node/src/controllers/screenGroupController.ts` | 화면 서브테이블 정보 API |
|
|
|
|
|
| `frontend/components/screen/ScreenRelationFlow.tsx` | ReactFlow 시각화 컴포넌트 |
|
|
|
|
|
| `frontend/lib/api/screenGroup.ts` | API 인터페이스 정의 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 사용된 데이터베이스 테이블
|
|
|
|
|
|
|
|
|
|
### 화면 정의 관련
|
|
|
|
|
|
|
|
|
|
| 테이블명 | 용도 | 주요 컬럼 |
|
|
|
|
|
|----------|------|----------|
|
|
|
|
|
| `screen_definitions` | 화면 정의 정보 | `screen_id`, `screen_name`, `table_name`, `company_code` |
|
|
|
|
|
| `screen_layouts` | 화면 레이아웃/컴포넌트 정보 | `screen_id`, `properties` (JSONB - componentConfig 포함) |
|
|
|
|
|
| `screen_groups` | 화면 그룹 정보 | `group_id`, `group_code`, `group_name`, `parent_group_id` |
|
|
|
|
|
| `screen_group_mappings` | 화면-그룹 매핑 | `group_id`, `screen_id`, `display_order` |
|
|
|
|
|
|
|
|
|
|
### 메타데이터 관련
|
|
|
|
|
|
|
|
|
|
| 테이블명 | 용도 | 주요 컬럼 |
|
|
|
|
|
|----------|------|----------|
|
|
|
|
|
| `column_labels` | 컬럼 한글명/참조 정보 | `table_name`, `column_name`, `column_label`, `reference_table`, `reference_column` |
|
|
|
|
|
| `table_info` | 테이블 메타 정보 | `table_name`, `table_label`, `column_count` |
|
|
|
|
|
|
|
|
|
|
### 조인 정보 추출 소스
|
|
|
|
|
|
|
|
|
|
#### 1. column_labels 테이블 (테이블 관리에서 설정)
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
SELECT
|
|
|
|
|
cl.table_name,
|
|
|
|
|
cl.column_name,
|
|
|
|
|
cl.reference_table, -- 참조 테이블
|
|
|
|
|
cl.reference_column, -- 참조 컬럼
|
|
|
|
|
cl.column_label -- 한글명
|
|
|
|
|
FROM column_labels cl
|
|
|
|
|
WHERE cl.reference_table IS NOT NULL
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 2. screen_layouts.properties (화면 컴포넌트에서 설정)
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
-- parentDataMapping 추출
|
|
|
|
|
SELECT
|
|
|
|
|
sd.screen_id,
|
|
|
|
|
sl.properties->'componentConfig'->'parentDataMapping' as parent_data_mapping
|
|
|
|
|
FROM screen_definitions sd
|
|
|
|
|
JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
|
|
|
|
|
WHERE sl.properties->'componentConfig'->'parentDataMapping' IS NOT NULL
|
|
|
|
|
|
|
|
|
|
-- rightPanel.relation 추출
|
|
|
|
|
SELECT
|
|
|
|
|
sd.screen_id,
|
|
|
|
|
sl.properties->'componentConfig'->'rightPanel'->'relation' as right_panel_relation
|
|
|
|
|
FROM screen_definitions sd
|
|
|
|
|
JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
|
|
|
|
|
WHERE sl.properties->'componentConfig'->'rightPanel'->'relation' IS NOT NULL
|
|
|
|
|
|
|
|
|
|
-- fieldMappings 추출
|
|
|
|
|
SELECT
|
|
|
|
|
sd.screen_id,
|
|
|
|
|
sl.properties->'componentConfig'->'fieldMappings' as field_mappings
|
|
|
|
|
FROM screen_definitions sd
|
|
|
|
|
JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
|
|
|
|
|
WHERE sl.properties->'componentConfig'->'fieldMappings' IS NOT NULL
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 테이블 관계도
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
screen_groups (그룹)
|
|
|
|
|
│
|
|
|
|
|
├─── screen_group_mappings (매핑)
|
|
|
|
|
│ │
|
|
|
|
|
│ └─── screen_definitions (화면)
|
|
|
|
|
│ │
|
|
|
|
|
│ └─── screen_layouts (레이아웃/컴포넌트)
|
|
|
|
|
│ │
|
|
|
|
|
│ └─── properties.componentConfig
|
|
|
|
|
│ ├── fieldMappings
|
|
|
|
|
│ ├── parentDataMapping
|
|
|
|
|
│ ├── columns.mapping
|
|
|
|
|
│ └── rightPanel.relation
|
|
|
|
|
│
|
|
|
|
|
└─── column_labels (컬럼 메타데이터)
|
|
|
|
|
│
|
|
|
|
|
├── reference_table (참조 테이블)
|
|
|
|
|
└── column_label (한글명)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 핵심 문제 및 해결
|
|
|
|
|
|
|
|
|
|
### 1. 조인 컬럼 식별 오류
|
|
|
|
|
|
|
|
|
|
#### 문제
|
|
|
|
|
- `customer_item_mapping` 테이블에서 `customer_id`가 조인 컬럼으로 표시되지 않음
|
|
|
|
|
- `customer_mng`가 `relationType: source`로 분류되어 `sourceField`가 잘못 사용됨
|
|
|
|
|
|
|
|
|
|
#### 원인
|
|
|
|
|
```typescript
|
|
|
|
|
// 기존 잘못된 로직
|
|
|
|
|
if (subTable.relationType === 'source') {
|
|
|
|
|
// sourceField를 메인테이블 컬럼으로 사용 (잘못됨)
|
|
|
|
|
joinColumns.push(mapping.sourceField);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 해결
|
|
|
|
|
```typescript
|
|
|
|
|
// 수정된 범용 로직
|
|
|
|
|
if (subTable.relationType === 'source' && mapping.sourceTable) {
|
|
|
|
|
// sourceTable이 있으면 parentDataMapping과 유사
|
|
|
|
|
// targetField가 메인테이블 컬럼
|
|
|
|
|
joinColumns.push(mapping.targetField);
|
|
|
|
|
} else if (subTable.relationType === 'source') {
|
|
|
|
|
// 일반 source 타입
|
|
|
|
|
joinColumns.push(mapping.sourceField);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 2. displayColumns 잘못된 처리
|
|
|
|
|
|
|
|
|
|
#### 문제
|
|
|
|
|
- `selected-items-detail-input` 컴포넌트의 `displayColumns`가 메인테이블 `joinColumns`에 추가됨
|
|
|
|
|
- `customer_name`, `customer_code` 등이 조인 컬럼으로 잘못 표시됨
|
|
|
|
|
|
|
|
|
|
#### 원인
|
|
|
|
|
```typescript
|
|
|
|
|
// 기존 잘못된 로직
|
|
|
|
|
if (componentConfig.displayColumns) {
|
|
|
|
|
componentConfig.displayColumns.forEach((col) => {
|
|
|
|
|
joinColumns.push(col.name); // 연관 테이블 컬럼을 메인테이블에 추가 (잘못됨)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 해결
|
|
|
|
|
```typescript
|
|
|
|
|
// 수정된 로직 - displayColumns는 연관 테이블 컬럼이므로 제외
|
|
|
|
|
// 조인 컬럼은 parentDataMapping.targetField에서 별도 추출됨
|
|
|
|
|
if (componentConfig.displayColumns) {
|
|
|
|
|
// 메인 테이블 joinColumns에 추가하지 않음
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 3. 조인 정보 한글명 미표시
|
|
|
|
|
|
|
|
|
|
#### 문제
|
|
|
|
|
- 조인 컬럼 옆에 `← customer_code` (영문)로 표시됨
|
|
|
|
|
- `← 거래처 코드` (한글)로 표시되어야 함
|
|
|
|
|
|
|
|
|
|
#### 해결
|
|
|
|
|
백엔드에서 `column_labels` 테이블 조회하여 한글명 적용:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 모든 테이블/컬럼 조합 수집
|
|
|
|
|
const columnLookups = [];
|
|
|
|
|
screenSubTables.forEach((screenData) => {
|
|
|
|
|
screenData.subTables.forEach((subTable) => {
|
|
|
|
|
subTable.fieldMappings?.forEach((mapping) => {
|
|
|
|
|
if (mapping.sourceTable && mapping.sourceField) {
|
|
|
|
|
columnLookups.push({ tableName: mapping.sourceTable, columnName: mapping.sourceField });
|
|
|
|
|
}
|
|
|
|
|
if (screenData.mainTable && mapping.targetField) {
|
|
|
|
|
columnLookups.push({ tableName: screenData.mainTable, columnName: mapping.targetField });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// column_labels에서 한글명 조회
|
|
|
|
|
const columnLabelsQuery = `
|
|
|
|
|
SELECT table_name, column_name, column_label
|
|
|
|
|
FROM column_labels
|
|
|
|
|
WHERE (table_name, column_name) IN (...)
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
// 각 fieldMapping에 한글명 적용
|
|
|
|
|
mapping.sourceDisplayName = columnLabelsMap[`${sourceTable}.${sourceField}`];
|
|
|
|
|
mapping.targetDisplayName = columnLabelsMap[`${mainTable}.${targetField}`];
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 4. 연결 선 라벨 제거 및 단일 로직화
|
|
|
|
|
|
|
|
|
|
#### 문제
|
|
|
|
|
- 테이블 간 연결 선에 `customer_code → customer_id` 라벨이 표시됨
|
|
|
|
|
- 조인 정보가 테이블 노드 내부에 이미 표시되므로 중복
|
|
|
|
|
- 메인-메인 테이블 조인 로직이 2개 존재 (주황색, 초록색)
|
|
|
|
|
|
|
|
|
|
#### 해결
|
|
|
|
|
1. **초록색 선 로직 완전 제거**: 중복 로직으로 인한 혼란 방지
|
|
|
|
|
2. **주황색 선 로직 개선**: 모든 메인-메인 조인을 단일 로직으로 처리
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 메인-메인 조인 엣지 생성 (단일 로직)
|
|
|
|
|
const joinEdges: Edge[] = [];
|
|
|
|
|
|
|
|
|
|
// 모든 화면의 메인 테이블 목록
|
|
|
|
|
const allMainTables = new Set(Object.values(screenTableMap));
|
|
|
|
|
|
|
|
|
|
focusedSubTablesData.subTables.forEach((subTable) => {
|
|
|
|
|
// 1. subTable.tableName이 다른 화면의 메인 테이블인 경우
|
|
|
|
|
const isTargetMainTable = allMainTables.has(subTable.tableName)
|
|
|
|
|
&& subTable.tableName !== focusedMainTable;
|
|
|
|
|
|
|
|
|
|
if (isTargetMainTable) {
|
|
|
|
|
joinEdges.push({
|
|
|
|
|
id: `edge-main-join-${focusedScreenId}-${subTable.tableName}-${focusedMainTable}`,
|
|
|
|
|
source: `table-${subTable.tableName}`,
|
|
|
|
|
target: `table-${focusedMainTable}`,
|
|
|
|
|
type: 'smoothstep',
|
|
|
|
|
animated: true,
|
|
|
|
|
style: {
|
|
|
|
|
stroke: '#ea580c', // 주황색 (단일 색상)
|
|
|
|
|
strokeWidth: 2,
|
|
|
|
|
strokeDasharray: '8,4',
|
|
|
|
|
},
|
|
|
|
|
markerEnd: { type: MarkerType.ArrowClosed, color: '#ea580c' },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. fieldMappings.sourceTable이 메인 테이블인 경우도 처리
|
|
|
|
|
// ... (parentMapping, rightPanelRelation 등)
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 5. 메인테이블 fieldMappings 미전달
|
|
|
|
|
|
|
|
|
|
#### 문제
|
|
|
|
|
- 서브테이블에만 `fieldMappings`가 전달되어 조인 정보 표시됨
|
|
|
|
|
- 메인테이블에는 조인 컬럼 옆 연결 정보가 미표시
|
|
|
|
|
|
|
|
|
|
#### 해결
|
|
|
|
|
메인테이블과 연관테이블에 각각 `fieldMappings` 생성:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 메인테이블용 fieldMappings 생성
|
|
|
|
|
let mainTableFieldMappings = [];
|
|
|
|
|
if (isFocusedTable && focusedSubTablesData) {
|
|
|
|
|
focusedSubTablesData.subTables.forEach((subTable) => {
|
|
|
|
|
subTable.fieldMappings?.forEach((mapping) => {
|
|
|
|
|
if (subTable.relationType === 'source' && mapping.sourceTable) {
|
|
|
|
|
mainTableFieldMappings.push({
|
|
|
|
|
sourceField: mapping.sourceField,
|
|
|
|
|
targetField: mapping.targetField,
|
|
|
|
|
sourceDisplayName: mapping.sourceDisplayName,
|
|
|
|
|
targetDisplayName: mapping.targetDisplayName,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// ... 기타 relationType 처리
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 연관 테이블용 fieldMappings 생성
|
|
|
|
|
let relatedTableFieldMappings = [];
|
|
|
|
|
if (isRelatedTable && relatedTableInfo && focusedSubTablesData) {
|
|
|
|
|
// 이 테이블이 sourceTable인 경우의 매핑 추출
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// node.data에 fieldMappings 전달
|
|
|
|
|
return {
|
|
|
|
|
...node,
|
|
|
|
|
data: {
|
|
|
|
|
...node.data,
|
|
|
|
|
fieldMappings: isFocusedTable ? mainTableFieldMappings
|
|
|
|
|
: (isRelatedTable ? relatedTableFieldMappings : []),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 6. 엣지 방향 오류 (참조 방향 반대)
|
|
|
|
|
|
|
|
|
|
#### 문제
|
|
|
|
|
- `customer_mng` 화면 포커스 시 `customer_item_mapping`으로 연결선이 표시됨
|
|
|
|
|
- 실제로는 `customer_item_mapping.customer_id` → `customer_mng.customer_code` 참조 관계
|
|
|
|
|
- 참조하는 쪽(A)이 아닌 참조당하는 쪽(B)에서 연결선이 나가는 오류
|
|
|
|
|
|
|
|
|
|
#### 원인
|
|
|
|
|
```typescript
|
|
|
|
|
// 기존 잘못된 로직
|
|
|
|
|
newEdges.push({
|
|
|
|
|
id: edgeId,
|
|
|
|
|
source: `table-${mainTable}`, // 참조당하는 테이블 (잘못됨)
|
|
|
|
|
target: `table-${subTable.tableName}`, // 참조하는 테이블 (잘못됨)
|
|
|
|
|
// ...
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 해결
|
|
|
|
|
```typescript
|
|
|
|
|
// 수정된 올바른 로직 - source/target 교환
|
|
|
|
|
newEdges.push({
|
|
|
|
|
id: edgeId,
|
|
|
|
|
source: `table-${subTable.tableName}`, // 참조하는 테이블 (올바름)
|
|
|
|
|
target: `table-${mainTable}`, // 참조당하는 테이블 (올바름)
|
|
|
|
|
// ...
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 동작 예시
|
|
|
|
|
| 관계 | 포커스된 화면 | 예상 동작 | 실제 동작 (수정 후) |
|
|
|
|
|
|------|--------------|----------|-------------------|
|
|
|
|
|
| A가 B를 참조 | A 포커스 | A→B 연결선 ✅ | A→B 연결선 ✅ |
|
|
|
|
|
| A가 B를 참조 | B 포커스 | 연결선 없음 ✅ | 연결선 없음 ✅ |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 7. column_labels 필터링 누락
|
|
|
|
|
|
|
|
|
|
#### 문제
|
|
|
|
|
- `inbound_mng.item_code`가 `item_info`를 참조하는 것으로 조인선 표시
|
|
|
|
|
- 해당 필드는 과거 `entity` 타입이었다가 `text`로 변경됨
|
|
|
|
|
- `reference_table` 데이터가 잔존하여 잘못된 조인 관계 생성
|
|
|
|
|
|
|
|
|
|
#### 원인
|
|
|
|
|
```sql
|
|
|
|
|
-- 기존 잘못된 쿼리
|
|
|
|
|
WHERE cl.reference_table IS NOT NULL
|
|
|
|
|
AND cl.reference_table != ''
|
|
|
|
|
AND cl.reference_table != suc.main_table
|
|
|
|
|
-- input_type 체크 없음!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 해결
|
|
|
|
|
```sql
|
|
|
|
|
-- 수정된 쿼리 - input_type = 'entity' 조건 추가
|
|
|
|
|
WHERE cl.reference_table IS NOT NULL
|
|
|
|
|
AND cl.reference_table != ''
|
|
|
|
|
AND cl.reference_table != suc.main_table
|
|
|
|
|
AND cl.input_type = 'entity' -- 추가됨
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## NTT 설정 소스별 처리
|
|
|
|
|
|
|
|
|
|
시스템에서 조인 정보가 정의되는 모든 소스를 범용적으로 처리합니다:
|
|
|
|
|
|
|
|
|
|
| 소스 | 위치 | 처리 상태 |
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
| `column_labels.reference_table` | 테이블 관리 | ✅ 처리됨 |
|
|
|
|
|
| `componentConfig.fieldMappings` | autocomplete, entity-search | ✅ 처리됨 |
|
|
|
|
|
| `componentConfig.columns.mapping` | modal-repeater-table | ✅ 처리됨 |
|
|
|
|
|
| `componentConfig.parentDataMapping` | selected-items-detail-input | ✅ 처리됨 |
|
|
|
|
|
| `componentConfig.rightPanel.relation` | split-panel-layout | ✅ 처리됨 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## API 인터페이스 변경
|
|
|
|
|
|
|
|
|
|
### FieldMappingInfo
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export interface FieldMappingInfo {
|
|
|
|
|
sourceTable?: string; // 연관 테이블명 (parentDataMapping에서 사용)
|
|
|
|
|
sourceField: string;
|
|
|
|
|
targetField: string;
|
|
|
|
|
sourceDisplayName?: string; // 연관 테이블 컬럼 한글명
|
|
|
|
|
targetDisplayName?: string; // 메인 테이블 컬럼 한글명
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### SubTableInfo
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export interface SubTableInfo {
|
|
|
|
|
tableName: string;
|
|
|
|
|
componentType: string;
|
|
|
|
|
relationType: 'lookup' | 'source' | 'join' | 'reference' | 'parentMapping' | 'rightPanelRelation';
|
|
|
|
|
fieldMappings?: FieldMappingInfo[];
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 테스트 결과
|
|
|
|
|
|
|
|
|
|
### 판매품목정보 그룹 (3번째 화면 포커스)
|
|
|
|
|
|
|
|
|
|
| 테이블 | 컬럼 | 표시 내용 |
|
|
|
|
|
|--------|------|----------|
|
|
|
|
|
| `customer_item_mapping` | 거래처 ID | ← 거래처 코드 (조인) |
|
|
|
|
|
| `customer_item_mapping` | 품목 ID | ← 품번 (조인) |
|
|
|
|
|
| `customer_mng` | 거래처 코드 | ← 거래처 ID (조인) |
|
|
|
|
|
| `item_info` | 품번 | ← 품목 ID (조인) |
|
|
|
|
|
|
|
|
|
|
### 수주관리 그룹 (기존 정상 작동 확인)
|
|
|
|
|
|
|
|
|
|
모든 조인 관계 및 컬럼 표시 정상 작동
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 범용성 검증
|
|
|
|
|
|
|
|
|
|
| 항목 | 상태 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| 특정 테이블명 하드코딩 | ❌ 없음 |
|
|
|
|
|
| 특정 컬럼명 하드코딩 | ❌ 없음 |
|
|
|
|
|
| 조건 기반 분기 | ✅ `sourceTable` 존재 여부, `relationType`으로 판단 |
|
|
|
|
|
| 새로운 화면/그룹 적용 | ✅ 자동 적용 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 시각화 결과 예시
|
|
|
|
|
|
|
|
|
|
### 포커스 전 (그룹 전체 선택)
|
|
|
|
|
```
|
|
|
|
|
[화면1] ─── [화면2] ─── [화면3]
|
|
|
|
|
│ │ │
|
|
|
|
|
▼ ▼ ▼
|
|
|
|
|
[item_info] [customer_mng] [customer_item_mapping]
|
|
|
|
|
(메인) (메인) (메인)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3번째 화면 포커스 시
|
|
|
|
|
```
|
|
|
|
|
[화면1] ─── [화면2] ─── [화면3] ← 활성
|
|
|
|
|
│ │ │
|
|
|
|
|
▼ ▼ ▼
|
|
|
|
|
[item_info] [customer_mng] [customer_item_mapping]
|
|
|
|
|
(연관) (연관) (메인/활성)
|
|
|
|
|
│ │ │
|
|
|
|
|
│ │ ┌───────────┴───────────┐
|
|
|
|
|
│ │ │ │
|
|
|
|
|
└─────────────┼──────┤ 거래처 ID ← 거래처 코드 │
|
|
|
|
|
│ │ 품목 ID ← 품번 │
|
|
|
|
|
│ │ (+ 13개 사용 컬럼) │
|
|
|
|
|
│ └───────────────────────┘
|
|
|
|
|
┌─────────────┘
|
|
|
|
|
│ 거래처 코드 ← 거래처 ID
|
|
|
|
|
└─────────────────────
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 결론
|
|
|
|
|
|
|
|
|
|
1. **범용성 확보**: 모든 NTT 설정 소스에서 조인 관계 자동 추출
|
|
|
|
|
2. **정확성 개선**: `relationType`과 `sourceTable` 기반 정확한 조인 컬럼 식별
|
|
|
|
|
3. **사용자 경험 향상**: 한글명 표시 및 직관적인 연결 정보 제공
|
|
|
|
|
4. **유지보수성**: 새로운 화면/그룹 추가 시 별도 코드 수정 불필요
|
|
|
|
|
5. **메인-메인 조인 로직 단일화**: 기존 중복 로직(초록색/주황색)을 주황색 단일 로직으로 통합
|
|
|
|
|
- `subTable.tableName`이 다른 화면의 메인 테이블인 경우 자동 연결
|
|
|
|
|
- `fieldMappings.sourceTable` 없어도 메인 테이블 간 조인 감지
|
|
|
|
|
6. **연관 테이블 포커싱 개선**: 포커스된 화면의 서브 테이블이 다른 화면의 메인 테이블인 경우 활성화
|
|
|
|
|
- 회색 처리 대신 정상 표시 및 조인 컬럼 강조
|
|
|
|
|
- `relatedMainTables` 생성 로직 확장으로 `subTable.tableName` 기반 감지
|
|
|
|
|
7. **선 연결 규격 정립**: 일관되고 직관적인 연결선 방향 규격화
|
|
|
|
|
- **메인-메인 연결선**: `bottom → bottom_target` 고정 (서브테이블 구간을 통해 연결)
|
|
|
|
|
- **서브 테이블 연결선**: `bottom → top` 고정 (아래로 문어발처럼 뻗어나감)
|
|
|
|
|
- **절대 규칙**: 선이 테이블이나 화면을 통과하지 않음
|
|
|
|
|
8. **초기 메인-메인 엣지 생성**: 그룹 로딩 시점에 메인 테이블 간 연결선 미리 생성
|
|
|
|
|
- 연한 주황색(`#fdba74`) 점선으로 기본 표시
|
|
|
|
|
- 포커싱 시 진한 주황색(`#ea580c`)으로 강조 및 애니메이션
|
|
|
|
|
- 중복 방지를 위한 `pairKey` 기반 Set 사용
|
|
|
|
|
9. **ReactFlow Handle ID 구분**: 메인-메인 연결을 위한 핸들 추가
|
|
|
|
|
- `TableNode`에 `id="bottom_target"` (type="target") 핸들 추가
|
|
|
|
|
- 메인-메인 엣지는 `sourceHandle="bottom"`, `targetHandle="bottom_target"` 사용
|
|
|
|
|
- 서브 테이블 엣지는 기존대로 `sourceHandle="bottom"`, `targetHandle="top"` 사용
|
|
|
|
|
10. **메인-메인 강조 로직 개선**: 중복 연결선 및 잘못된 연결선 방지
|
|
|
|
|
- 포커스된 메인 테이블이 `source`인 경우에만 해당 엣지 강조
|
|
|
|
|
- 양방향 중복 강조 방지 (A→B와 B→A 동시 강조 안 함)
|
|
|
|
|
- 연결되지 않은 테이블에서 조인선이 나타나는 문제 해결
|
|
|
|
|
11. **엣지 방향 수정**: 참조 방향에 맞게 엣지 source/target 교정
|
|
|
|
|
- 기존 잘못된 방향: `mainTable → subTable.tableName` (참조당하는 테이블 → 참조하는 테이블)
|
|
|
|
|
- 수정된 올바른 방향: `subTable.tableName → mainTable` (참조하는 테이블 → 참조당하는 테이블)
|
|
|
|
|
- A가 B를 참조(entity 설정)하면: A 포커스 시 A→B 연결선 표시
|
|
|
|
|
- B 포커스 시 연결선 없음 (B는 A를 참조하지 않으므로)
|
|
|
|
|
12. **column_labels 필터링 강화**: `input_type = 'entity'`인 경우만 참조 관계로 인정
|
|
|
|
|
- `input_type = 'text'`인 경우 `reference_table`이 있어도 조인 관계로 취급하지 않음
|
|
|
|
|
- 과거 entity 설정 후 text로 변경된 경우 잔존 데이터 무시
|
|
|
|
|
13. **범용적 엣지 방향 결정 로직**: `relationType`에 따른 조건부 방향 결정
|
|
|
|
|
- `parentMapping` (sourceTable 있음): `mainTable → sourceTable` 방향
|
|
|
|
|
- `rightPanelRelation` (foreignKey 있음): `subTable → mainTable` 방향
|
|
|
|
|
- `reference` (column_labels): `mainTable → subTable` 방향
|
|
|
|
|
- 기본값: `subTable → mainTable` 방향
|
|
|
|
|
|
|
|
|
|
### 연결선 규격 다이어그램
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
[화면1] [화면2] [화면3] [화면4]
|
|
|
|
|
│ │ │ │
|
|
|
|
|
▼ ▼ ▼ ▼
|
|
|
|
|
[Table1] [Table2] [Table3] [Table4] ← 메인 테이블
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ bottom │ bottom │ bottom │ bottom
|
|
|
|
|
▼ ▼ ▼ ▼
|
|
|
|
|
┌─────────────────────────────────────────┐ ← 서브테이블 구간
|
|
|
|
|
│ (메인-메인 연결선이 이 구간을 통과) │
|
|
|
|
|
│ ◄── bottom ─ bottom_target ───► │
|
|
|
|
|
│ (서브테이블 연결선도 이 구간에서 연결) │
|
|
|
|
|
└─────────────────────────────────────────┘
|
|
|
|
|
│ │
|
|
|
|
|
▼ top ▼ top
|
|
|
|
|
[서브1] [서브2] [서브3] ← 서브 테이블
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## [계획] 연결선 정리 시스템 설계
|
|
|
|
|
|
|
|
|
|
> **상태**: 요건 정의 단계 (미구현)
|
|
|
|
|
|
|
|
|
|
### 배경 및 필요성
|
|
|
|
|
|
|
|
|
|
현재 시스템에서 연결선 종류가 증가함에 따라 체계적인 선 관리 시스템이 필요합니다.
|
|
|
|
|
|
|
|
|
|
| 현재 상태 | 문제점 |
|
|
|
|
|
|-----------|--------|
|
|
|
|
|
| 조인선만 존재 | 종속 조회, 저장 테이블 등 추가 선 종류 필요 |
|
|
|
|
|
| 경로 규격 미정립 | 선이 테이블을 통과할 가능성 |
|
|
|
|
|
| 겹침 방지 미흡 | 같은 경로 사용 시 선 겹침 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 절대 규칙 (위반 불가)
|
|
|
|
|
|
|
|
|
|
1. **선이 테이블 노드를 가로로 통과하면 안됨**
|
|
|
|
|
2. **선이 화면 노드를 통과하면 안됨**
|
|
|
|
|
3. **같은 종류의 선은 같은 구간에서 이동**
|
|
|
|
|
4. **다른 종류의 선은 겹치지 않아야 함**
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 연결선 종류 정의
|
|
|
|
|
|
|
|
|
|
#### 현재 구현됨
|
|
|
|
|
|
|
|
|
|
| 번호 | 선 종류 | 의미 | 색상 | 스타일 | 상태 |
|
|
|
|
|
|------|---------|------|------|--------|------|
|
|
|
|
|
| 1 | 화면 → 메인 테이블 | 화면이 사용하는 테이블 | 파란색 (`#3b82f6`) | 실선 | 구현됨 |
|
|
|
|
|
| 2 | 조인선 (엔티티 참조) | 테이블 간 데이터 병합 | 주황색 (`#ea580c`) | 점선 `8,4` | 구현됨 |
|
|
|
|
|
|
|
|
|
|
#### 추가 예정
|
|
|
|
|
|
|
|
|
|
| 번호 | 선 종류 | 의미 | 색상 (예정) | 스타일 (예정) | 상태 |
|
|
|
|
|
|------|---------|------|-------------|---------------|------|
|
|
|
|
|
| 3 | 종속 조회선 (Master-Detail) | 선택 기준 필터 조회 | 시안/보라색 | 점선 `4,4` | 미구현 |
|
|
|
|
|
| 4 | 저장 테이블선 | 데이터 저장 대상 | 녹색 | 실선 or 점선 | 미구현 |
|
|
|
|
|
| 5+ | 기타 확장 | 데이터 플로우 등 | 미정 | 미정 | 미구현 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 개념 구분: Join vs 종속 조회
|
|
|
|
|
|
|
|
|
|
| 구분 | Join (조인) | 종속 조회 (Filter) |
|
|
|
|
|
|------|------------|-------------------|
|
|
|
|
|
| **데이터 처리** | 두 테이블 **병합** | 한 테이블로 다른 테이블 **필터링** |
|
|
|
|
|
| **표시 방식** | 같은 그리드에 합쳐서 | 별도 그리드에 각각 |
|
|
|
|
|
| **SQL** | `JOIN ON A.key = B.key` | `WHERE B.key = (선택된 A.key)` |
|
|
|
|
|
| **예시** | `inbound_mng` + `item_info` 정보 합침 | `customer_mng` 선택 → `customer_item_mapping` 별도 조회 |
|
|
|
|
|
| **현재 표현** | 주황색 점선 | 미표현 |
|
|
|
|
|
|
|
|
|
|
**더블 그리드 패턴 예시:**
|
|
|
|
|
```
|
|
|
|
|
[customer_mng 그리드] | [customer_item_mapping 그리드]
|
|
|
|
|
(메인/선택) → (필터링된 결과)
|
|
|
|
|
```
|
|
|
|
|
- 이 경우 `customer_mng`가 진짜 메인
|
|
|
|
|
- `customer_item_mapping`은 **종속 조회** (메인이 아님, 선택에 따라 필터링)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 레이아웃 구간 정의
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Y: 0-200 ┌─────────────────────────────────────────────┐
|
|
|
|
|
│ [화면1] [화면2] [화면3] [화면4] │ ← 화면 구간
|
|
|
|
|
└─────────────────────────────────────────────┘
|
|
|
|
|
│ │ │ │
|
|
|
|
|
Y: 200-300 ════════════════════════════════════════════════ ← 화면-테이블 연결 구간 (파란 실선)
|
|
|
|
|
│ │ │ │
|
|
|
|
|
Y: 300-500 ┌─────────────────────────────────────────────┐
|
|
|
|
|
│ [Table1] [Table2] [Table3] [Table4] │ ← 메인 테이블 구간
|
|
|
|
|
└─────────────────────────────────────────────┘
|
|
|
|
|
│ │ │ │
|
|
|
|
|
▼ ▼ ▼ ▼
|
|
|
|
|
Y: 500-700 ┌─────────────────────────────────────────────┐
|
|
|
|
|
│ [서브1] [서브2] [서브3] │ ← 서브 테이블 구간
|
|
|
|
|
└─────────────────────────────────────────────┘
|
|
|
|
|
│ │ │ │
|
|
|
|
|
Y: 700-750 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ← [레벨1] 조인선 구간 (주황)
|
|
|
|
|
Y: 750-800 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ← [레벨2] 종속 조회선 구간 (시안/보라)
|
|
|
|
|
Y: 800-850 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ← [레벨3] 저장 테이블선 구간 (녹색)
|
|
|
|
|
Y: 850+ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ← [레벨4+] 확장 구간
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 선 경로 규격
|
|
|
|
|
|
|
|
|
|
#### 원칙
|
|
|
|
|
1. **수직 이동**: 노드 사이 공간에서만 (노드 통과 안함)
|
|
|
|
|
2. **수평 이동**: 각 선 종류별 전용 레벨에서만
|
|
|
|
|
3. **겹침 방지**: 서로 다른 Y 레벨 사용
|
|
|
|
|
|
|
|
|
|
#### 선 종류별 경로
|
|
|
|
|
|
|
|
|
|
| 선 종류 | 출발 핸들 | 도착 핸들 | 수평 이동 레벨 |
|
|
|
|
|
|---------|-----------|-----------|----------------|
|
|
|
|
|
| 화면 → 메인 | 화면 bottom | 테이블 top | Y: 200-300 (화면-테이블 구간) |
|
|
|
|
|
| 메인 → 서브 | 테이블 bottom | 서브 top | Y: 500-700 (서브 테이블 구간 내) |
|
|
|
|
|
| 조인선 (메인-메인) | 테이블 bottom | 테이블 bottom_target | **Y: 700-750** (레벨1) |
|
|
|
|
|
| 종속 조회선 | 테이블 bottom | 테이블 bottom_target | **Y: 750-800** (레벨2) |
|
|
|
|
|
| 저장 테이블선 | 테이블 bottom | 테이블 bottom_target | **Y: 800-850** (레벨3) |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 핸들 설계
|
|
|
|
|
|
|
|
|
|
현재 `TableNode`에 필요한 핸들:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
[top] (target) ← 화면에서 오는 선
|
|
|
|
|
│
|
|
|
|
|
┌────────┼────────┐
|
|
|
|
|
│ │ │
|
|
|
|
|
│ 테이블 노드 │
|
|
|
|
|
│ │
|
|
|
|
|
└────────┬────────┘
|
|
|
|
|
│
|
|
|
|
|
[bottom] (source) ← 나가는 선 (서브테이블, 조인 등)
|
|
|
|
|
│
|
|
|
|
|
[bottom_target] (target) ← 메인-메인 연결용 들어오는 선
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**추가 필요 핸들 (겹침 방지용):**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌────────────────────────┐
|
|
|
|
|
│ │
|
|
|
|
|
│ 테이블 노드 │
|
|
|
|
|
│ │
|
|
|
|
|
└───┬───────┬───────┬────┘
|
|
|
|
|
│ │ │
|
|
|
|
|
(30%) (50%) (70%) ← X 위치
|
|
|
|
|
│ │ │
|
|
|
|
|
[bottom_join] [bottom] [bottom_filter]
|
|
|
|
|
│ │
|
|
|
|
|
조인선 전용 종속 조회선 전용
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 구현 방안 비교
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### 방안 A: 커스텀 엣지 경로 (완벽 분리)
|
|
|
|
|
|
|
|
|
|
**레이어 다이어그램:**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ [화면1] [화면2] [화면3] [화면4] │ Y: 0-200
|
|
|
|
|
│ │ │ │ │ │
|
|
|
|
|
└────┼─────────────┼──────────────┼──────────────┼────────────────────┘
|
|
|
|
|
│ │ │ │
|
|
|
|
|
═════╧═════════════╧══════════════╧══════════════╧════════════════════ Y: 250 (화면-테이블 구간)
|
|
|
|
|
│ │ │ │
|
|
|
|
|
┌────┼─────────────┼──────────────┼──────────────┼────────────────────┐
|
|
|
|
|
│ [Table1] [Table2] [Table3] [Table4] │ Y: 300-500
|
|
|
|
|
│ │ │ │ │ │
|
|
|
|
|
└────┼─────────────┼──────────────┼──────────────┼────────────────────┘
|
|
|
|
|
│ │ │ │
|
|
|
|
|
▼ ▼ ▼ ▼
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ [서브1] [서브2] [서브3] │ Y: 550-650
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
│ │
|
|
|
|
|
─────┴───────────── 레벨1: 조인선 (주황) ────────┴───────────────────── Y: 700
|
|
|
|
|
│ │
|
|
|
|
|
└───────────────────────────────────────────┘
|
|
|
|
|
│ │
|
|
|
|
|
─────┴───────────── 레벨2: 종속 조회 (보라) ─────┴───────────────────── Y: 750
|
|
|
|
|
│ │
|
|
|
|
|
└─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
|
|
|
|
│ │
|
|
|
|
|
─────┴───────────── 레벨3: 저장 테이블 (녹색) ───┴───────────────────── Y: 800
|
|
|
|
|
│ │
|
|
|
|
|
└─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**특징**: 각 선 종류가 **전용 Y 레벨**에서만 수평 이동. 절대 겹치지 않음.
|
|
|
|
|
|
|
|
|
|
**코드 예시:**
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 커스텀 경로 계산
|
|
|
|
|
const getCustomPath = (source, target, lineType) => {
|
|
|
|
|
const levels = {
|
|
|
|
|
join: 725, // 조인선 Y 레벨
|
|
|
|
|
filter: 775, // 종속 조회선 Y 레벨
|
|
|
|
|
save: 825, // 저장 테이블선 Y 레벨
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const level = levels[lineType];
|
|
|
|
|
|
|
|
|
|
// 수직 → 수평(레벨) → 수직 경로
|
|
|
|
|
return `M${source.x},${source.y}
|
|
|
|
|
L${source.x},${level}
|
|
|
|
|
L${target.x},${level}
|
|
|
|
|
L${target.x},${target.y}`;
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**장점:**
|
|
|
|
|
- 완벽한 분리 보장
|
|
|
|
|
- 절대 규칙 100% 준수
|
|
|
|
|
- 확장성 우수
|
|
|
|
|
|
|
|
|
|
**단점:**
|
|
|
|
|
- 구현 복잡도 높음
|
|
|
|
|
- ReactFlow 기본 기능 대신 커스텀 필요
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### 방안 B: 핸들 위치 분리 + smoothstep
|
|
|
|
|
|
|
|
|
|
**레이어 다이어그램:**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ [화면1] [화면2] [화면3] [화면4] │
|
|
|
|
|
└────┬─────────────┬──────────────┬──────────────┬────────────────────┘
|
|
|
|
|
│ │ │ │
|
|
|
|
|
┌────┼─────────────┼──────────────┼──────────────┼────────────────────┐
|
|
|
|
|
│ [Table1] [Table2] [Table3] [Table4] │
|
|
|
|
|
│ 30% 50% 70% 30% 50% 70% │ ← 핸들 X 위치
|
|
|
|
|
│ │ │ │ │ │ │ │
|
|
|
|
|
└───┼───┼───┼──────────────────────────────────┼───┼───┼──────────────┘
|
|
|
|
|
│ │ │ │ │ │
|
|
|
|
|
│ │ └── 종속 조회 (보라) ──────────────┼───┼───┘ ← 70% 위치
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ └────── 조인선 (주황) ─────────────────┼───┘ ← 50% 위치
|
|
|
|
|
│ │
|
|
|
|
|
└────────── 저장 테이블 (녹색) ────────────┘ ← 30% 위치
|
|
|
|
|
|
|
|
|
|
※ 시작점은 다르지만 경로가 가까움 (smoothstep 자동 계산)
|
|
|
|
|
※ 선이 가까이 지나가서 겹칠 가능성 있음
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**특징**: 핸들 X 위치만 다름. **경로가 가까이 지나가서 겹칠 가능성** 있음.
|
|
|
|
|
|
|
|
|
|
**코드 예시:**
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 핸들 위치로 분리
|
|
|
|
|
<Handle id="bottom_join" style={{ left: '30%' }} />
|
|
|
|
|
<Handle id="bottom_filter" style={{ left: '70%' }} />
|
|
|
|
|
|
|
|
|
|
// smoothstep + offset
|
|
|
|
|
edge.pathOptions = { offset: lineType === 'filter' ? 50 : 0 };
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**장점:**
|
|
|
|
|
- ReactFlow 기본 기능 활용
|
|
|
|
|
- 구현 상대적 단순
|
|
|
|
|
|
|
|
|
|
**단점:**
|
|
|
|
|
- 완벽한 분리 보장 어려움
|
|
|
|
|
- 복잡한 경우 선 겹침 가능
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### 방안 C: 선 대신 마커/뱃지 (종속 조회만)
|
|
|
|
|
|
|
|
|
|
**레이어 다이어그램:**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ [화면1] [화면2] [화면3] [화면4] │
|
|
|
|
|
└────┬─────────────┬──────────────┬──────────────┬────────────────────┘
|
|
|
|
|
│ │ │ │
|
|
|
|
|
┌────┼─────────────┼──────────────┼──────────────┼────────────────────┐
|
|
|
|
|
│ [Table1] [Table2] [Table3] [Table4 🔗] │
|
|
|
|
|
│ │ ↑ │
|
|
|
|
|
│ │ "Table1에서 필터 조회" (툴팁) │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ └────────── 조인선만 표시 (주황) ──────────┘ │
|
|
|
|
|
│ │
|
|
|
|
|
│ ※ 종속 조회, 저장 테이블은 선 없이 뱃지/아이콘으로만 표시 │
|
|
|
|
|
│ ※ 마우스 오버 시 관계 정보 툴팁 표시 │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
│
|
|
|
|
|
▼
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ [서브1] [서브2] [서브3] │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**특징**: 선 없음 = **겹침/통과 문제 없음**. 하지만 관계 시각화 약함.
|
|
|
|
|
|
|
|
|
|
**코드 예시:**
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 테이블 노드에 관계 뱃지 표시
|
|
|
|
|
<TableNode>
|
|
|
|
|
<Badge icon="filter" tooltip="customer_mng에서 필터 조회" />
|
|
|
|
|
</TableNode>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**장점:**
|
|
|
|
|
- 선 없음 = 겹침/통과 문제 없음
|
|
|
|
|
- 화면 깔끔
|
|
|
|
|
|
|
|
|
|
**단점:**
|
|
|
|
|
- 관계 시각화 약함
|
|
|
|
|
- 일관성 부족 (조인은 선, 종속은 뱃지)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### 방안 D: 하이브리드 (커스텀 경로 통일) - 권장
|
|
|
|
|
|
|
|
|
|
**레이어 다이어그램:**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 레이어 0: 화면 노드 │
|
|
|
|
|
│ [화면1] [화면2] [화면3] [화면4] │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
↓
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 레이어 1: 화면-테이블 연결 (파란 실선) │
|
|
|
|
|
│ ═══════════════════════════════════════════════════════════════════ │ Y: 250
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
↓
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 레이어 2: 메인 테이블 노드 │
|
|
|
|
|
│ [Table1] [Table2] [Table3] [Table4] │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
↓
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 레이어 3: 서브 테이블 노드 │
|
|
|
|
|
│ [서브1] [서브2] [서브3] │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
↓
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 레이어 4: 조인선 구간 (주황색) │
|
|
|
|
|
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ Y: 700-725
|
|
|
|
|
│ Table1 ──────────────────────────────────► Table4 │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
↓
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 레이어 5: 종속 조회 구간 (보라색) │
|
|
|
|
|
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ Y: 750-775
|
|
|
|
|
│ Table1 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─► Table3 │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
↓
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 레이어 6: 저장 테이블 구간 (녹색) │
|
|
|
|
|
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ Y: 800-825
|
|
|
|
|
│ Table2 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─► Table4 │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
↓
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 레이어 7+: 확장 가능 │
|
|
|
|
|
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ Y: 850+
|
|
|
|
|
│ (미래 확장용) │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**특징**: 모든 선이 **전용 레이어**에서 이동. 확장성 최고. 절대 겹치지 않음.
|
|
|
|
|
|
|
|
|
|
**구현 방식:**
|
|
|
|
|
- **조인선**: 커스텀 경로 (방안 A) - 레벨4 (Y: 700-725)
|
|
|
|
|
- **종속 조회선**: 커스텀 경로 (방안 A) - 레벨5 (Y: 750-775)
|
|
|
|
|
- **저장 테이블선**: 커스텀 경로 (방안 A) - 레벨6 (Y: 800-825)
|
|
|
|
|
|
|
|
|
|
모든 선을 동일한 커스텀 경로 시스템으로 통일.
|
|
|
|
|
|
|
|
|
|
**장점:**
|
|
|
|
|
- 일관된 시스템
|
|
|
|
|
- 완벽한 분리
|
|
|
|
|
- 확장성 최고
|
|
|
|
|
- 절대 규칙 100% 준수
|
|
|
|
|
|
|
|
|
|
**단점:**
|
|
|
|
|
- 초기 구현 비용 높음
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 방안 비교 요약표
|
|
|
|
|
|
|
|
|
|
| 방안 | 겹침 방지 | 절대규칙 준수 | 구현 복잡도 | 확장성 | 시각적 일관성 |
|
|
|
|
|
|------|-----------|---------------|-------------|--------|---------------|
|
|
|
|
|
| **A** | 완벽 | 100% | 높음 | 좋음 | 좋음 |
|
|
|
|
|
| **B** | 불완전 | 90% | 낮음 | 보통 | 보통 |
|
|
|
|
|
| **C** | 완벽 (선 없음) | 100% | 낮음 | 좋음 | 약함 |
|
|
|
|
|
| **D** | **완벽** | **100%** | 높음 | **최고** | **최고** |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 구현 우선순위 (제안)
|
|
|
|
|
|
|
|
|
|
| 순서 | 작업 | 설명 |
|
|
|
|
|
|------|------|------|
|
|
|
|
|
| 1 | 커스텀 엣지 컴포넌트 개발 | 레벨 기반 경로 계산 |
|
|
|
|
|
| 2 | 기존 조인선 마이그레이션 | smoothstep → 커스텀 경로 |
|
|
|
|
|
| 3 | 종속 조회선 구현 | 레벨2 경로 + 시안/보라 색상 |
|
|
|
|
|
| 4 | 저장 테이블선 구현 | 레벨3 경로 + 녹색 |
|
|
|
|
|
| 5 | 테스트 및 최적화 | 다양한 그룹에서 검증 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 색상 팔레트 (확정 - 2026-01-08)
|
|
|
|
|
|
|
|
|
|
| 관계 유형 | 시각적 유형 | 기본 색상 | 강조 색상 | 설명 |
|
|
|
|
|
|-----------|-------------|-----------|-----------|------|
|
|
|
|
|
| 화면 → 메인 | - | `#3b82f6` (파랑) | `#2563eb` | 화면-테이블 연결 |
|
|
|
|
|
| 마스터-디테일 | `filter` | `#8b5cf6` (보라) | `#c4b5fd` | split-panel의 필터링 관계 |
|
|
|
|
|
| 계층 구조 | `hierarchy` | `#06b6d4` (시안) | `#a5f3fc` | 부모-자식 자기 참조 |
|
|
|
|
|
| 코드 참조 | `lookup` | `#f59e0b` (주황) | `#fcd34d` | autocomplete 등 코드→명칭 |
|
|
|
|
|
| 데이터 매핑 | `mapping` | `#10b981` (녹색) | `#6ee7b7` | parentDataMapping |
|
|
|
|
|
| 엔티티 조인 | `join` | `#ea580c` (주황) | `#fdba74` | 실제 LEFT/INNER JOIN |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 관계 유형 추론 시스템 (2026-01-08 구현)
|
|
|
|
|
|
|
|
|
|
### 구현 개요
|
|
|
|
|
|
|
|
|
|
테이블 간 연결선이 단순 "조인"만이 아니라 다양한 관계 유형을 가지므로,
|
|
|
|
|
기존 컴포넌트 설정을 기반으로 관계 유형을 추론하여 시각화에 반영합니다.
|
|
|
|
|
|
|
|
|
|
### 관계 유형 분류
|
|
|
|
|
|
|
|
|
|
| 유형 | 기술적 의미 | 컴포넌트 | 식별 조건 |
|
|
|
|
|
|------|------------|----------|-----------|
|
|
|
|
|
| `filter` | 마스터-디테일 필터링 | split-panel-layout | `relationType='rightPanelRelation'` + `originalRelationType='join'` |
|
|
|
|
|
| `hierarchy` | 자기 참조 계층 구조 | split-panel-layout | `relationType='rightPanelRelation'` + `originalRelationType='detail'` |
|
|
|
|
|
| `mapping` | 데이터 참조 주입 | selected-items-detail-input | `relationType='parentMapping'` |
|
|
|
|
|
| `lookup` | 코드→명칭 변환 | autocomplete, entity-search | `relationType='lookup'` |
|
|
|
|
|
| `join` | 실제 엔티티 조인 | column_labels 참조 | `relationType='reference'` |
|
|
|
|
|
|
|
|
|
|
### 백엔드 수정 사항
|
|
|
|
|
|
|
|
|
|
`screenGroupController.ts`의 `getScreenSubTables` 함수에서 추가 필드 전달:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// rightPanel.relation 파싱 시
|
|
|
|
|
screenSubTables[screenId].subTables.push({
|
|
|
|
|
tableName: subTable,
|
|
|
|
|
componentType: componentType,
|
|
|
|
|
relationType: 'rightPanelRelation',
|
|
|
|
|
originalRelationType: relation?.type || 'join', // 추가: 원본 relation.type
|
|
|
|
|
foreignKey: relation?.foreignKey, // 추가: FK 컬럼
|
|
|
|
|
leftColumn: relation?.leftColumn, // 추가: 마스터 컬럼
|
|
|
|
|
fieldMappings: ...,
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 프론트엔드 수정 사항
|
|
|
|
|
|
|
|
|
|
1. **타입 확장** (`screenGroup.ts`):
|
|
|
|
|
- `SubTableInfo`에 `originalRelationType`, `foreignKey`, `leftColumn` 필드 추가
|
|
|
|
|
- `VisualRelationType` 타입 정의: `'filter' | 'hierarchy' | 'lookup' | 'mapping' | 'join'`
|
|
|
|
|
- `inferVisualRelationType()` 함수 추가
|
|
|
|
|
|
|
|
|
|
2. **시각화 적용** (`ScreenRelationFlow.tsx`):
|
|
|
|
|
- `RELATION_COLORS` 상수 정의 (관계 유형별 색상)
|
|
|
|
|
- 엣지 생성 시 `inferVisualRelationType()` 호출
|
|
|
|
|
- 엣지 스타일에 관계 유형별 색상 적용
|
|
|
|
|
|
|
|
|
|
### 2026-01-09 추가 수정 사항
|
|
|
|
|
|
|
|
|
|
1. **방향 수정**: `rightPanelRelation` 엣지 방향을 `mainTable → subTable`로 변경
|
|
|
|
|
- 이전: `customer_item_mapping → customer_mng` (디테일 → 마스터, 잘못됨)
|
|
|
|
|
- 수정: `customer_mng → customer_item_mapping` (마스터 → 디테일, 올바름)
|
|
|
|
|
|
|
|
|
|
2. **화면별 엣지 분리**: 같은 테이블 쌍이라도 화면별로 별도 엣지 생성
|
|
|
|
|
- `pairKey`에 `screenId` 포함: `${sourceScreenId}-${[mainTable, subTable].sort().join('-')}`
|
|
|
|
|
- `edgeId`에 `screenId` 포함: `edge-main-main-${sourceScreenId}-${referrerTable}-${referencedTable}`
|
|
|
|
|
|
|
|
|
|
3. **포커스 필터링 개선**: 해당 화면에서 생성된 연결선만 표시
|
|
|
|
|
- 이전: `sourceTable === focusedMainTable` 조건만 체크 (다른 화면 연결선도 표시됨)
|
|
|
|
|
- 수정: `edge.data.sourceScreenId === focusedScreenId` 조건으로 변경
|
|
|
|
|
|
|
|
|
|
4. **parentMapping을 join으로 변경**: `selected-items-detail-input`의 `parentDataMapping`은 FK 관계이므로 `join`으로 분류
|
|
|
|
|
- `customer_item_mapping → customer_mng`: 주황색 (FK: customer_id → customer_code)
|
|
|
|
|
- `customer_item_mapping → item_info`: 주황색 (FK: item_id → item_number)
|
|
|
|
|
|
|
|
|
|
5. **참조 테이블 시각적 표시**: lookup/reference 관계로 참조되는 테이블에 "X곳 참조" 배지 표시
|
|
|
|
|
- `TableNodeData`에 `referencedBy` 필드 추가
|
|
|
|
|
- `ReferenceInfo` 인터페이스 정의 (fromTable, fromColumn, toColumn, relationType)
|
|
|
|
|
- 테이블 노드 헤더에 주황색 배지로 참조 카운트 표시
|
|
|
|
|
- 툴팁에 참조하는 테이블 목록 표시
|
|
|
|
|
|
|
|
|
|
6. **마스터-디테일 필터링 관계 표시**: 디테일 테이블에 "X 필터" 배지 표시
|
|
|
|
|
- 마스터-디테일 관계(rightPanelRelation)도 참조 정보 수집에 추가
|
|
|
|
|
- **보라색 배지**로 "customer_mng 필터" 형태로 표시
|
|
|
|
|
- 툴팁에 FK 컬럼 정보 표시 (예: "customer_mng에서 필터링 (FK: customer_id)")
|
|
|
|
|
- lookup 관계는 주황색, filter 관계는 보라색으로 구분
|
|
|
|
|
|
|
|
|
|
7. **FK 컬럼 보라색 강조 + 키값 정보 표시**
|
|
|
|
|
- 디테일 테이블에서 필터링에 사용되는 FK 컬럼을 **보라색 배경**으로 강조
|
|
|
|
|
- 컬럼 옆에 참조 정보 표시: "← customer_mng.customer_code"
|
|
|
|
|
- 배지에 키값 정보 명확히 표시: "customer_mng.customer_code 필터"
|
|
|
|
|
- `TableNodeData`에 `filterColumns` 필드 추가
|
|
|
|
|
- `ReferenceInfo`에서 `toColumn` 정보로 FK 컬럼 식별
|
|
|
|
|
|
|
|
|
|
8. **포커스 상태 기반 필터 표시 개선**
|
|
|
|
|
- **문제**: 필터 배지가 모든 화면에서 항상 표시되어 혼란 발생
|
|
|
|
|
- **해결**: 포커스된 화면에서만 해당 관계의 필터 정보 표시
|
|
|
|
|
- 노드 생성 시 `referencedBy`, `filterColumns` 제거
|
|
|
|
|
- `styledNodes` 함수에서 포커스 상태에 따라 동적으로 설정
|
|
|
|
|
- 배지를 헤더 아래 별도 영역으로 이동하여 테이블명 가림 방지
|
|
|
|
|
|
|
|
|
|
**결과:**
|
|
|
|
|
| 화면 | customer_item_mapping 표시 |
|
|
|
|
|
|------|----------------------------|
|
2026-01-08 16:20:51 +09:00
|
|
|
| 1번 화면 포커스 | 필터 배지 O + FK 컬럼 보라색 + **상단 정렬** |
|
2026-01-08 14:24:33 +09:00
|
|
|
| 4번 화면 포커스 | 필터 배지 X, 조인만 표시 |
|
|
|
|
|
| 그룹 선택 (포커스 없음) | 필터 배지 X, 테이블명만 표시 |
|
|
|
|
|
|
2026-01-08 16:20:51 +09:00
|
|
|
9. **필터 컬럼 상단 정렬**
|
|
|
|
|
- 필터 컬럼도 파란색/주황색 컬럼처럼 상단에 정렬되어 표시
|
|
|
|
|
- `potentialFilteredColumns`에 `filterSet` 포함
|
|
|
|
|
- 정렬 순서: **조인 컬럼 → 필터 컬럼 → 사용 컬럼**
|
|
|
|
|
- 보라색 강조로 필터링 관계 명확히 구분
|
|
|
|
|
|
|
|
|
|
**정렬 우선순위:**
|
|
|
|
|
| 순서 | 컬럼 유형 | 색상 | 설명 |
|
|
|
|
|
|------|----------|------|------|
|
|
|
|
|
| 1 | 조인 컬럼 | 주황색 | FK 조인 관계 |
|
|
|
|
|
| 2 | 필터 컬럼 | 보라색 | 마스터-디테일 필터링 |
|
|
|
|
|
| 3 | 사용 컬럼 | 파란색 | 화면 필드 매핑 |
|
|
|
|
|
|
|
|
|
|
10. **방안 C 적용: 필터선 제거 + 보라색 테두리 애니메이션**
|
|
|
|
|
- 필터 관계는 선 없이 뱃지 + 테이블 테두리로만 표시 (겹침 방지)
|
|
|
|
|
- 필터링된 테이블에 **보라색 테두리 + 펄스 애니메이션** 적용
|
|
|
|
|
- 조인선(주황)만 표시, 필터선(보라) 제거
|
|
|
|
|
|
|
|
|
|
**시각적 표현:**
|
|
|
|
|
| 관계 유형 | 선 표시 | 테두리 | 배지 |
|
|
|
|
|
|----------|---------|--------|------|
|
|
|
|
|
| 조인 | ✅ 주황색 점선 | - | "조인" |
|
|
|
|
|
| 필터 | ❌ 없음 | 보라색 펄스 | "필터 + 키값" |
|
|
|
|
|
| 룩업 | ✅ 황색 점선 | - | "N곳 참조" |
|
|
|
|
|
|
|
|
|
|
**구현 상세:**
|
|
|
|
|
- `ScreenRelationFlow.tsx`: `visualRelationType === 'filter'`인 경우 엣지 생성 건너뛰기
|
|
|
|
|
- `ScreenNode.tsx`: `hasFilterRelation` 조건으로 보라색 테두리 + `animate-pulse` 클래스 적용
|
|
|
|
|
|
2026-01-08 14:24:33 +09:00
|
|
|
### 향후 개선 가능 사항
|
|
|
|
|
|
|
|
|
|
1. [ ] 범례(Legend) UI 추가 - 관계 유형별 색상 설명
|
|
|
|
|
2. [ ] 엣지 라벨에 관계 유형 표시
|
|
|
|
|
3. [x] 툴팁에 상세 관계 정보 표시 (FK, 연결 컬럼 등) - 완료
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 다음 단계
|
|
|
|
|
|
|
|
|
|
1. [x] 방안 확정 - 방안 1 (추론 로직) 선택
|
|
|
|
|
2. [x] 색상 팔레트 확정
|
|
|
|
|
3. [x] 관계 유형 추론 함수 구현
|
|
|
|
|
4. [x] 방향 및 포커스 필터링 수정
|
|
|
|
|
5. [x] parentMapping을 join으로 변경
|
|
|
|
|
6. [x] 참조 테이블 시각적 표시 추가
|
|
|
|
|
7. [x] 마스터-디테일 필터링 관계 표시 추가
|
|
|
|
|
8. [x] FK 컬럼 보라색 강조 + 키값 정보 표시
|
|
|
|
|
9. [x] 포커스 상태 기반 필터 표시 개선
|
2026-01-08 16:20:51 +09:00
|
|
|
10. [x] 필터 컬럼 상단 정렬 (조인 → 필터 → 사용 순서)
|
|
|
|
|
11. [x] 방안 C 적용: 필터선 제거 + 보라색 테두리 애니메이션
|
|
|
|
|
12. [ ] 범례 UI 추가 (선택사항)
|
|
|
|
|
13. [ ] 엣지 라벨에 관계 유형 표시 (선택사항)
|
2026-01-08 14:24:33 +09:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 관련 문서
|
|
|
|
|
|
|
|
|
|
- [멀티테넌시 구현 가이드](.cursor/rules/multi-tenancy-guide.mdc)
|
|
|
|
|
- [API 클라이언트 사용 규칙](.cursor/rules/api-client-usage.mdc)
|
|
|
|
|
- [관리자 페이지 스타일 가이드](.cursor/rules/admin-page-style-guide.mdc)
|
|
|
|
|
|