ERP-node/docs/화면설정모달_개선_완료_보고서.md

1029 lines
41 KiB
Markdown
Raw Permalink Normal View History

# 화면 설정 모달 개선 완료 보고서
## 개요
화면 관리에서 화면 노드 우클릭 시 열리는 설정 모달을 대폭 개선하여, 테이블 정보 시각화, 필드 매핑 확인, 컬럼 변경/추가/제거 기능, 조인 설정 기능, 실시간 프리뷰 기능을 강화했습니다.
## 주요 개선 사항
### 1. 화면 개요 탭 통합 개선
#### 1.1 필드 매핑 탭 → 개요 탭 통합
- 기존 "필드 매핑" 탭 제거
- 필드 매핑 정보를 개요 탭의 메인/필터 테이블 아코디언에 통합 표시
- 더 직관적이고 간결한 UI 제공
#### 1.2 메인 테이블 아코디언
- 메인 테이블(예: `customer_mng`)을 아코디언 형식으로 표시
- 클릭 시 테이블의 모든 컬럼 정보 표시
- **1열 레이아웃**: 컬럼 정보를 세로로 배치
- 화면에서 사용 중인 컬럼은 **파란색 배경 + "필드" 배지**로 강조
- **컬럼 정렬**:
- 사용중인 필드가 상단에 표시
- 화면에 표시되는 순서대로 정렬 (y좌표 기준)
- 미사용 컬럼은 하단에 표시
#### 1.3 필터 테이블 아코디언
- 필터 테이블(예: `customer_item_mapping`)을 아코디언 형식으로 표시
- 클릭 시 테이블의 모든 컬럼 정보 표시
- 컬럼별 색상 구분:
- **파란색**: 화면에서 사용 중인 컬럼 (필드)
- **보라색**: 필터 키 컬럼 (WHERE 절에 사용)
- **주황색**: 조인 키 컬럼 (JOIN 조건에 사용)
- **다중 배지 표시**: 컬럼이 필드이면서 조인/필터 키인 경우 배지 동시 표시
- 필터 연결 정보 표시 (예: `→ customer_mng`)
#### 1.4 컬럼 레이아웃 순서
- **순서**: `컬럼명 | 배지 | 데이터타입`
- 예: `거래처 코드` `[필드]` `character varying`
- 데이터타입은 오른쪽 정렬
#### 1.5 클릭 스타일 개선
- **테두리 제거**: ring-2, ring-offset 등 제거
- **강조 색상 연하게**:
- 선택됨: `bg-blue-100 border-blue-300`
- 미선택: `bg-blue-50 border-blue-200`
- 더 부드러운 시각적 피드백
#### 1.6 패널 높이 동기화
- 왼쪽(컬럼 목록)과 오른쪽(설정 패널) 동일한 `max-h-[350px]` 적용
- `overflow-y-auto`로 스크롤 처리
- `items-stretch`로 양쪽 패널 높이 동기화
### 2. 컬럼 변경 기능
#### 2.1 인라인 컬럼 편집
- 사용중인 필드(파란색 배경)를 클릭하면 우측에 "컬럼 설정" 패널 표시
- 패널 정보:
- **화면 필드**: 컬럼 한글명 표시 (예: "거래처 코드")
- **현재 컬럼**: 영문 컬럼명 표시 (예: `customer_code`)
- **컬럼 변경**: 드롭다운으로 다른 컬럼 선택
- 검색 기능으로 컬럼 빠르게 찾기
#### 2.2 실시간 반영
- 컬럼 변경 후 **페이지 새로고침 없이** 실시간 반영
- `onRefresh` 콜백으로 데이터 리로드 + iframe 새로고침
- 더 빠른 사용자 경험
#### 2.3 변경사항 저장
- `screenApi.saveLayout()` 사용하여 **화면 디자이너와 동일한 테이블에 저장**
- 저장 위치:
- `componentConfig.leftPanel.columns` (분할 패널)
- `componentConfig.rightPanel.columns` (분할 패널)
- `usedColumns` 배열
- `bindField` 필드
- `fieldMapping` 배열
### 3. 필드 추가/제거 기능 (신규)
#### 3.1 필드 추가
- 비필드 컬럼(회색/흰색 배경) 클릭
- "컬럼 설정" 패널에 컬럼 정보 표시
- **"필드로 추가"** 버튼 클릭 → 해당 컬럼이 화면 필드로 추가됨
- 버튼 스타일: `text-blue-600 border-blue-300 hover:bg-blue-50` (파란색 테두리)
#### 3.2 필드 제거
- 기존 필드(파란색 배경) 클릭
- "컬럼 설정" 패널에 필드 정보 표시
- **"필드에서 제거"** 버튼 클릭 → 해당 필드가 화면에서 제거됨
- 버튼 스타일: `text-red-600 border-red-300 hover:bg-red-50` (빨간색 테두리)
#### 3.3 저장 로직
```typescript
// 필드 추가: 배열에 새 컬럼 추가
if (isAddingField) {
return {
...comp,
componentConfig: {
...comp.componentConfig,
leftPanel: {
...comp.componentConfig.leftPanel,
columns: [...leftColumns, { name: newColumn, columnName: newColumn }],
},
},
};
}
// 필드 제거: 배열에서 해당 컬럼 제거
if (isRemovingField) {
const filteredColumns = leftColumns.filter((_, i) => i !== columnIdx);
return {
...comp,
componentConfig: {
...comp.componentConfig,
leftPanel: {
...comp.componentConfig.leftPanel,
columns: filteredColumns,
},
},
};
}
```
#### 3.4 적용 범위
- 메인 테이블 아코디언: 필드 추가/제거 가능
- 필터 테이블 아코디언: 필드 추가/제거 가능
- `usedColumns`, `componentConfig.usedColumns`, `componentConfig.columns`, `leftPanel.columns`, `rightPanel.columns` 모두 지원
### 4. 화면 프리뷰 상시 표시
#### 4.1 레이아웃 변경
- 기존: 탭으로 프리뷰 전환
- 개선: **모달 우측에 프리뷰 상시 표시**
- 모달 크기 확대 (1600px 최대 너비)
- 좌측 40% (탭 콘텐츠) / 우측 60% (프리뷰)
#### 4.2 줌/드래그/클릭 기능 (react-zoom-pan-pinch 라이브러리)
- **휠 스크롤**: 마우스 포인터 위치 기준 확대/축소 (20% ~ 300%)
- **드래그**: 마우스 왼쪽 버튼으로 화면 이동 (5px 이상 이동 시)
- **클릭**: iframe 내부 요소 정상 클릭 가능
- 버튼, 셀렉트박스, 체크박스, 테이블 행 클릭
- 인풋박스/텍스트박스 포커스 및 입력
- X버튼(닫기) 등 SVG 아이콘 버튼 클릭
#### 4.3 클릭 좌표 보정 시스템
- 줌 상태에서도 정확한 클릭 위치 계산
- `designWidth / rect.width` 비율로 좌표 변환
- 오버레이 방식으로 드래그와 클릭 분리 처리
### 5. 필드+조인 컬럼 스타일 개선
#### 5.1 다중 역할 컬럼 표시
- 컬럼이 **필드이면서 조인 키**인 경우:
- **파란색 배경** (필드 기준)
- **왼쪽에 주황색 세로 선** (`border-l-4 border-l-orange-500`)
- 배지: `조인` `필드` 동시 표시
- 컬럼이 **필드이면서 필터 키**인 경우:
- **파란색 배경** (필드 기준)
- **왼쪽에 보라색 세로 선** (`border-l-4 border-l-purple-400`)
- 배지: `필터` `필드` 동시 표시
#### 5.2 조인 컬럼도 필드로 인식
- `filterTableColumnMappings` 생성 시 조인 컬럼(`ft.joinColumnRefs`)도 포함
- 조인 테이블 데이터를 화면에서 보여주므로 필드로 간주
#### 5.3 컬럼 설정 패널 - 조인 정보 표시
- 조인 키 클릭 시 패널에 조인 정보 표시:
- **대상 테이블**: item_info (실제 참조 테이블)
- **연결 컬럼**: item_number (참조 컬럼)
### 6. 조인 관계 설정/수정 기능
#### 6.1 기능 설명
- 컬럼 설정 패널에서 **조인 관계 직접 수정** 가능
- **모든 컬럼에서 조인 설정 가능** (기존 조인 키가 아닌 컬럼도 포함)
- 테이블 타입 관리(`column_labels` 테이블)와 동일한 저장 위치 사용
#### 6.2 저장 테이블
```
column_labels 테이블:
├── reference_table (참조 테이블명)
├── reference_column (참조 컬럼 - 보통 PK)
└── display_column (화면에 표시할 컬럼)
```
#### 6.3 구현된 UI
1. **컬럼 클릭** → 컬럼 설정 패널 표시
2. **"조인" 섹션 확인**:
- 조인 설정 있음: "편집" 버튼
- 조인 설정 없음: "추가" 버튼
3. **드롭다운으로 설정** (모두 검색 가능):
- 대상 테이블: 전체 테이블 목록에서 검색/선택
- 연결 컬럼 (PK): 선택한 테이블의 컬럼 중 검색/선택
- 표시 컬럼: 화면에 표시할 컬럼 검색/선택
4. **저장 버튼**`column_labels` 테이블에 저장
5. **취소 버튼** → 편집 취소
#### 6.4 검색 가능한 드롭다운
- Popover + Command 컴포넌트 사용
- 실시간 텍스트 검색 지원
- 대상 테이블, 연결 컬럼, 표시 컬럼 모두 검색 가능
#### 6.5 API 연동
- **테이블 목록 조회**: `tableManagementApi.getTableList()`
- **컬럼 목록 조회**: `tableManagementApi.getColumnList(tableName)`
- **저장**: `tableManagementApi.updateColumnSettings(tableName, columnName, settings)`
#### 6.6 메인 테이블에도 조인 설정 적용
- 메인 테이블 아코디언에서도 조인 설정 가능
- 필터 테이블과 동일한 UI/기능 제공
#### 6.7 조인 데이터 소스 수정
- 기존 조인 키 클릭 시 `joinRef.refTable` 값을 사용
- 예: `품목 ID``item_info` (실제 참조 테이블)
- `mainTable` 대신 `joinRef.refTable` 사용으로 정확한 테이블 표시
### 7. 배지 순서 및 스타일
- **배지 순서**: `필터``조인``필드` (필드가 맨 뒤)
- **조인 배지**: 주황색 배경 (`bg-orange-200 text-orange-700`)
- **필터 배지**: 보라색 배경 (`bg-purple-200 text-purple-700`)
- **필드 배지**: 파란색 배경 (`bg-blue-500 text-white`)
### 8. 컴포넌트 통합 리팩토링
#### 8.1 `TableColumnAccordion` 통합 컴포넌트
- 기존 `MainTableAccordion``FilterTableAccordion`을 하나의 컴포넌트로 통합
- `tableType` prop으로 "main" 또는 "filter" 구분
- 코드 중복 제거 및 유지보수성 향상
#### 8.2 Props 구조
```typescript
interface TableColumnAccordionProps {
tableName: string;
tableLabel?: string;
tableType: "main" | "filter";
columnMappings?: ColumnMapping[];
onColumnChange?: (fieldLabel: string, oldColumn: string, newColumn: string) => void;
onColumnReorder?: (newOrder: string[]) => void;
onJoinSettingSaved?: () => void;
// 필터 테이블 전용 props
mainTable?: string;
filterKeyMapping?: FilterKeyMapping;
joinColumnRefs?: JoinColumnRef[];
}
```
#### 8.3 동적 테마 적용
- 메인 테이블: 파란색 테마 (`blue`)
- 필터 테이블: 보라색 테마 (`purple`)
- `themeColor`, `themeIcon`, `themeBadge` 변수로 동적 스타일 적용
### 9. 제어 관리 탭 (신규)
#### 9.1 개요
- 화면 설정 모달에 **"제어 관리"** 탭 추가
- 화면 디자이너의 버튼 제어 설정을 간편하게 관리
- 상세 설정은 화면 디자이너 링크 제공
#### 9.2 버튼 액션 설정
- 화면에 배치된 버튼 목록 표시
- 버튼별 액션 타입, 대상 화면, 플로우 연동 등 수정 가능
- **편집** 버튼 클릭 시 인라인 편집 모드 활성화
- **저장** 버튼 클릭 시 `screenApi.saveLayout()` 으로 저장
#### 9.3 지원 액션 타입
| 액션 | 설명 |
|------|------|
| `save` | 저장 |
| `delete` | 삭제 |
| `edit` | 편집 |
| `copy` | 복사 |
| `navigate` | 페이지 이동 |
| `modal` | 모달 열기 |
| `openModalWithData` | 데이터 전달 + 모달 |
| `openRelatedModal` | 연관 데이터 모달 |
| `transferData` | 데이터 전달 |
| `quickInsert` | 즉시 저장 |
| `control` | 제어 흐름 |
| `view_table_history` | 테이블 이력 |
| `excel_download` | 엑셀 다운로드 |
| `excel_upload` | 엑셀 업로드 |
#### 9.4 모달/네비게이션 화면 선택
- 액션 타입이 `modal`, `openModalWithData`, `openRelatedModal`인 경우:
- **모달 화면** 선택 가능
- 검색 가능한 드롭다운 (Combobox)
- **현재 그룹** 화면 우선 표시, **다른 그룹** 화면도 선택 가능
- 액션 타입이 `navigate`인 경우:
- **이동 화면** 선택 가능
- 동일하게 검색 가능한 드롭다운 제공
#### 9.5 다중 플로우 연동 지원 (신규)
- **한 버튼에 여러 플로우 연동 가능**
- 버튼별 플로우 목록을 세로 리스트 형식으로 표시
- 각 플로우별 **실행 타이밍** 개별 설정: `before` (버튼 실행 전), `after` (버튼 실행 후)
- 플로우 개별 제거 가능 (X 버튼)
- **플로우 추가** 버튼으로 새 플로우 연동 (검색 가능한 Combobox)
- 화면 디자이너의 `webTypeConfig.dataflowConfig.flowConfigs` 배열로 저장
#### 9.6 상시 편집 모드 (개선)
- 기존: "편집" 버튼 클릭 시에만 설정 변경 가능
- 개선: **상시 편집 가능** (별도 편집 버튼 없음)
- 변경사항이 있으면 "저장" 버튼 활성화
- 더 빠르고 직관적인 설정 변경 경험
#### 9.7 섹션 구분 개선 (UI 개선)
- **버튼 액션 설정** 섹션:
- 파란색 테마 (`border-blue-200 bg-blue-50/30`)
- 헤더: `bg-blue-100/50 text-blue-900`
- 아이콘: MousePointer (파란색)
- **플로우 연동 현황** 섹션:
- 보라색 테마 (`border-purple-200 bg-purple-50/30`)
- 헤더: `bg-purple-100/50 text-purple-900`
- 아이콘: Workflow (보라색)
- 시각적으로 명확한 섹션 구분
#### 9.8 플로우 연동 현황 표시 개선
- 플로우 이름: **일반 텍스트**로 표시 (배지 아님)
- 연동된 버튼: 배지로 표시
- 미연동 플로우: **보라색 "미연동" 배지**로 표시
- 플로우 관리 바로가기 버튼 제공
#### 9.9 다중 플로우 저장 로직
```typescript
// 버튼 설정 저장 시 다중 플로우 처리
if (values.linkedFlows !== undefined) {
if (values.linkedFlows && values.linkedFlows.length > 0) {
comp.webTypeConfig.enableDataflowControl = true;
comp.webTypeConfig.dataflowConfig = {
controlMode: "flow",
// 다중 플로우 저장
flowConfigs: values.linkedFlows.map((lf: any) => ({
flowId: lf.id,
flowName: lf.name,
executionTiming: lf.timing || "after",
})),
// 레거시 호환 - 첫 번째 플로우를 단일 flowConfig로도 저장
flowConfig: {
flowId: values.linkedFlows[0].id,
flowName: values.linkedFlows[0].name,
executionTiming: values.linkedFlows[0].timing || "after",
},
};
} else {
// 플로우 연동 해제 (빈 배열)
comp.webTypeConfig.enableDataflowControl = false;
delete comp.webTypeConfig.dataflowConfig;
}
}
```
#### 9.10 화면 목록 조회 로직
```typescript
// 1. 전체 화면 조회 (모든 화면의 ID→이름 맵핑)
const allScreensResponse = await screenApi.getScreens({ size: 1000 });
const allScreensMap = new Map<number, string>();
allScreensResponse.data.forEach((s: any) => {
const sid = Number(s.screenId || s.screen_id || s.id);
const sname = s.screenName || s.screen_name || s.name || `화면 ${sid}`;
if (!isNaN(sid)) allScreensMap.set(sid, sname);
});
// 2. 그룹 내 화면 조회
if (groupId) {
const groupResponse = await getScreenGroup(groupId);
if (groupResponse.success && groupResponse.data?.screens) {
groupScreenIds = groupResponse.data.screens.map((s: any) =>
Number(s.screen_id || s.screenId || s.id)
).filter(id => !isNaN(id));
}
}
// 3. 화면 목록 구성 (그룹 내 우선, 전체 포함)
groupScreenIds.forEach(sid => {
screenListResult.push({ id: sid, name: allScreensMap.get(sid), inGroup: true });
});
allScreensMap.forEach((name, id) => {
if (!groupScreenIds.includes(id)) {
screenListResult.push({ id, name, inGroup: false });
}
});
```
### 10. 드래그 앤 드롭 컬럼 순서 변경
#### 10.1 기능 설명
- 사용 중인 컬럼(필드)을 드래그하여 순서 변경 가능
- 드래그 중에는 시각적으로만 순서 변경, **드롭 시에만 저장**
- 드래그 취소(영역 밖으로 나간 경우) 시 원래 순서로 복원
#### 10.2 드래그 상태 관리
```typescript
// 드래그 상태
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
const [localColumnOrder, setLocalColumnOrder] = useState<string[] | null>(null);
```
#### 10.3 드래그 핸들러
```typescript
// 드래그 시작: 현재 순서를 로컬 상태로 저장
const handleDragStart = (e: React.DragEvent, index: number) => {
setDraggedIndex(index);
const usedColumns = sortedColumns.filter(col => columnMappingMap.has(col.columnName.toLowerCase()));
setLocalColumnOrder(usedColumns.map(col => col.columnName));
};
// 드래그 중: 로컬 순서만 변경 (저장하지 않음)
const handleDragOver = (e: React.DragEvent, hoverIndex: number) => {
if (draggedIndex === null || draggedIndex === hoverIndex || !localColumnOrder) return;
const newOrder = [...localColumnOrder];
const draggedItem = newOrder[draggedIndex];
newOrder.splice(draggedIndex, 1);
newOrder.splice(hoverIndex, 0, draggedItem);
setDraggedIndex(hoverIndex);
setLocalColumnOrder(newOrder);
};
// 드롭: 최종 순서로 저장
const handleDrop = (e: React.DragEvent) => {
if (localColumnOrder && onColumnReorder) {
onColumnReorder(localColumnOrder);
}
setDraggedIndex(null);
setLocalColumnOrder(null);
};
// 드래그 취소
const handleDragEnd = () => {
setDraggedIndex(null);
setLocalColumnOrder(null);
};
```
#### 10.4 시각적 피드백
- 드래그 가능한 컬럼: `cursor-grab active:cursor-grabbing`
- 드래그 중인 컬럼: `opacity-50 scale-95`
- 드래그 중 실시간 순서 변경 표시
#### 10.5 저장 로직 (`handleColumnReorder`)
```typescript
const handleColumnReorder = async (tableType: "main" | "filter", newOrder: string[]) => {
const currentLayout = await screenApi.getLayout(screenId);
const updatedComponents = currentLayout.components.map((comp: any) => {
// leftPanel.columns 순서 변경
if (comp.componentConfig?.leftPanel?.columns) {
const leftColumns = comp.componentConfig.leftPanel.columns;
const reorderedColumns = newOrder.map(colName =>
leftColumns.find((col: any) => col.name?.toLowerCase() === colName.toLowerCase())
).filter(Boolean);
const remainingColumns = leftColumns.filter((col: any) =>
!newOrder.some(n => n.toLowerCase() === col.name?.toLowerCase())
);
return {
...comp,
componentConfig: {
...comp.componentConfig,
leftPanel: {
...comp.componentConfig.leftPanel,
columns: [...reorderedColumns, ...remainingColumns],
},
},
};
}
return comp;
});
await screenApi.saveLayout(screenId, { ...currentLayout, components: updatedComponents });
onRefresh?.();
};
```
#### 10.6 지원 범위
- 메인 테이블: `onColumnReorder={(newOrder) => handleColumnReorder("main", newOrder)}`
- 필터 테이블: `onColumnReorder={(newOrder) => handleColumnReorder("filter", newOrder)}`
- 지원 배열:
- `componentConfig.leftPanel.columns`
- `componentConfig.rightPanel.columns`
- `componentConfig.usedColumns`
- `componentConfig.columns`
### 11. FlowEditor 임베드 (신규)
#### 11.1 개요
- 제어 관리 탭에서 **플로우 빠른 생성** 시 전체 FlowEditor를 모달로 임베드
- 골격 생성이 아닌 **완전한 플로우 생성** 가능
- 저장 시 자동으로 버튼에 연동
#### 11.2 FlowEditor 컴포넌트 수정
```typescript
interface FlowEditorProps {
initialFlowId?: number | null;
onSaveComplete?: (flowId: number, flowName: string) => void; // 저장 완료 콜백
embedded?: boolean; // 임베디드 모드
}
```
#### 11.3 FlowToolbar 수정
- 저장 완료 시 `onSaveComplete` 콜백 호출
- 기존 postMessage 로직 대체
#### 11.4 사용 방법
1. "새 플로우" 버튼 클릭
2. 전체화면 모달에서 FlowEditor 열림
3. 플로우 완전 구성 (테이블, 필드 매핑, 조건 등)
4. 저장 시 자동으로:
- 플로우 생성
- 버튼에 연동 (버튼에서 시작한 경우)
- 플로우 목록 새로고침
### 12. 화면 캔버스 크기 자동 조절 (신규)
#### 12.1 문제
- 기존: iframe 크기가 고정되어 화면 내용이 잘림
- 특히 폼 화면에서 인풋 필드, 저장 버튼 등이 보이지 않음
#### 12.2 해결
- 백엔드: 컴포넌트 최대 좌표 기준으로 `canvasWidth`, `canvasHeight` 계산
- 프론트엔드: `PreviewTab`에 캔버스 크기 전달, 여유 마진 추가
#### 12.3 구현
```typescript
// 백엔드 (screenGroupController.ts)
const rightEdge = (row.position_x || 0) + (row.width || 100);
const bottomEdge = (row.position_y || 0) + (row.height || 30);
if (rightEdge > summaryMap[screenId].canvasWidth) {
summaryMap[screenId].canvasWidth = rightEdge;
}
if (bottomEdge > summaryMap[screenId].canvasHeight) {
summaryMap[screenId].canvasHeight = bottomEdge;
}
// 프론트엔드 (ScreenSettingModal.tsx)
const designWidth = Math.max((canvasWidth || 400) + 120, 500);
const designHeight = Math.max((canvasHeight || 400) + 250, 650);
```
### 13. 인풋 필드 인식 개선 (신규)
#### 13.1 문제
- 폼 화면의 인풋 필드가 "필드"로 인식되지 않음
- 필드 매핑 0개로 표시
#### 13.2 원인
- 백엔드 SQL 쿼리에서 `columnName` 속성을 추출하지 않음
- 프론트엔드에서 `bindField`를 필드 카운트에 포함하지 않음
#### 13.3 해결
```sql
-- 백엔드 SQL 수정
COALESCE(
properties->'componentConfig'->>'bindField',
properties->>'bindField',
properties->'componentConfig'->>'field',
properties->>'field',
properties->>'columnName' -- 추가됨
) as bind_field,
```
```typescript
// 프론트엔드 필드 카운트 수정
layoutItems.forEach((item) => {
if (item.usedColumns) {
item.usedColumns.forEach((col) => layoutColumnsSet.add(col));
}
if (item.bindField) {
layoutColumnsSet.add(item.bindField); // 추가됨
}
});
```
### 14. 폼 화면 필드 추가/제거 (신규)
#### 14.1 기존 그리드 vs 폼 화면
- **그리드 화면**: `leftPanel.columns`, `rightPanel.columns` 배열에서 컬럼 추가/제거
- **폼 화면**: `text-input` 등 컴포넌트 자체를 추가/제거해야 함
#### 14.2 구현
```typescript
// 필드 추가: 새 text-input 컴포넌트 생성
if (isAddingField && !columnChanged) {
const newFormComponent: LayoutItem = {
id: `comp-${Date.now()}`,
componentType: "text-input",
label: newColumn,
bindField: newColumn,
position_x: newComponentX,
position_y: newComponentY,
width: 300,
height: 30,
// ...
};
updatedComponents.push(newFormComponent);
}
// 필드 제거: bindField가 일치하는 컴포넌트 삭제
if (isRemovingField && !columnChanged) {
updatedComponents = updatedComponents.filter((comp: any) =>
comp.bindField?.toLowerCase() !== oldColumn.toLowerCase()
);
}
```
### 15. 화면 디자이너 모달 통합 (신규)
#### 15.1 개요
- 기존: "디자이너" 버튼 클릭 시 새 탭/창에서 열림
- 변경: 전체화면 Dialog 내부에 ScreenDesigner 임베드
#### 15.2 장점
- 화면 설정 모달을 닫지 않고 디자이너 사용
- 디자이너 닫을 때 자동 새로고침
#### 15.3 구현
```tsx
<Dialog open={showDesignerModal} onOpenChange={setShowDesignerModal}>
<DialogContent className="max-w-[98vw] h-[95vh] p-0 overflow-hidden">
<ScreenDesigner
selectedScreen={{...}}
onBackToList={async () => {
setShowDesignerModal(false);
await loadData();
setIframeKey(prev => prev + 1);
}}
/>
</DialogContent>
</Dialog>
```
### 16. 그룹 내 화면 전환 셀렉트박스 (신규)
#### 16.1 개요
- 그룹에 여러 화면이 있을 때 모달 내에서 화면 전환 가능
- 모달을 닫지 않고도 다른 화면 설정 가능
#### 16.2 구현
```typescript
// 그룹 내 화면 목록 로드
const loadGroupScreens = useCallback(async () => {
if (!groupId) return;
const groupRes = await getScreenGroup(groupId);
if (groupRes.success && groupRes.data) {
const screens = groupRes.data.screens || [];
screens.sort((a, b) => (a.display_order || 0) - (b.display_order || 0));
setGroupScreens(screens);
}
}, [groupId]);
// 화면 선택 변경 핸들러
const handleScreenChange = useCallback(async (newScreenId: number) => {
const selectedScreen = groupScreens.find(s => s.screen_id === newScreenId);
if (!selectedScreen) return;
setCurrentScreenId(newScreenId);
setCurrentScreenName(selectedScreen.screen_name);
setCurrentMainTable(selectedScreen.table_name);
setIframeKey(prev => prev + 1);
}, [groupScreens]);
```
#### 16.3 UI
- 그룹 내 화면이 2개 이상: 제목 옆에 셀렉트박스 표시
- 그룹 내 화면이 1개: 기존처럼 텍스트 표시
- 화면 역할(screen_role)도 함께 표시
## 기술 스택
### 신규 의존성
```bash
npm install react-zoom-pan-pinch
```
### 사용된 컴포넌트
- `TransformWrapper`, `TransformComponent` - 줌/드래그 기능
- `Accordion`, `AccordionContent`, `AccordionItem`, `AccordionTrigger` - 아코디언 UI
- `Popover`, `PopoverTrigger`, `PopoverContent` - 드롭다운 컨테이너
- `Command`, `CommandInput`, `CommandList`, `CommandItem`, `CommandEmpty` - 검색 가능한 선택 UI
- `tableManagementApi.getColumnList()` - 테이블 컬럼 정보 조회
- `tableManagementApi.getTableList()` - 테이블 목록 조회
- `tableManagementApi.updateColumnSettings()` - 조인 설정 저장
- `screenApi.saveLayout()` - 레이아웃 저장
- `screenApi.getLayout()` - 레이아웃 조회
### 핵심 로직
#### 컬럼 변경/추가/제거
```typescript
const handleColumnChange = async (fieldLabel: string, oldColumn: string, newColumn: string) => {
const isAddingField = fieldLabel === "__NEW_FIELD__";
const isRemovingField = newColumn === "__REMOVE_FIELD__";
const currentLayout = await screenApi.getLayout(screenId);
const updatedComponents = currentLayout.components.map((comp: any) => {
if (comp.componentConfig?.leftPanel?.columns) {
const leftColumns = comp.componentConfig.leftPanel.columns;
// 필드 추가
if (isAddingField) {
return {
...comp,
componentConfig: {
...comp.componentConfig,
leftPanel: {
...comp.componentConfig.leftPanel,
columns: [...leftColumns, { name: newColumn, columnName: newColumn }],
},
},
};
}
// 필드 제거
const columnIdx = leftColumns.findIndex((col: any) => ...);
if (columnIdx !== -1 && isRemovingField) {
return {
...comp,
componentConfig: {
...comp.componentConfig,
leftPanel: {
...comp.componentConfig.leftPanel,
columns: leftColumns.filter((_, i) => i !== columnIdx),
},
},
};
}
// 컬럼 변경
if (columnIdx !== -1) {
return {
...comp,
componentConfig: {
...comp.componentConfig,
leftPanel: {
...comp.componentConfig.leftPanel,
columns: leftColumns.map((col, i) =>
i === columnIdx ? { ...col, name: newColumn } : col
),
},
},
};
}
}
return comp;
});
await screenApi.saveLayout(screenId, { ...currentLayout, components: updatedComponents });
onRefresh?.();
};
```
#### 조인 설정 편집기 (JoinSettingEditor)
```tsx
<JoinSettingEditor
editingJoin={editingJoin}
setEditingJoin={setEditingJoin}
allTables={allTables}
refTableColumns={refTableColumns}
loadingRefColumns={loadingRefColumns}
savingJoinSetting={savingJoinSetting}
loadRefTableColumns={loadRefTableColumns}
handleSaveJoinSetting={handleSaveJoinSetting}
/>
```
## 파일 변경 목록
| 파일 | 변경 내용 |
|------|----------|
| `frontend/components/screen/ScreenSettingModal.tsx` | 전체 UI 개선, 줌/드래그 기능, 컬럼 변경/추가/제거 기능, 조인 설정 기능, 필드 매핑 통합, 실시간 반영, 제어 관리 탭 추가, 버튼 액션 설정, 플로우 연동, FlowEditor 임베드, ScreenDesigner 모달 임베드, 그룹 내 화면 전환 셀렉트박스 |
| `frontend/components/screen/ScreenRelationFlow.tsx` | `filterKeyMapping`, `joinColumnRefs` 데이터 전달 |
| `frontend/components/dataflow/node-editor/FlowEditor.tsx` | `onSaveComplete` 콜백, `embedded` 모드 props 추가 |
| `frontend/components/dataflow/node-editor/FlowToolbar.tsx` | 저장 완료 시 `onSaveComplete` 콜백 호출 |
| `frontend/lib/api/entityJoin.ts` | `companyCodeOverride` 파라미터 추가 |
| `frontend/lib/api/screen.ts` | `saveLayout`, `getLayout` API 사용 |
| `frontend/lib/api/tableManagement.ts` | `getTableList`, `getColumnList`, `updateColumnSettings` API |
| `frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx` | `companyCode` prop 추가 |
| `frontend/lib/registry/components/button/ButtonPrimaryComponent.tsx` | `webTypeConfig.backgroundColor/textColor` 지원 추가 |
| `backend-node/src/controllers/entityJoinController.ts` | `companyCodeOverride` 처리 로직 추가 |
| `backend-node/src/controllers/screenGroupController.ts` | `bind_field` 쿼리에 `columnName` 추가, `canvasWidth`/`canvasHeight` 계산 |
## 사용 방법
### 화면 설정 모달 열기
1. 화면 관리 페이지에서 화면 그룹 선택
2. 화면 노드 우클릭 → 컨텍스트 메뉴 표시
3. "화면 설정" 선택 → 모달 열림
4. 좌측 탭에서 정보 확인/수정, 우측에서 실시간 프리뷰
### 프리뷰 영역 조작
- **휠 스크롤**: 확대/축소 (5% 단위)
- **마우스 드래그**: 화면 이동 (5px 이상 움직여야 드래그로 인식)
- **짧은 클릭**: iframe 내부 요소 클릭
### 컬럼 변경
1. 메인/필터 테이블 아코디언 펼치기
2. 파란색 배경의 "필드" 컬럼 클릭
3. 우측 "컬럼 설정" 패널 확인
4. "컬럼 변경" 드롭다운에서 새 컬럼 선택
5. **실시간 반영** (페이지 새로고침 없음)
### 필드 추가
1. 메인/필터 테이블 아코디언 펼치기
2. 회색/흰색 배경의 비필드 컬럼 클릭
3. 우측 패널에서 **"필드로 추가"** 버튼 클릭
4. 해당 컬럼이 화면 필드로 추가됨
### 필드 제거
1. 메인/필터 테이블 아코디언 펼치기
2. 파란색 배경의 필드 컬럼 클릭
3. 우측 패널에서 **"필드에서 제거"** 버튼 클릭
4. 해당 필드가 화면에서 제거됨
### 조인 설정 추가/편집
1. 메인/필터 테이블 아코디언 펼치기
2. 아무 컬럼 클릭 (조인 키가 아니어도 됨)
3. 우측 패널의 "조인" 섹션에서:
- 조인 없음: **"추가"** 버튼 클릭
- 조인 있음: **"편집"** 버튼 클릭
4. 대상 테이블 선택 (검색 가능)
5. 연결 컬럼 (PK) 선택 (검색 가능)
6. 표시 컬럼 선택 (검색 가능)
7. **"저장"** 버튼 클릭
### 컬럼 순서 변경 (드래그 앤 드롭)
1. 메인/필터 테이블 아코디언 펼치기
2. 파란색 배경의 "필드" 컬럼을 드래그 시작
3. 원하는 위치로 드래그하여 이동 (실시간으로 순서 변경 표시)
4. 마우스를 놓으면 (드롭) 순서가 저장됨
5. 드래그 취소하려면 컬럼 영역 밖으로 드래그
**참고:**
- 사용 중인 필드만 드래그 가능 (파란색 배경)
- 미사용 컬럼은 드래그 불가
- 드래그 중에는 저장되지 않고, 드롭 시에만 저장됨
### 그룹 내 화면 전환
1. 화면 설정 모달을 열면 제목 옆에 **셀렉트박스** 표시 (그룹 내 화면이 2개 이상일 때)
2. 셀렉트박스 클릭하여 같은 그룹의 다른 화면 선택
3. 선택 즉시:
- 화면 데이터 자동 리로드
- 프리뷰 iframe 자동 새로고침
4. 화면 역할(list, form, modal 등)도 함께 표시
**참고:**
- 그룹 내 화면이 1개뿐이면 기존처럼 텍스트로 표시
- 모달을 닫지 않고도 여러 화면 설정 가능
### 플로우 빠른 생성
1. 제어 관리 탭의 "플로우 연동 현황"에서 **"새 플로우"** 버튼 클릭
2. 또는 버튼별 플로우 선택에서 **"새 플로우 생성"** 옵션 클릭
3. 전체화면 모달에서 FlowEditor가 열림
4. 플로우를 완전하게 구성 (테이블 선택, 필드 매핑, 조건 등)
5. 저장 시 자동으로 해당 버튼에 연동
**참고:**
- 버튼에서 생성 시: 해당 버튼에 자동 연동
- 헤더에서 생성 시: 연동 없이 플로우만 생성
- 모달을 닫으면 플로우 목록 자동 새로고침
### 화면 디자이너 열기
1. 화면 설정 모달의 제목 옆 **외부 링크 아이콘** 클릭
2. 전체화면 모달에서 ScreenDesigner가 열림
3. 컴포넌트 배치, 속성 변경 등 디자인 작업 수행
4. 모달 닫을 때 자동으로:
- 화면 데이터 리로드
- 프리뷰 iframe 새로고침
---
## 향후 개선 계획 (Phase 2)
### 컨셉: "손쉬운 사용"
> 화면 디자이너에 들어가지 않고도 **자잘한 설정을 빠르게** 처리
> **화면 테스트 → 설정 수정 → 다시 테스트** 사이클을 최소화
### 1순위: 버튼 이름 변경 + 색상 변경 ✅ 완료
| 항목 | 내용 |
|------|------|
| **필요성** | 오타 수정, 문구 변경, 버튼 색상 변경 시 화면 디자이너 진입 필요 |
| **현재 상태** | ✅ **구현 완료** |
| **구현 내용** | |
#### 버튼 이름 변경
- 버튼 이름을 직접 입력 필드에서 수정 가능
- 실시간 버튼 프리뷰 제공
- 저장 위치: `componentConfig.text`, `comp.label`, `comp.title` (레거시 호환)
#### 버튼 색상 변경
- 배경색 + 글자색 컬러 피커 제공
- **프리셋 색상 버튼**: 파랑, 초록, 빨강, 회색, 흰색
- 실시간 버튼 프리뷰에 색상 반영
- 저장 위치:
- 배경색: `componentConfig.backgroundColor`, `style.backgroundColor`
- 글자색: `componentConfig.textColor`, `style.color`, `style.labelColor`
### 1순위: 컬럼 라벨(표시명) 변경
| 항목 | 내용 |
|------|------|
| **필요성** | "거래처코드" → "고객코드" 같은 헤더 변경 |
| **현재 상태** | 컬럼명만 표시, 수정 불가 |
| **구현 방향** | 컬럼 설정 패널에 "표시명" Input 추가 |
| **주의사항** | 라벨만 변경, 실제 DB 컬럼명(`columnName`)은 변경 불가 (company_code 안전) |
| **예상 난이도** | 중간 (1시간) |
### 2순위: 확인 메시지 설정 ✅ 완료
| 항목 | 내용 |
|------|------|
| **필요성** | "삭제하시겠습니까?" 같은 확인 다이얼로그 메시지 수정 |
| **현재 상태** | ✅ **구현 완료** |
| **구현 내용** | 저장/삭제 버튼에 확인 메시지 Input 필드 추가, 화면 디자이너와 동일하게 `confirmMessage` 저장 |
### 추가 요청: 플로우 빠른 생성 ✅ 완료
| 항목 | 내용 |
|------|------|
| **필요성** | 시스템관리 > 제어관리에 나가지 않고 바로 플로우 생성 |
| **현재 상태** | ✅ **구현 완료** |
| **구현 내용** | FlowEditor를 전체화면 모달로 임베드, 저장 시 자동 버튼 연동 |
| **현재 상태** | 플로우 선택/연동만 가능, 생성 불가 |
| **구현 방안** | |
#### 방안 A: 모달 내 간이 플로우 생성
- 장점: 화면 이탈 없음
- 단점: FlowEditor 축소판 개발 필요 (큰 작업)
#### 방안 B: iframe으로 FlowEditor 임베드
- 장점: 기존 FlowEditor 재사용
- 단점: 화면 공간 부족, 상태 동기화 복잡
#### 방안 C: 새 창/탭으로 FlowEditor 열기 + 콜백
- 장점: 전체 기능 사용 가능, 개발 비용 최소
- 단점: 화면 전환 필요
#### 방안 D: 간이 플로우 템플릿 선택 ⭐ 권장
- 장점: 빠른 설정, 사용자 친화적
- 단점: 커스터마이징 제한
**템플릿 종류 예시:**
- 데이터 저장 (INSERT)
- 데이터 수정 (UPDATE)
- 이력 저장 (INSERT to 이력 테이블)
- 외부 API 호출
### 미포함 항목 (상세 설정 권장)
- 버튼 표시/숨김
- 버튼 색상 변경
- 컬럼 너비 조정
- 필터 라벨 변경
- 화면 이름/설명 변경
---
## 완료일
2026-01-14
## 변경 이력
- 2026-01-12: 최초 작성 (줌/드래그/클릭, company_code 전달)
- 2026-01-12: 컬럼 변경 기능 추가, 필드 매핑 통합, UI 개선 (1열 레이아웃, 배지 변경)
- 2026-01-12: 실시간 반영 구현 (reload 제거), 레이아웃 순서 변경, 스타일 개선
- 2026-01-12: 필드+조인 컬럼 스타일 개선 (파란배경 + 왼쪽 주황선), 조인 정보 패널 표시
- 2026-01-12: 조인 관계 설정/수정 기능 구현 완료 (column_labels 테이블 저장)
- 2026-01-13: 필드 추가/제거 기능 구현
- 2026-01-13: 검색 가능한 조인 설정 드롭다운 (Command 컴포넌트)
- 2026-01-13: 모든 컬럼에서 조인 설정 가능 (범용성 패치)
- 2026-01-13: 메인 테이블에도 조인 설정 기능 추가
- 2026-01-13: 조인 라인 색상 주황색으로 변경 (`border-l-orange-500`)
- 2026-01-13: 조인 데이터 소스 수정 (`joinRef.refTable` 사용)
- 2026-01-13: 패널 높이 동기화 (`max-h-[350px]`, `items-stretch`)
- 2026-01-13: `MainTableAccordion``FilterTableAccordion``TableColumnAccordion`으로 통합
- 2026-01-13: 드래그 앤 드롭 컬럼 순서 변경 기능 구현
- 2026-01-13: 드래그 중에는 로컬 상태만 변경, 드롭 시에만 저장하도록 최적화
- 2026-01-13: "제어 관리" 탭 신규 추가 (버튼 액션 설정, 플로우 연동)
- 2026-01-13: 버튼별 플로우 연동 및 실행 타이밍 설정 기능
- 2026-01-13: 모달/네비게이션 화면 선택 시 검색 가능한 Combobox 적용
- 2026-01-13: 화면 목록 조회 개선 (전체 화면 조회 후 그룹별 분류)
- 2026-01-13: 외부 연동 섹션 제거 (미사용)
- 2026-01-13: 플로우 연동 섹션 추가 (화면에 연동된 전체 플로우 목록)
- 2026-01-13: **다중 플로우 지원** - 한 버튼에 여러 플로우 연동 가능 (`flowConfigs` 배열)
- 2026-01-13: **상시 편집 모드** - "편집" 버튼 제거, 상시 설정 변경 가능
- 2026-01-13: **섹션 구분 개선** - 버튼 액션(파란색) / 플로우 연동(보라색) 테마 분리
- 2026-01-13: **플로우 표시 방식 개선** - 플로우명은 텍스트, 미연동은 보라색 배지
- 2026-01-13: **플로우 타이밍 개별 설정** - 각 플로우별 실행 전/후 설정 가능
- 2026-01-13: **향후 개선 계획 문서화** - 버튼 이름 변경, 컬럼 라벨 변경, 확인 메시지 설정, 플로우 빠른 생성
- 2026-01-13: **버튼 이름 변경 기능 구현** - `<span>``<Input>`, `componentConfig.text` 저장
- 2026-01-13: **버튼 색상 변경 기능 추가** - 배경색/글자색 컬러 피커, 프리셋 색상 버튼, 실시간 프리뷰
- 2026-01-13: **버튼 색상 저장 위치 수정** - `webTypeConfig.backgroundColor/textColor`에 저장 (OptimizedButtonComponent와 일치)
- 2026-01-14: **버튼 색상 렌더링 수정** - `ButtonPrimaryComponent.tsx`에서 `webTypeConfig.backgroundColor/textColor` 지원 추가
- 2026-01-14: **확인 메시지 설정 기능 추가** - 화면 디자이너와 동일하게 `confirmMessage` 필드 사용, Input으로 메시지 설정
- 2026-01-14: **확인 메시지 save/delete 전용** - `save`/`delete` 액션에서만 확인 메시지 필드 표시, 다른 액션 타입으로 변경 시 confirmMessage 자동 제거
- 2026-01-14: **플로우 빠른 생성 기능 구현** - 제어 플로우 에디터와 동일한 형식으로 플로우 **골격** 생성
- 플로우 연동 현황 헤더에 "빠른 생성" 버튼 추가
- 버튼별 플로우 추가 드롭다운에도 "새 플로우 빠른 생성" 옵션 추가
- 생성 후 자동 연동 옵션 (선택한 버튼에 자동 연결)
- 테이블 선택 또는 직접 입력, 액션 타입 선택 (INSERT/UPDATE/DELETE)
- **중요**: 빠른 생성은 기본 구조만 생성, 필드 매핑/WHERE 조건은 제어 관리에서 추가 설정 필요
- "생성만" / "생성 후 편집" 버튼으로 워크플로우 선택 가능
- 경고 안내 UI 추가 (추가 설정 필요 안내)
- 2026-01-14: **플로우 빠른 생성 → FlowEditor 임베드 방식으로 변경**
- 골격 생성 대신 **전체 FlowEditor를 전체화면 모달로 임베드**
- 플로우 생성 후 자동으로 버튼에 연동
- `FlowEditor` 컴포넌트에 `onSaveComplete` 콜백과 `embedded` 모드 추가
- `FlowToolbar`에서 저장 완료 시 콜백 호출
- 2026-01-14: **인풋 필드 인식 개선**
- 백엔드 `getMultipleScreenLayoutSummary` SQL 쿼리에 `properties->>'columnName'` 추가
- `bindField`, `componentConfig.field`, `componentConfig.valueField``usedColumns`에 포함
- 프론트엔드 `stats` 계산 시 `item.bindField``layoutColumnsSet`에 추가
- `TableColumnAccordion``usedFields` props 전달하여 필드 배지 정확히 표시
- 2026-01-14: **화면 캔버스 크기 자동 조절**
- 백엔드에서 `canvasWidth`, `canvasHeight` 계산 (컴포넌트 최대 좌표 기준)
- `PreviewTab`에서 `canvasWidth`, `canvasHeight` props 수신
- 여유 마진 추가: 가로 +120px, 세로 +250px (패딩, 헤더, 하단 요소 고려)
- 최소 크기 보장 (가로 500px, 세로 650px)
- 2026-01-14: **폼 화면 필드 추가/제거 기능**
- `handleColumnChange`에서 `text-input` 등 폼 컴포넌트 처리 로직 추가
- 필드 추가 시: 마지막 컴포넌트 아래에 새 `text-input` 컴포넌트 자동 배치
- 필드 제거 시: `bindField`가 일치하는 컴포넌트 삭제
- 기존 그리드 컬럼 변경 로직과 통합
- 2026-01-14: **화면 디자이너 모달 통합**
- 기존: "디자이너" 버튼 클릭 시 새 탭/창에서 열림
- 변경: **전체화면 Dialog 내부에 ScreenDesigner 임베드**
- `showDesignerModal` 상태로 모달 제어
- 디자이너 닫을 때 `loadData()` + `setIframeKey()` 호출하여 자동 새로고침
- 2026-01-14: **그룹 내 화면 전환 기능 (셀렉트박스)**
- `groupId`가 있으면 `getScreenGroup(groupId)`로 그룹 내 화면 목록 조회
- 그룹 내 화면이 2개 이상일 때 제목 옆에 **셀렉트박스** 표시
- 화면 선택 시:
- `currentScreenId`, `currentScreenName`, `currentMainTable` 상태 업데이트
- 레이아웃 데이터 자동 리로드
- iframe 자동 새로고침
- 화면 역할(screen_role)도 함께 표시 (예: "거래처 목록 (list)")