화면관리 설계
This commit is contained in:
parent
b0e450a90a
commit
aa969f0cb2
|
|
@ -19,15 +19,16 @@
|
|||
|
||||
### 화면관리 시스템이란?
|
||||
|
||||
화면관리 시스템은 실제 서비스되는 화면을 드래그앤드롭으로 설계하고 관리할 수 있는 시스템입니다. 테이블 타입관리와 연계하여 각 필드가 웹에서 어떻게 표시될지를 정의하고, 사용자가 직관적으로 화면을 구성할 수 있습니다.
|
||||
화면관리 시스템은 사용자가 속한 회사에 맞춰 화면을 드래그앤드롭으로 설계하고 관리할 수 있는 시스템입니다. 테이블 타입관리와 연계하여 각 필드가 웹에서 어떻게 표시될지를 정의하고, 사용자가 직관적으로 화면을 구성할 수 있습니다.
|
||||
|
||||
### 주요 특징
|
||||
|
||||
- **회사별 화면 관리**: 사용자 회사 코드에 따른 화면 접근 제어
|
||||
- **드래그앤드롭 인터페이스**: 직관적인 화면 설계
|
||||
- **컨테이너 그룹화**: 컴포넌트를 깔끔하게 정렬하는 그룹 기능
|
||||
- **테이블 타입 연계**: 컬럼의 웹 타입에 따른 자동 위젯 생성
|
||||
- **실시간 미리보기**: 설계한 화면을 즉시 확인 가능
|
||||
- **반응형 디자인**: 다양한 화면 크기에 대응
|
||||
- **템플릿 시스템**: 재사용 가능한 화면 템플릿 제공
|
||||
- **실시간 미리보기**: 설계한 화면을 실제 화면과 동일하게 확인 가능
|
||||
- **메뉴 연동**: 각 회사의 메뉴에 화면 할당 및 관리
|
||||
|
||||
### 🎯 **현재 테이블 구조와 100% 호환**
|
||||
|
||||
|
|
@ -42,6 +43,15 @@
|
|||
|
||||
**별도의 테이블 구조 변경 없이 바로 개발 가능!** 🚀
|
||||
|
||||
### 🏢 **회사별 화면 관리 시스템**
|
||||
|
||||
**사용자 권한에 따른 화면 접근 제어:**
|
||||
|
||||
- ✅ **일반 사용자**: 자신이 속한 회사의 화면만 제작/수정 가능
|
||||
- ✅ **관리자 (회사코드 '\*')**: 모든 회사의 화면을 제어 가능
|
||||
- ✅ **회사별 메뉴 할당**: 각 회사의 메뉴에만 화면 할당 가능
|
||||
- ✅ **권한 격리**: 회사 간 화면 데이터 완전 분리
|
||||
|
||||
### 지원하는 웹 타입
|
||||
|
||||
테이블 타입관리에서 각 컬럼별로 설정할 수 있는 웹 타입입니다:
|
||||
|
|
@ -118,6 +128,25 @@
|
|||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
### 회사별 권한 관리 구조
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ 사용자 │ │ 권한 검증 │ │ 화면 데이터 │
|
||||
│ │ │ │ │ │
|
||||
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
|
||||
│ │ 회사코드 │ │───▶│ │ 권한 검증 │ │───▶│ │ 회사별 │ │
|
||||
│ │ (company_ │ │ │ │ 미들웨어 │ │ │ │ 화면 │ │
|
||||
│ │ code) │ │ │ │ │ │ │ │ 격리 │ │
|
||||
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
|
||||
│ │ 권한 레벨 │ │ │ │ 회사별 │ │ │ │ 메뉴 할당 │ │
|
||||
│ │ (admin: '*')│ │ │ │ 데이터 │ │ │ │ 제한 │ │
|
||||
│ └─────────────┘ │ │ │ 필터링 │ │ │ └─────────────┘ │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
### 데이터 흐름
|
||||
|
||||
1. **테이블 타입 정의**: 테이블 타입관리에서 컬럼의 웹 타입 설정
|
||||
|
|
@ -132,28 +161,42 @@
|
|||
|
||||
- **드래그앤드롭 인터페이스**: 컴포넌트를 캔버스에 배치
|
||||
- **그리드 시스템**: 12컬럼 그리드 기반 레이아웃
|
||||
- **반응형 설정**: 화면 크기별 레이아웃 조정
|
||||
- **실시간 미리보기**: 설계한 화면을 즉시 확인
|
||||
- **컨테이너 그룹화**: 컴포넌트를 깔끔하게 정렬하는 그룹 기능
|
||||
- **실시간 미리보기**: 설계한 화면을 실제 화면과 동일하게 확인
|
||||
|
||||
### 2. 컴포넌트 라이브러리
|
||||
|
||||
- **입력 컴포넌트**: text, number, date, textarea 등
|
||||
- **선택 컴포넌트**: select, checkbox, radio 등
|
||||
- **표시 컴포넌트**: label, display, image 등
|
||||
- **레이아웃 컴포넌트**: container, row, column 등
|
||||
- **레이아웃 컴포넌트**: container, row, column, group 등
|
||||
- **컨테이너 컴포넌트**: 컴포넌트들을 그룹으로 묶는 기능
|
||||
|
||||
### 3. 테이블 연계 시스템
|
||||
### 3. 회사별 권한 관리
|
||||
|
||||
- **회사 코드 기반 접근 제어**: 사용자 회사 코드에 따른 화면 접근
|
||||
- **관리자 권한**: 회사 코드 '\*'인 사용자는 모든 회사 화면 제어
|
||||
- **회사별 메뉴 할당**: 각 회사의 메뉴에만 화면 할당 가능
|
||||
- **데이터 격리**: 회사 간 화면 데이터 완전 분리
|
||||
|
||||
### 4. 테이블 연계 시스템
|
||||
|
||||
- **자동 위젯 생성**: 컬럼의 웹 타입에 따른 위젯 자동 생성
|
||||
- **데이터 바인딩**: 컬럼과 위젯의 자동 연결
|
||||
- **유효성 검증**: 컬럼 설정에 따른 자동 검증 규칙 적용
|
||||
|
||||
### 4. 템플릿 시스템
|
||||
### 5. 템플릿 시스템
|
||||
|
||||
- **기본 템플릿**: CRUD, 목록, 상세 등 기본 패턴
|
||||
- **사용자 정의 템플릿**: 자주 사용하는 레이아웃 저장
|
||||
- **템플릿 공유**: 팀원 간 템플릿 공유 및 재사용
|
||||
|
||||
### 6. 메뉴 연동 시스템
|
||||
|
||||
- **회사별 메뉴 할당**: 각 회사의 메뉴에만 화면 할당
|
||||
- **메뉴-화면 연결**: 메뉴와 화면의 1:1 또는 1:N 연결
|
||||
- **권한 기반 메뉴 표시**: 사용자 권한에 따른 메뉴 표시 제어
|
||||
|
||||
## 🗄️ 데이터베이스 설계
|
||||
|
||||
### 1. 기존 테이블 구조 (테이블 타입관리)
|
||||
|
|
@ -221,6 +264,7 @@ CREATE TABLE screen_definitions (
|
|||
screen_name VARCHAR(100) NOT NULL,
|
||||
screen_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
table_name VARCHAR(100) NOT NULL, -- 🎯 table_labels와 연계
|
||||
company_code VARCHAR(50) NOT NULL, -- 🎯 회사 코드 (권한 관리용)
|
||||
description TEXT,
|
||||
is_active CHAR(1) DEFAULT 'Y',
|
||||
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
|
@ -232,6 +276,9 @@ CREATE TABLE screen_definitions (
|
|||
CONSTRAINT fk_screen_definitions_table_name
|
||||
FOREIGN KEY (table_name) REFERENCES table_labels(table_name)
|
||||
);
|
||||
|
||||
-- 회사 코드 인덱스 추가
|
||||
CREATE INDEX idx_screen_definitions_company_code ON screen_definitions(company_code);
|
||||
```
|
||||
|
||||
#### screen_layouts (화면 레이아웃)
|
||||
|
|
@ -283,12 +330,43 @@ CREATE TABLE screen_templates (
|
|||
template_id SERIAL PRIMARY KEY,
|
||||
template_name VARCHAR(100) NOT NULL,
|
||||
template_type VARCHAR(50) NOT NULL, -- CRUD, LIST, DETAIL 등
|
||||
company_code VARCHAR(50) NOT NULL, -- 🎯 회사 코드 (권한 관리용)
|
||||
description TEXT,
|
||||
layout_data JSONB, -- 레이아웃 데이터
|
||||
is_public BOOLEAN DEFAULT FALSE, -- 공개 여부
|
||||
created_by VARCHAR(50),
|
||||
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 회사 코드 인덱스 추가
|
||||
CREATE INDEX idx_screen_templates_company_code ON screen_templates(company_code);
|
||||
```
|
||||
|
||||
#### screen_menu_assignments (화면-메뉴 할당)
|
||||
|
||||
```sql
|
||||
CREATE TABLE screen_menu_assignments (
|
||||
assignment_id SERIAL PRIMARY KEY,
|
||||
screen_id INTEGER NOT NULL,
|
||||
menu_id INTEGER NOT NULL,
|
||||
company_code VARCHAR(50) NOT NULL, -- 🎯 회사 코드 (권한 관리용)
|
||||
display_order INTEGER DEFAULT 0,
|
||||
is_active CHAR(1) DEFAULT 'Y',
|
||||
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
|
||||
-- 외래키 제약조건
|
||||
CONSTRAINT fk_screen_menu_assignments_screen_id
|
||||
FOREIGN KEY (screen_id) REFERENCES screen_definitions(screen_id),
|
||||
CONSTRAINT fk_screen_menu_assignments_menu_id
|
||||
FOREIGN KEY (menu_id) REFERENCES menu_info(menu_id),
|
||||
|
||||
-- 유니크 제약조건 (한 메뉴에 같은 화면 중복 할당 방지)
|
||||
CONSTRAINT uk_screen_menu_company UNIQUE (screen_id, menu_id, company_code)
|
||||
);
|
||||
|
||||
-- 회사 코드 인덱스 추가
|
||||
CREATE INDEX idx_screen_menu_assignments_company_code ON screen_menu_assignments(company_code);
|
||||
```
|
||||
|
||||
### 3. 테이블 간 연계 관계
|
||||
|
|
@ -303,6 +381,8 @@ screen_definitions (화면 정의)
|
|||
screen_layouts (화면 레이아웃)
|
||||
↓ (1:N)
|
||||
screen_widgets (화면 위젯)
|
||||
↓ (1:N)
|
||||
screen_menu_assignments (화면-메뉴 할당)
|
||||
```
|
||||
|
||||
**핵심 연계 포인트:**
|
||||
|
|
@ -310,6 +390,8 @@ screen_widgets (화면 위젯)
|
|||
- ✅ `screen_definitions.table_name` ↔ `table_labels.table_name`
|
||||
- ✅ `screen_widgets.table_name, column_name` ↔ `column_labels.table_name, column_name`
|
||||
- ✅ `screen_widgets.widget_type` ↔ `column_labels.web_type` (자동 동기화)
|
||||
- ✅ `screen_definitions.company_code` ↔ 사용자 회사 코드 (권한 관리)
|
||||
- ✅ `screen_menu_assignments.company_code` ↔ 메뉴 회사 코드 (메뉴 할당 제한)
|
||||
|
||||
## 🎨 화면 구성 요소
|
||||
|
||||
|
|
@ -327,6 +409,29 @@ interface ContainerProps {
|
|||
margin: number;
|
||||
backgroundColor?: string;
|
||||
border?: string;
|
||||
borderRadius?: number;
|
||||
shadow?: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### Group (그룹)
|
||||
|
||||
```typescript
|
||||
interface GroupProps {
|
||||
id: string;
|
||||
type: "group";
|
||||
title?: string;
|
||||
width: number; // 1-12
|
||||
height: number;
|
||||
padding: number;
|
||||
margin: number;
|
||||
backgroundColor?: string;
|
||||
border?: string;
|
||||
borderRadius?: number;
|
||||
shadow?: string;
|
||||
collapsible?: boolean; // 접을 수 있는 그룹
|
||||
collapsed?: boolean; // 접힌 상태
|
||||
children: string[]; // 포함된 컴포넌트 ID 목록
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -440,16 +545,27 @@ interface DragState {
|
|||
draggedItem: ComponentData | null;
|
||||
dragSource: "toolbox" | "canvas";
|
||||
dropTarget: string | null;
|
||||
dropZone?: DropZone; // 드롭 가능한 영역 정보
|
||||
}
|
||||
|
||||
// 그룹화 상태 관리
|
||||
interface GroupState {
|
||||
isGrouping: boolean;
|
||||
selectedComponents: string[];
|
||||
groupTarget: string | null;
|
||||
groupMode: "create" | "add" | "remove";
|
||||
}
|
||||
```
|
||||
|
||||
// 드롭 영역 정의
|
||||
interface DropZone {
|
||||
id: string;
|
||||
accepts: string[]; // 허용되는 컴포넌트 타입
|
||||
position: { x: number; y: number };
|
||||
size: { width: number; height: number };
|
||||
id: string;
|
||||
accepts: string[]; // 허용되는 컴포넌트 타입
|
||||
position: { x: number; y: number };
|
||||
size: { width: number; height: number };
|
||||
}
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
### 2. 컴포넌트 배치 로직
|
||||
|
||||
|
|
@ -478,7 +594,7 @@ function resizeComponent(
|
|||
height: Math.max(50, newHeight),
|
||||
};
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
### 3. 실시간 미리보기
|
||||
|
||||
|
|
@ -492,21 +608,40 @@ function generatePreview(layout: LayoutData): React.ReactElement {
|
|||
);
|
||||
}
|
||||
|
||||
// 컴포넌트 렌더링
|
||||
function renderComponent(component: ComponentData): React.ReactElement {
|
||||
switch (component.type) {
|
||||
case "text":
|
||||
return <TextInput {...component.props} />;
|
||||
case "select":
|
||||
return <Select {...component.props} />;
|
||||
case "date":
|
||||
return <DatePicker {...component.props} />;
|
||||
default:
|
||||
return <div>Unknown component</div>;
|
||||
}
|
||||
// 그룹 컴포넌트 렌더링
|
||||
function renderGroupComponent(
|
||||
group: GroupProps,
|
||||
components: ComponentData[]
|
||||
): React.ReactElement {
|
||||
const groupChildren = components.filter((c) => group.children.includes(c.id));
|
||||
|
||||
return (
|
||||
<div className="component-group" style={getGroupStyles(group)}>
|
||||
{group.title && <div className="group-title">{group.title}</div>}
|
||||
<div className="group-content">
|
||||
{groupChildren.map((component) => renderComponent(component))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
// 컴포넌트 렌더링
|
||||
function renderComponent(component: ComponentData): React.ReactElement {
|
||||
switch (component.type) {
|
||||
case "text":
|
||||
return <TextInput {...component.props} />;
|
||||
case "select":
|
||||
return <Select {...component.props} />;
|
||||
case "date":
|
||||
return <DatePicker {...component.props} />;
|
||||
default:
|
||||
return <div>Unknown component</div>;
|
||||
}
|
||||
}
|
||||
|
||||
````
|
||||
|
||||
## 🔗 테이블 타입 연계
|
||||
|
||||
### 1. 웹 타입 설정 방법
|
||||
|
|
@ -552,7 +687,7 @@ async function getTableColumnWebTypes(
|
|||
): Promise<ColumnWebTypeSetting[]> {
|
||||
return api.get(`/table-management/tables/${tableName}/columns/web-types`);
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
#### 웹 타입별 추가 설정 (현재 테이블 구조 기반)
|
||||
|
||||
|
|
@ -798,10 +933,15 @@ function generateValidationRules(column: ColumnInfo): ValidationRule[] {
|
|||
|
||||
### 1. 화면 정의 API
|
||||
|
||||
#### 화면 목록 조회
|
||||
#### 화면 목록 조회 (회사별)
|
||||
|
||||
```typescript
|
||||
GET /api/screen-management/screens
|
||||
Query: {
|
||||
companyCode?: string; // 회사 코드 (관리자는 생략 가능)
|
||||
page?: number;
|
||||
size?: number;
|
||||
}
|
||||
Response: {
|
||||
success: boolean;
|
||||
data: ScreenDefinition[];
|
||||
|
|
@ -809,6 +949,19 @@ Response: {
|
|||
}
|
||||
```
|
||||
|
||||
#### 화면 생성 (회사별)
|
||||
|
||||
```typescript
|
||||
POST /api/screen-management/screens
|
||||
Body: {
|
||||
screenName: string;
|
||||
screenCode: string;
|
||||
tableName: string;
|
||||
companyCode: string; // 사용자 회사 코드 자동 설정
|
||||
description?: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### 화면 생성
|
||||
|
||||
```typescript
|
||||
|
|
@ -896,17 +1049,44 @@ Body: {
|
|||
|
||||
### 4. 템플릿 API
|
||||
|
||||
#### 템플릿 목록 조회
|
||||
#### 템플릿 목록 조회 (회사별)
|
||||
|
||||
```typescript
|
||||
GET /api/screen-management/templates
|
||||
Query: {
|
||||
companyCode?: string; // 회사 코드 (관리자는 생략 가능)
|
||||
type?: string;
|
||||
isPublic?: boolean;
|
||||
createdBy?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 메뉴 할당 API
|
||||
|
||||
#### 화면-메뉴 할당
|
||||
|
||||
```typescript
|
||||
POST /api/screen-management/screens/:screenId/menu-assignments
|
||||
Body: {
|
||||
menuId: number;
|
||||
companyCode: string; // 사용자 회사 코드 자동 설정
|
||||
displayOrder?: number;
|
||||
}
|
||||
```
|
||||
|
||||
#### 메뉴별 화면 목록 조회
|
||||
|
||||
```typescript
|
||||
GET /api/screen-management/menus/:menuId/screens
|
||||
Query: {
|
||||
companyCode: string; // 회사 코드 필수
|
||||
}
|
||||
Response: {
|
||||
success: boolean;
|
||||
data: ScreenDefinition[];
|
||||
}
|
||||
```
|
||||
|
||||
#### 템플릿 적용
|
||||
|
||||
```typescript
|
||||
|
|
@ -934,37 +1114,88 @@ export default function ScreenDesigner() {
|
|||
dragSource: "toolbox",
|
||||
dropTarget: null,
|
||||
});
|
||||
const [groupState, setGroupState] = useState<GroupState>({
|
||||
isGrouping: false,
|
||||
selectedComponents: [],
|
||||
groupTarget: null,
|
||||
groupMode: "create",
|
||||
});
|
||||
const [userCompanyCode, setUserCompanyCode] = useState<string>("");
|
||||
```
|
||||
|
||||
// 컴포넌트 추가
|
||||
const addComponent = (component: ComponentData) => {
|
||||
setLayout((prev) => ({
|
||||
...prev,
|
||||
components: [...prev.components, component],
|
||||
}));
|
||||
};
|
||||
|
||||
// 컴포넌트 삭제
|
||||
const removeComponent = (componentId: string) => {
|
||||
setLayout((prev) => ({
|
||||
...prev,
|
||||
components: prev.components.filter((c) => c.id !== componentId),
|
||||
}));
|
||||
};
|
||||
|
||||
// 컴포넌트 이동
|
||||
const moveComponent = (componentId: string, newPosition: Position) => {
|
||||
setLayout((prev) => ({
|
||||
...prev,
|
||||
components: prev.components.map((c) =>
|
||||
c.id === componentId ? { ...c, position: newPosition } : c
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
// 컴포넌트 그룹화
|
||||
const groupComponents = (componentIds: string[], groupTitle?: string) => {
|
||||
const groupId = generateId();
|
||||
const groupComponent: GroupProps = {
|
||||
id: groupId,
|
||||
type: "group",
|
||||
title: groupTitle || "그룹",
|
||||
width: 12,
|
||||
height: 200,
|
||||
padding: 16,
|
||||
margin: 8,
|
||||
backgroundColor: "#f8f9fa",
|
||||
border: "1px solid #dee2e6",
|
||||
borderRadius: 8,
|
||||
shadow: "0 2px 4px rgba(0,0,0,0.1)",
|
||||
collapsible: true,
|
||||
collapsed: false,
|
||||
children: componentIds,
|
||||
};
|
||||
|
||||
// 컴포넌트 추가
|
||||
const addComponent = (component: ComponentData) => {
|
||||
setLayout((prev) => ({
|
||||
...prev,
|
||||
components: [...prev.components, component],
|
||||
components: [...prev.components, groupComponent],
|
||||
}));
|
||||
};
|
||||
|
||||
// 컴포넌트 삭제
|
||||
const removeComponent = (componentId: string) => {
|
||||
setLayout((prev) => ({
|
||||
...prev,
|
||||
components: prev.components.filter((c) => c.id !== componentId),
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
||||
// 컴포넌트 이동
|
||||
const moveComponent = (componentId: string, newPosition: Position) => {
|
||||
setLayout((prev) => ({
|
||||
...prev,
|
||||
components: prev.components.map((c) =>
|
||||
c.id === componentId ? { ...c, position: newPosition } : c
|
||||
),
|
||||
}));
|
||||
};
|
||||
// 그룹에서 컴포넌트 제거
|
||||
const ungroupComponent = (componentId: string, groupId: string) => {
|
||||
setLayout((prev) => ({
|
||||
...prev,
|
||||
components: prev.components.map((c) => {
|
||||
if (c.id === groupId && c.type === "group") {
|
||||
return {
|
||||
...c,
|
||||
children: c.children.filter((id) => id !== componentId),
|
||||
};
|
||||
}
|
||||
return c;
|
||||
}),
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="screen-designer">
|
||||
<Toolbox onComponentSelect={addComponent} />
|
||||
<Canvas
|
||||
return (
|
||||
<div className="screen-designer">
|
||||
<Toolbox onComponentSelect={addComponent} />
|
||||
<Canvas
|
||||
layout={layout}
|
||||
selectedComponent={selectedComponent}
|
||||
onComponentSelect={setSelectedComponent}
|
||||
|
|
@ -973,15 +1204,24 @@ export default function ScreenDesigner() {
|
|||
dragState={dragState}
|
||||
onDragStateChange={setDragState}
|
||||
/>
|
||||
<PropertiesPanel
|
||||
component={layout.components.find((c) => c.id === selectedComponent)}
|
||||
onPropertyChange={updateComponentProperty}
|
||||
<PropertiesPanel
|
||||
component={layout.components.find((c) => c.id === selectedComponent)}
|
||||
onPropertyChange={updateComponentProperty}
|
||||
onGroupCreate={groupComponents}
|
||||
onGroupRemove={ungroupComponent}
|
||||
/>
|
||||
<PreviewPanel layout={layout} />
|
||||
<GroupingToolbar
|
||||
groupState={groupState}
|
||||
onGroupStateChange={setGroupState}
|
||||
onGroupCreate={groupComponents}
|
||||
onGroupRemove={ungroupComponent}
|
||||
/>
|
||||
<PreviewPanel layout={layout} />
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
### 2. 드래그앤드롭 구현
|
||||
|
||||
|
|
@ -1024,7 +1264,7 @@ export function useDragAndDrop() {
|
|||
updateDropTarget,
|
||||
};
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
### 3. 그리드 시스템
|
||||
|
||||
|
|
@ -1045,6 +1285,56 @@ export default function GridSystem({ children, columns = 12 }) {
|
|||
);
|
||||
}
|
||||
|
||||
// 그룹화 도구 모음
|
||||
export function GroupingToolbar({
|
||||
groupState,
|
||||
onGroupStateChange,
|
||||
onGroupCreate,
|
||||
onGroupRemove,
|
||||
}) {
|
||||
const handleGroupCreate = () => {
|
||||
if (groupState.selectedComponents.length > 1) {
|
||||
const groupTitle = prompt("그룹 제목을 입력하세요:");
|
||||
onGroupCreate(groupState.selectedComponents, groupTitle);
|
||||
onGroupStateChange({
|
||||
...groupState,
|
||||
isGrouping: false,
|
||||
selectedComponents: [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleGroupRemove = () => {
|
||||
if (groupState.groupTarget) {
|
||||
onGroupRemove(groupState.selectedComponents[0], groupState.groupTarget);
|
||||
onGroupStateChange({
|
||||
...groupState,
|
||||
isGrouping: false,
|
||||
selectedComponents: [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grouping-toolbar">
|
||||
<button
|
||||
onClick={handleGroupCreate}
|
||||
disabled={groupState.selectedComponents.length < 2}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
그룹 생성
|
||||
</button>
|
||||
<button
|
||||
onClick={handleGroupRemove}
|
||||
disabled={!groupState.groupTarget}
|
||||
className="btn btn-secondary"
|
||||
>
|
||||
그룹 해제
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// GridItem.tsx
|
||||
interface GridItemProps {
|
||||
width: number; // 1-12
|
||||
|
|
@ -1080,15 +1370,22 @@ export default function GridItem({
|
|||
```typescript
|
||||
// screenManagementService.ts
|
||||
export class ScreenManagementService {
|
||||
// 화면 정의 생성
|
||||
// 화면 정의 생성 (회사별)
|
||||
async createScreen(
|
||||
screenData: CreateScreenRequest
|
||||
screenData: CreateScreenRequest,
|
||||
userCompanyCode: string
|
||||
): Promise<ScreenDefinition> {
|
||||
// 권한 검증: 사용자 회사 코드 확인
|
||||
if (userCompanyCode !== "*" && userCompanyCode !== screenData.companyCode) {
|
||||
throw new Error("해당 회사의 화면을 생성할 권한이 없습니다.");
|
||||
}
|
||||
|
||||
const screen = await prisma.screen_definitions.create({
|
||||
data: {
|
||||
screen_name: screenData.screenName,
|
||||
screen_code: screenData.screenCode,
|
||||
table_name: screenData.tableName,
|
||||
company_code: screenData.companyCode,
|
||||
description: screenData.description,
|
||||
created_by: screenData.createdBy,
|
||||
},
|
||||
|
|
@ -1097,6 +1394,30 @@ export class ScreenManagementService {
|
|||
return this.mapToScreenDefinition(screen);
|
||||
}
|
||||
|
||||
// 회사별 화면 목록 조회
|
||||
async getScreensByCompany(
|
||||
companyCode: string,
|
||||
page: number = 1,
|
||||
size: number = 20
|
||||
): Promise<{ screens: ScreenDefinition[]; total: number }> {
|
||||
const whereClause = companyCode === "*" ? {} : { company_code: companyCode };
|
||||
|
||||
const [screens, total] = await Promise.all([
|
||||
prisma.screen_definitions.findMany({
|
||||
where: whereClause,
|
||||
skip: (page - 1) * size,
|
||||
take: size,
|
||||
orderBy: { created_date: "desc" },
|
||||
}),
|
||||
prisma.screen_definitions.count({ where: whereClause }),
|
||||
]);
|
||||
|
||||
return {
|
||||
screens: screens.map(this.mapToScreenDefinition),
|
||||
total,
|
||||
};
|
||||
|
||||
|
||||
// 레이아웃 저장
|
||||
async saveLayout(screenId: number, layoutData: LayoutData): Promise<void> {
|
||||
// 기존 레이아웃 삭제
|
||||
|
|
@ -1449,7 +1770,23 @@ export class TableTypeIntegrationService {
|
|||
|
||||
## 🎬 사용 시나리오
|
||||
|
||||
### 1. 웹 타입 설정 (테이블 타입관리)
|
||||
### 1. 회사별 화면 관리
|
||||
|
||||
#### 일반 사용자 (회사 코드: 'COMP001')
|
||||
|
||||
1. **로그인**: 자신의 회사 코드로 시스템 로그인
|
||||
2. **화면 목록 조회**: 자신이 속한 회사의 화면만 표시
|
||||
3. **화면 생성**: 회사 코드가 자동으로 설정되어 생성
|
||||
4. **메뉴 할당**: 자신의 회사 메뉴에만 화면 할당 가능
|
||||
|
||||
#### 관리자 (회사 코드: '\*')
|
||||
|
||||
1. **로그인**: 관리자 권한으로 시스템 로그인
|
||||
2. **전체 화면 조회**: 모든 회사의 화면을 조회/수정 가능
|
||||
3. **회사별 화면 관리**: 각 회사별로 화면 생성/수정/삭제
|
||||
4. **크로스 회사 메뉴 할당**: 모든 회사의 메뉴에 화면 할당 가능
|
||||
|
||||
### 2. 웹 타입 설정 (테이블 타입관리)
|
||||
|
||||
1. **테이블 선택**: 테이블 타입관리에서 웹 타입을 설정할 테이블 선택
|
||||
2. **컬럼 관리**: 해당 테이블의 컬럼 목록에서 웹 타입을 설정할 컬럼 선택
|
||||
|
|
@ -1462,38 +1799,48 @@ export class TableTypeIntegrationService {
|
|||
5. **저장**: 웹 타입 설정을 데이터베이스에 저장
|
||||
6. **연계 확인**: 화면관리 시스템에서 자동 위젯 생성 확인
|
||||
|
||||
### 2. 새로운 화면 설계
|
||||
### 3. 새로운 화면 설계
|
||||
|
||||
1. **테이블 선택**: 테이블 타입관리에서 설계할 테이블 선택
|
||||
2. **웹 타입 확인**: 각 컬럼의 웹 타입 설정 상태 확인
|
||||
3. **화면 생성**: 화면명과 코드를 입력하여 새 화면 생성
|
||||
3. **화면 생성**: 화면명과 코드를 입력하여 새 화면 생성 (회사 코드 자동 설정)
|
||||
4. **자동 위젯 생성**: 컬럼의 웹 타입에 따라 자동으로 위젯 생성
|
||||
5. **컴포넌트 배치**: 드래그앤드롭으로 컴포넌트를 캔버스에 배치
|
||||
6. **속성 설정**: 각 컴포넌트의 속성을 Properties 패널에서 설정
|
||||
7. **미리보기**: 실시간으로 설계한 화면 확인
|
||||
8. **저장**: 완성된 화면 레이아웃을 데이터베이스에 저장
|
||||
6. **컨테이너 그룹화**: 관련 컴포넌트들을 그룹으로 묶어 깔끔하게 정렬
|
||||
7. **속성 설정**: 각 컴포넌트의 속성을 Properties 패널에서 설정
|
||||
8. **실시간 미리보기**: 설계한 화면을 실제 화면과 동일하게 확인
|
||||
9. **저장**: 완성된 화면 레이아웃을 데이터베이스에 저장
|
||||
|
||||
### 2. 기존 화면 수정
|
||||
### 4. 기존 화면 수정
|
||||
|
||||
1. **화면 선택**: 수정할 화면을 목록에서 선택
|
||||
1. **화면 선택**: 수정할 화면을 목록에서 선택 (권한 확인)
|
||||
2. **레이아웃 로드**: 기존 레이아웃을 캔버스에 로드
|
||||
3. **컴포넌트 수정**: 컴포넌트 추가/삭제/이동/수정
|
||||
4. **속성 변경**: 컴포넌트 속성 변경
|
||||
5. **변경사항 확인**: 미리보기로 변경사항 확인
|
||||
6. **저장**: 수정된 레이아웃 저장
|
||||
4. **그룹 구조 조정**: 컴포넌트 그룹화/그룹 해제/그룹 속성 변경
|
||||
5. **속성 변경**: 컴포넌트 속성 변경
|
||||
6. **변경사항 확인**: 실시간 미리보기로 변경사항 확인
|
||||
7. **저장**: 수정된 레이아웃 저장
|
||||
|
||||
### 3. 템플릿 활용
|
||||
### 5. 템플릿 활용
|
||||
|
||||
1. **템플릿 선택**: 적합한 템플릿을 목록에서 선택
|
||||
1. **템플릿 선택**: 적합한 템플릿을 목록에서 선택 (회사별 템플릿)
|
||||
2. **템플릿 적용**: 선택한 템플릿을 현재 화면에 적용
|
||||
3. **커스터마이징**: 템플릿을 기반으로 필요한 부분 수정
|
||||
4. **저장**: 커스터마이징된 화면 저장
|
||||
|
||||
### 4. 화면 배포
|
||||
### 6. 메뉴 할당 및 관리
|
||||
|
||||
1. **메뉴 선택**: 화면을 할당할 메뉴 선택 (회사별 메뉴만 표시)
|
||||
2. **화면 할당**: 선택한 화면을 메뉴에 할당
|
||||
3. **할당 순서 조정**: 메뉴 내 화면 표시 순서 조정
|
||||
4. **할당 해제**: 메뉴에서 화면 할당 해제
|
||||
5. **권한 확인**: 메뉴 할당 시 회사 코드 일치 여부 확인
|
||||
|
||||
### 7. 화면 배포
|
||||
|
||||
1. **화면 활성화**: 설계 완료된 화면을 활성 상태로 변경
|
||||
2. **권한 설정**: 화면 접근 권한 설정
|
||||
3. **메뉴 연결**: 메뉴 시스템에 화면 연결
|
||||
2. **권한 설정**: 화면 접근 권한 설정 (회사별 권한)
|
||||
3. **메뉴 연결**: 메뉴 시스템에 화면 연결 (회사별 메뉴)
|
||||
4. **테스트**: 실제 환경에서 화면 동작 테스트
|
||||
5. **배포**: 운영 환경에 화면 배포
|
||||
|
||||
|
|
@ -1545,6 +1892,25 @@ export class TableTypeIntegrationService {
|
|||
|
||||
## 🎯 결론
|
||||
|
||||
화면관리 시스템은 테이블 타입관리와 연계하여 사용자가 직관적으로 웹 화면을 설계할 수 있는 강력한 도구입니다. 드래그앤드롭 인터페이스와 자동 위젯 생성 기능을 통해 개발자가 아닌 사용자도 전문적인 웹 화면을 쉽게 구성할 수 있습니다.
|
||||
화면관리 시스템은 **회사별 권한 관리**와 **테이블 타입관리 연계**를 통해 사용자가 직관적으로 웹 화면을 설계할 수 있는 강력한 도구입니다.
|
||||
|
||||
이 시스템을 통해 ERP 시스템의 화면 개발 생산성을 크게 향상시키고, 사용자 요구사항에 따른 빠른 화면 구성이 가능해질 것입니다.
|
||||
### 🏢 **회사별 화면 관리의 핵심 가치**
|
||||
|
||||
- **권한 격리**: 사용자는 자신이 속한 회사의 화면만 제작/수정 가능
|
||||
- **관리자 통제**: 회사 코드 '\*'인 관리자는 모든 회사의 화면을 제어
|
||||
- **메뉴 연동**: 각 회사의 메뉴에만 화면 할당하여 완벽한 데이터 분리
|
||||
|
||||
### 🎨 **향상된 사용자 경험**
|
||||
|
||||
- **드래그앤드롭 인터페이스**: 직관적인 화면 설계
|
||||
- **컨테이너 그룹화**: 컴포넌트를 깔끔하게 정렬하는 그룹 기능
|
||||
- **실시간 미리보기**: 설계한 화면을 실제 화면과 동일하게 확인
|
||||
- **자동 위젯 생성**: 컬럼의 웹 타입에 따른 스마트한 위젯 생성
|
||||
|
||||
### 🚀 **기술적 혜택**
|
||||
|
||||
- **기존 테이블 구조 100% 호환**: 별도 스키마 변경 없이 바로 개발 가능
|
||||
- **권한 기반 보안**: 회사 간 데이터 완전 격리
|
||||
- **확장 가능한 아키텍처**: 새로운 웹 타입과 컴포넌트 쉽게 추가
|
||||
|
||||
이 시스템을 통해 ERP 시스템의 화면 개발 생산성을 크게 향상시키고, **회사별 맞춤형 화면 구성**과 **사용자 요구사항에 따른 빠른 화면 구성**이 가능해질 것입니다.
|
||||
|
|
|
|||
Loading…
Reference in New Issue