화면관리 설계

This commit is contained in:
kjs 2025-09-01 10:19:47 +09:00
parent b0e450a90a
commit aa969f0cb2
1 changed files with 448 additions and 82 deletions

View File

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