# 화면관리 시스템 설계문서
## 1. 개요
### 1.1 목적
ERP 시스템에서 사용자가 직관적인 드래그앤드롭 인터페이스를 통해 동적으로 화면을 설계하고 관리할 수 있는 시스템
### 1.2 주요 기능
- 드래그앤드롭 기반 화면 설계
- 실시간 미리보기 및 속성 편집
- 다양한 위젯 타입 지원
- 데이터베이스 테이블/컬럼과의 연동
- 템플릿 기반 빠른 화면 생성
- 스타일 및 레이아웃 커스터마이징
## 2. 시스템 아키텍처
### 2.1 전체 구조
```
화면 편집기 (ScreenDesigner)
├── 템플릿 패널 (TemplatesPanel)
├── 테이블 패널 (TablesPanel)
├── 속성 편집 패널 (PropertiesPanel)
├── 스타일 편집 패널 (StyleEditor)
├── 상세설정 패널 (DetailSettingsPanel)
├── 격자 설정 패널 (GridPanel)
└── 캔버스 영역 (RealtimePreview)
할당된 화면 (InteractiveScreenViewer)
├── 라벨 렌더링
├── 위젯 렌더링
└── 폼 데이터 관리
```
### 2.2 데이터 흐름
```
사용자 입력 → 컴포넌트 상태 → 레이아웃 데이터 → API 저장/불러오기 → 할당된 화면 렌더링
```
## 3. 컴포넌트 구조
### 3.1 컴포넌트 타입
```typescript
type ComponentType = "container" | "widget" | "group" | "datatable";
interface BaseComponent {
id: string;
type: ComponentType;
position: { x: number; y: number; z?: number };
size: { width: number; height: number };
parentId?: string;
label?: string;
required?: boolean;
readonly?: boolean;
style?: ComponentStyle;
}
interface WidgetComponent extends BaseComponent {
type: "widget";
widgetType: WebType;
placeholder?: string;
columnName?: string;
webTypeConfig?: WebTypeConfig;
}
```
### 3.2 지원하는 웹 타입
- **텍스트 입력**: text, email, tel
- **숫자 입력**: number, decimal
- **날짜/시간**: date, datetime
- **선택**: select, dropdown, radio
- **체크박스**: checkbox, boolean
- **텍스트 영역**: textarea
- **파일**: file
- **코드**: code
- **엔티티**: entity
- **버튼**: button
## 4. 주요 기능 상세
### 4.1 드래그앤드롭 시스템
#### 템플릿 드래그
- 사전 정의된 템플릿을 캔버스에 드롭
- 컨테이너와 자식 컴포넌트 관계 자동 설정
- 격자 스냅 및 자동 크기 조정
#### 컬럼 드래그
- 데이터베이스 테이블의 컬럼을 위젯으로 변환
- 컬럼 타입에 따른 자동 웹타입 매핑
- 폼 컨테이너에 드롭 시 자동 부모-자식 관계 설정
#### 다중 컴포넌트 드래그
- Ctrl/Cmd + 클릭으로 다중 선택
- 선택된 모든 컴포넌트 동시 이동
- 실시간 미리보기 제공
### 4.2 속성 편집 시스템
#### 실시간 속성 편집 패턴
```typescript
// 로컬 상태 기반 즉시 반영
const [localInputs, setLocalInputs] = useState({
title: component.title || "",
placeholder: component.placeholder || "",
});
// 입력과 동시에 업데이트
{
const newValue = e.target.value;
setLocalInputs(prev => ({ ...prev, title: newValue }));
onUpdateProperty("title", newValue);
}}
/>
```
#### 컴포넌트별 개별 상태 관리
```typescript
// 동적 ID 기반 상태 관리
const [localColumnInputs, setLocalColumnInputs] = useState>({});
// 컴포넌트 변경 시 기존 값 보존하면서 새 항목만 추가
useEffect(() => {
setLocalColumnInputs((prev) => {
const newInputs = { ...prev };
component.columns?.forEach((col) => {
if (!(col.id in newInputs)) {
newInputs[col.id] = col.label;
}
});
return newInputs;
});
}, [component.columns]);
```
### 4.3 스타일 시스템
#### 스타일 적용 계층
1. **컴포넌트 기본 스타일**: `component.style`
2. **웹타입 설정 스타일**: `webTypeConfig`에서 정의
3. **라벨 스타일**: 별도 관리 (`labelColor`, `labelFontSize` 등)
#### 스타일 패널 구성
- **여백**: margin, padding, gap
- **테두리**: borderWidth, borderStyle, borderColor, borderRadius
- **배경**: backgroundColor, backgroundImage
- **텍스트**: color, fontSize, fontWeight, textAlign
### 4.4 템플릿 시스템
#### 데이터 테이블 템플릿
```typescript
{
id: "data-table",
name: "데이터 테이블",
category: "table",
components: [
{
id: "table-container",
type: "datatable",
searchFilters: [],
columns: [
{ id: "col1", label: "컬럼 1", visible: true, sortable: true },
{ id: "col2", label: "컬럼 2", visible: true, sortable: false }
],
pagination: { enabled: true, pageSize: 10 },
actions: {
create: { enabled: true, label: "추가" },
edit: { enabled: true, label: "수정" },
delete: { enabled: true, label: "삭제" }
}
}
]
}
```
#### 입력 폼 템플릿
```typescript
{
id: "input-form",
name: "입력 폼",
category: "form",
components: [
{
id: "form-container",
type: "container",
style: { backgroundColor: "#f8f9fa", borderRadius: "8px" },
children: [
{
id: "save-button",
type: "widget",
widgetType: "button",
parentId: "form-container",
position: { x: 0, y: 0 },
style: { position: "absolute", bottom: "24px", right: "104px" }
},
{
id: "cancel-button",
type: "widget",
widgetType: "button",
parentId: "form-container",
position: { x: 0, y: 0 },
style: { position: "absolute", bottom: "24px", right: "24px" }
}
]
}
]
}
```
#### 범용 버튼 템플릿
```typescript
{
id: "universal-button",
name: "버튼",
category: "button",
components: [
{
id: "button",
type: "widget",
widgetType: "button",
webTypeConfig: {
actionType: "save",
variant: "default",
size: "sm"
}
}
]
}
```
### 4.5 웹타입별 상세 설정
#### 버튼 설정 (ButtonConfigPanel)
```typescript
interface ButtonTypeConfig {
actionType: ButtonActionType;
variant: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
size: "default" | "sm" | "lg" | "icon";
icon?: string;
confirmMessage?: string;
popupTitle?: string;
popupContent?: string;
popupSize?: "sm" | "md" | "lg";
navigateUrl?: string;
navigateTarget?: "_self" | "_blank";
customAction?: string;
backgroundColor?: string;
textColor?: string;
borderColor?: string;
}
type ButtonActionType =
| "save"
| "cancel"
| "delete"
| "edit"
| "add"
| "search"
| "reset"
| "submit"
| "close"
| "popup"
| "navigate"
| "custom";
```
#### 텍스트 설정 (TextTypeConfig)
```typescript
interface TextTypeConfig {
format: "none" | "korean" | "english" | "alphanumeric" | "numeric" | "email" | "phone" | "url";
minLength?: number;
maxLength?: number;
pattern?: string;
placeholder?: string;
multiline?: boolean;
}
```
#### 숫자 설정 (NumberTypeConfig)
```typescript
interface NumberTypeConfig {
min?: number;
max?: number;
step?: number;
format?: "integer" | "decimal" | "currency" | "percentage";
decimalPlaces?: number;
thousandSeparator?: boolean;
}
```
#### 날짜 설정 (DateTypeConfig)
```typescript
interface DateTypeConfig {
format: "YYYY-MM-DD" | "YYYY-MM-DD HH:mm" | "YYYY-MM-DD HH:mm:ss";
showTime: boolean;
minDate?: string;
maxDate?: string;
defaultValue?: string;
}
```
#### 선택박스 설정 (SelectTypeConfig)
```typescript
interface SelectTypeConfig {
options: Array<{ label: string; value: string }>;
multiple?: boolean;
searchable?: boolean;
placeholder?: string;
}
```
#### 엔티티 설정 (EntityTypeConfig)
```typescript
interface EntityTypeConfig {
entityName: string;
displayField: string;
valueField: string;
filters: Array<{ field: string; operator: string; value: any }>;
multiple: boolean;
searchable: boolean;
allowClear: boolean;
placeholder?: string;
displayFormat?: string;
defaultValue?: any;
}
```
### 4.6 격자 시스템
#### 격자 설정
```typescript
interface GridSettings {
columns: number; // 격자 컬럼 수
gap: number; // 격자 간격 (px)
padding: number; // 캔버스 패딩 (px)
snapToGrid: boolean; // 격자 스냅 활성화
showGrid: boolean; // 격자 표시 여부
gridColor: string; // 격자 선 색상
gridOpacity: number; // 격자 투명도 (0-1)
}
```
#### 격자 스냅 로직
```typescript
const snapToGrid = (value: number, gridSize: number): number => {
return Math.round(value / gridSize) * gridSize;
};
const snapSizeToGrid = (size: number, gridSize: number): number => {
return Math.max(gridSize, Math.round(size / gridSize) * gridSize);
};
```
## 5. 렌더링 시스템
### 5.1 편집기 렌더링 (ScreenDesigner)
#### 컴포넌트 위치 계산
```typescript
// 절대 위치 래퍼 div
```
#### 자식 컴포넌트 상대 위치
```typescript
// 자식 컴포넌트는 부모 기준 상대 위치로 계산
const relativePosition = {
x: child.position.x - parent.position.x,
y: child.position.y - parent.position.y,
};
```
### 5.2 할당된 화면 렌더링 (InteractiveScreenViewer)
#### 라벨 외부 분리 렌더링
```typescript
// 라벨을 컴포넌트 외부에 별도 렌더링 (높이에 영향 없음)
{shouldShowLabel && (
{labelText}
{component.required && *}
)}
// 실제 컴포넌트 (라벨 높이에 영향받지 않음)
```
#### 스타일 적용 시스템
```typescript
const applyStyles = (element: React.ReactElement) => {
if (!comp.style) return element;
return React.cloneElement(element, {
style: {
...element.props.style, // 기존 스타일 유지
...comp.style, // 컴포넌트 스타일 적용
width: "100%", // 부모 컨테이너에 맞춤
height: "100%",
minHeight: "100%", // 강제 높이 적용
maxHeight: "100%",
boxSizing: "border-box",
},
});
};
```
#### 위젯별 렌더링
```typescript
switch (widgetType) {
case "text":
case "email":
case "tel":
return applyStyles(
);
case "button":
const config = widget.webTypeConfig as ButtonTypeConfig;
return (
);
case "entity":
return (
);
}
```
## 6. 상태 관리
### 6.1 레이아웃 상태
```typescript
interface LayoutData {
components: ComponentData[];
gridSettings: GridSettings;
}
const [layout, setLayout] = useState({
components: [],
gridSettings: defaultGridSettings,
});
```
### 6.2 선택 상태
```typescript
const [selectedComponent, setSelectedComponent] = useState(null);
const [selectedComponents, setSelectedComponents] = useState([]);
```
### 6.3 드래그 상태
```typescript
interface DragState {
isDragging: boolean;
draggedComponents: ComponentData[];
startPosition: { x: number; y: number };
currentPosition: { x: number; y: number };
}
```
### 6.4 패널 상태
```typescript
interface PanelState {
isOpen: boolean;
position: { x: number; y: number };
size: { width: number; height: number };
}
const [panelStates, setPanelStates] = useState>({
templates: { isOpen: true, position: { x: 0, y: 0 }, size: { width: 300, height: 400 } },
properties: { isOpen: false, position: { x: 0, y: 0 }, size: { width: 360, height: 600 } },
styles: { isOpen: false, position: { x: 0, y: 0 }, size: { width: 360, height: 400 } },
// ...
});
```
## 7. API 연동
### 7.1 화면 정보 API
```typescript
// 화면 목록 조회
GET /api/screens
Response: {
screens: Array<{
id: number;
name: string;
description: string;
createdAt: string;
updatedAt: string;
}>
}
// 화면 상세 조회
GET /api/screens/:id
Response: {
id: number;
name: string;
description: string;
layout: LayoutData;
}
// 화면 저장
POST /api/screens/:id/layout
Request: {
layout: LayoutData
}
```
### 7.2 테이블 정보 API
```typescript
// 테이블 목록 조회
GET / api / tables;
Response: {
tables: Array<{
id: string;
name: string;
description: string;
columns: Array<{
id: string;
name: string;
type: string;
nullable: boolean;
primaryKey: boolean;
}>;
}>;
}
```
## 8. 성능 최적화
### 8.1 렌더링 최적화
- `useCallback`으로 이벤트 핸들러 메모이제이션
- `useMemo`로 계산 비용이 큰 값 캐싱
- 컴포넌트 분할을 통한 불필요한 리렌더링 방지
### 8.2 상태 최적화
- 로컬 상태 기반 즉시 반영으로 UI 응답성 향상
- 디바운싱을 통한 과도한 API 호출 방지
- 컴포넌트별 개별 상태 관리로 전역 상태 오염 방지
## 9. 개발 가이드
### 9.1 새로운 웹타입 추가 (간편한 3단계)
새로운 웹타입 추가가 대폭 간편해졌습니다! 이제 **데이터베이스 기반 동적 웹타입 시스템**을 사용합니다.
#### **1단계: 데이터베이스에 웹타입 등록**
```sql
-- web_type_standard 테이블에 새 웹타입 추가
INSERT INTO web_type_standard (
web_type,
type_name,
config_panel,
active
) VALUES (
'my_new_type', -- 웹타입 코드 (영문)
'새로운 입력 타입', -- 한글 표시명
'MyNewTypeConfigPanel', -- 설정 패널 컴포넌트명 (선택사항)
'Y' -- 활성화 여부
);
```
#### **2단계: 설정 패널 컴포넌트 생성 (선택사항)**
웹타입에 특별한 설정이 필요한 경우만 생성:
```typescript
// frontend/components/screen/config-panels/MyNewTypeConfigPanel.tsx
export const MyNewTypeConfigPanel = ({ config, onConfigChange }) => {
return (
);
};
```
#### **3단계: 레지스트리에 등록**
```typescript
// frontend/lib/utils/availableConfigPanels.ts
import { MyNewTypeConfigPanel } from "@/components/screen/config-panels/MyNewTypeConfigPanel";
export const availableConfigPanels = {
// 기존 패널들...
ButtonConfigPanel,
TextTypeConfigPanel,
NumberTypeConfigPanel,
// 새 패널 추가
MyNewTypeConfigPanel, // ← 이 한 줄만 추가!
};
```
#### **완료! 🎉**
- ✅ **PropertiesPanel**: 자동으로 드롭다운에 "새로운 입력 타입" 표시
- ✅ **DetailSettingsPanel**: 위젯 타입 변경 시 자동으로 설정 패널 표시
- ✅ **실시간 업데이트**: React key props로 즉시 반영
#### **설정 패널이 없는 경우**
config_panel을 NULL로 설정하거나 레지스트리에 등록하지 않으면 "기본 설정" 메시지가 표시됩니다.
```sql
-- 설정 패널 없는 간단한 웹타입
INSERT INTO web_type_standard (web_type, type_name, active)
VALUES ('simple_type', '간단한 타입', 'Y');
```
#### **렌더링 로직 추가 (필요한 경우)**
새 웹타입이 특별한 렌더링이 필요한 경우에만 추가:
```typescript
// RealtimePreviewDynamic.tsx 또는 InteractiveScreenViewer.tsx
case "my_new_type":
return (
);
```
#### **타입 정의 추가 (TypeScript 지원)**
```typescript
// types/screen.ts
export type WebType =
| "text"
| "number"
| "date"
| "my_new_type" // ← 새 타입 추가
| /* 기타 타입들 */;
export interface MyNewTypeConfig {
option1?: string;
option2?: "default" | "custom";
// 기타 설정 옵션들
}
export type WebTypeConfig =
| TextTypeConfig
| NumberTypeConfig
| MyNewTypeConfig // ← 새 설정 타입 추가
| /* 기타 설정 타입들 */;
```
### 🎯 **핵심 장점**
- **플러그 앤 플레이**: 코드 수정 최소화
- **데이터베이스 기반**: 개발자 도구나 어드민에서 웹타입 관리 가능
- **자동 감지**: 별도 등록 로직 없이 자동으로 시스템에 반영
- **실시간 업데이트**: React key props로 즉시 설정 변경 반영
### 9.2 새로운 템플릿 추가
1. **TemplatesPanel에 템플릿 정의 추가**
```typescript
const templates: TemplateComponent[] = [
{
id: "새로운-템플릿",
name: "새로운 템플릿",
category: "카테고리",
icon: ,
defaultSize: { width: 400, height: 300 },
components: [
// 템플릿 구성 컴포넌트들
]
}
];
```
2. **필요한 경우 특별한 렌더링 로직 추가**
### 9.3 코딩 컨벤션
#### 실시간 속성 편집 패턴 (필수)
```typescript
// 1. 로컬 상태 정의
const [localInputs, setLocalInputs] = useState({
title: component.title || "",
});
// 2. 컴포넌트 변경 시 동기화
useEffect(() => {
setLocalInputs({
title: component.title || "",
});
}, [component.title]);
// 3. 실시간 입력 처리
{
const newValue = e.target.value;
setLocalInputs(prev => ({ ...prev, title: newValue }));
onUpdateProperty("title", newValue);
}}
/>
```
## 10. 테스트 전략
### 10.1 단위 테스트
- 유틸리티 함수 (격자 계산, 스타일 적용 등)
- 컴포넌트 상태 관리 로직
- 데이터 변환 함수
### 10.2 통합 테스트
- 드래그앤드롭 시나리오
- 속성 편집 플로우
- API 연동 테스트
### 10.3 E2E 테스트
- 화면 생성부터 렌더링까지 전체 플로우
- 복잡한 사용자 시나리오
## 11. 향후 개선 계획
### 11.1 단기 계획
- 웹타입별 상세 설정 완성 (Date, Number, Select, Radio, File, Code, Entity)
- 조건부 표시 기능 (특정 조건에 따른 컴포넌트 표시/숨김)
- 계산 필드 기능 (다른 필드 값을 기반으로 한 자동 계산)
### 11.2 중장기 계획
- 컴포넌트 간 데이터 바인딩
- 워크플로우 연동
- 다국어 지원
- 반응형 디자인
- 컴포넌트 라이브러리 확장
## 12. 트러블슈팅
### 12.1 일반적인 문제
#### 높이가 적용되지 않는 문제
- **원인**: Tailwind CSS의 `h-full` 클래스가 인라인 스타일을 무시
- **해결**: `className="w-full"` + `style={{ height: "100%" }}` 사용
#### 라벨이 컴포넌트 높이에 포함되는 문제
- **원인**: 라벨과 위젯이 같은 컨테이너 내에 위치
- **해결**: 라벨을 외부에 별도 렌더링하여 높이에서 제외
#### 스타일이 할당된 화면에서 적용되지 않는 문제
- **원인**: `applyStyles` 함수 미사용 또는 잘못된 스타일 병합
- **해결**: 모든 위젯에서 일관된 스타일 적용 로직 사용
#### 다중 드래그 시 성능 문제
- **원인**: 과도한 리렌더링
- **해결**: `useCallback`, `useMemo` 적극 활용
### 12.2 디버깅 팁
- 브라우저 개발자 도구의 React DevTools 활용
- 콘솔 로그를 통한 상태 추적
- 컴포넌트 트리 구조 시각화
---
_본 문서는 지속적으로 업데이트되며, 새로운 기능 추가 시 해당 섹션을 업데이트해야 합니다._