2025-09-10 18:36:28 +09:00
|
|
|
# 레이아웃 추가 가이드
|
|
|
|
|
|
|
|
|
|
화면관리 시스템에서 새로운 레이아웃을 추가하는 방법을 설명합니다.
|
|
|
|
|
|
|
|
|
|
## 📋 목차
|
|
|
|
|
|
2025-09-11 12:22:39 +09:00
|
|
|
1. [현재 지원하는 레이아웃](#현재-지원하는-레이아웃)
|
|
|
|
|
2. [CLI를 이용한 자동 생성](#cli를-이용한-자동-생성)
|
|
|
|
|
3. [생성된 파일 구조](#생성된-파일-구조)
|
|
|
|
|
4. [레이아웃 커스터마이징](#레이아웃-커스터마이징)
|
2025-09-11 16:21:00 +09:00
|
|
|
5. [카드 레이아웃 상세 가이드](#카드-레이아웃-상세-가이드)
|
2025-09-11 12:22:39 +09:00
|
|
|
6. [고급 설정](#고급-설정)
|
|
|
|
|
7. [문제 해결](#문제-해결)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🎨 현재 지원하는 레이아웃
|
|
|
|
|
|
|
|
|
|
### 기본 레이아웃 (Basic)
|
|
|
|
|
|
|
|
|
|
#### 1. 그리드 레이아웃 (Grid Layout)
|
|
|
|
|
|
|
|
|
|
- **ID**: `grid`
|
|
|
|
|
- **존 개수**: 4개 (동적 설정 가능)
|
|
|
|
|
- **특징**: 격자 형태의 균등 분할 레이아웃
|
|
|
|
|
- **용도**: 카드, 대시보드 위젯 배치
|
|
|
|
|
|
|
|
|
|
#### 2. 플렉스박스 레이아웃 (Flexbox Layout)
|
|
|
|
|
|
|
|
|
|
- **ID**: `flexbox`
|
|
|
|
|
- **존 개수**: 2개 (동적 설정 가능)
|
|
|
|
|
- **특징**: 가로/세로 방향 설정 가능, 자동 크기 조정
|
|
|
|
|
- **방향 설정**: 수평(`row`) / 수직(`column`)
|
|
|
|
|
- **용도**: 반응형 레이아웃, 사이드바 구조
|
|
|
|
|
|
|
|
|
|
#### 3. 분할 레이아웃 (Split Layout)
|
|
|
|
|
|
|
|
|
|
- **ID**: `split`
|
|
|
|
|
- **존 개수**: 2개
|
|
|
|
|
- **특징**: 좌우 또는 상하 분할
|
|
|
|
|
- **용도**: 마스터-디테일 화면, 비교 화면
|
|
|
|
|
|
|
|
|
|
### 네비게이션 레이아웃 (Navigation)
|
|
|
|
|
|
|
|
|
|
#### 4. 아코디언 레이아웃 (Accordion Layout)
|
|
|
|
|
|
|
|
|
|
- **ID**: `accordion`
|
|
|
|
|
- **존 개수**: 3개
|
|
|
|
|
- **특징**: 접을 수 있는 섹션들
|
|
|
|
|
- **용도**: FAQ, 설정 패널, 단계별 폼
|
|
|
|
|
|
|
|
|
|
### 대시보드 레이아웃 (Dashboard)
|
|
|
|
|
|
|
|
|
|
#### 5. 카드 레이아웃 (Card Layout) ⭐ **데이터 기반**
|
|
|
|
|
|
|
|
|
|
- **ID**: `card-layout`
|
|
|
|
|
- **존 개수**: 6개 (디자인 모드), 데이터 기반 (실행 모드)
|
|
|
|
|
- **특징**: 실제 테이블 데이터를 카드 형태로 표시
|
|
|
|
|
- **핵심 기능**:
|
|
|
|
|
- 테이블 컬럼 매핑 (타이틀, 서브타이틀, 설명, 이미지)
|
|
|
|
|
- 동적 표시 컬럼 관리
|
|
|
|
|
- 컬럼 라벨 우선 표시
|
|
|
|
|
- 실시간 카드 스타일 설정
|
|
|
|
|
- 한 행당 카드 수, 간격 조정
|
|
|
|
|
- **용도**: 상품 목록, 직원 카드, 프로젝트 카드
|
|
|
|
|
|
|
|
|
|
### 콘텐츠 레이아웃 (Content)
|
|
|
|
|
|
|
|
|
|
#### 6. 히어로 섹션 레이아웃 (Hero Section Layout)
|
|
|
|
|
|
|
|
|
|
- **ID**: `hero-section`
|
|
|
|
|
- **존 개수**: 3개
|
|
|
|
|
- **특징**: 대형 헤더 섹션과 하위 영역들
|
|
|
|
|
- **용도**: 랜딩 페이지, 제품 소개
|
2025-09-10 18:36:28 +09:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🚀 CLI를 이용한 자동 생성
|
|
|
|
|
|
|
|
|
|
### 기본 사용법
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# 기본 형태
|
|
|
|
|
node scripts/create-layout.js <레이아웃이름> [옵션]
|
|
|
|
|
|
|
|
|
|
# 예시
|
|
|
|
|
node scripts/create-layout.js card-grid --category=dashboard --zones=6 --description="카드 형태의 그리드 레이아웃"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 📝 사용 가능한 옵션
|
|
|
|
|
|
|
|
|
|
| 옵션 | 필수 여부 | 기본값 | 설명 | 예시 |
|
|
|
|
|
| --------------- | --------- | ----------- | ----------------- | ----------------------------------- |
|
|
|
|
|
| `--category` | 선택 | `basic` | 레이아웃 카테고리 | `--category=dashboard` |
|
|
|
|
|
| `--zones` | 선택 | `2` | 영역 개수 | `--zones=4` |
|
|
|
|
|
| `--description` | 선택 | 자동 생성 | 레이아웃 설명 | `--description="사이드바 레이아웃"` |
|
|
|
|
|
| `--author` | 선택 | `Developer` | 작성자 이름 | `--author="김개발"` |
|
|
|
|
|
|
|
|
|
|
### 🏷️ 카테고리 종류
|
|
|
|
|
|
|
|
|
|
| 카테고리 | 설명 | 예시 레이아웃 |
|
|
|
|
|
| ------------ | ------------- | ------------------------ |
|
|
|
|
|
| `basic` | 기본 레이아웃 | 그리드, 플렉스박스, 분할 |
|
|
|
|
|
| `navigation` | 네비게이션 | 탭, 아코디언, 메뉴 |
|
|
|
|
|
| `dashboard` | 대시보드 | 카드, 위젯, 차트 |
|
|
|
|
|
| `content` | 콘텐츠 | 헤더-본문, 영웅 섹션 |
|
|
|
|
|
| `form` | 폼 | 입력 폼, 설정 패널 |
|
|
|
|
|
| `table` | 테이블 | 데이터 테이블, 목록 |
|
|
|
|
|
|
|
|
|
|
### 💡 이름 규칙
|
|
|
|
|
|
|
|
|
|
레이아웃 이름은 **하이픈(`-`)을 사용한 kebab-case**로 입력하면 자동으로 변환됩니다:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# 입력: hero-section
|
|
|
|
|
📁 디렉토리: hero-section/
|
|
|
|
|
🔖 ID: hero-section
|
|
|
|
|
📄 클래스명: HeroSection
|
|
|
|
|
🔧 변수명: heroSection
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### ✅ 올바른 이름 예시
|
|
|
|
|
|
|
|
|
|
- `card-grid`
|
|
|
|
|
- `side-navigation`
|
|
|
|
|
- `data-table`
|
|
|
|
|
- `hero-section`
|
|
|
|
|
- `my-awesome-layout`
|
|
|
|
|
|
|
|
|
|
#### ❌ 피해야 할 이름
|
|
|
|
|
|
|
|
|
|
- `CardGrid` (파스칼케이스)
|
|
|
|
|
- `card_grid` (스네이크케이스)
|
|
|
|
|
- `cardGrid` (카멜케이스)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 📂 생성된 파일 구조
|
|
|
|
|
|
|
|
|
|
CLI로 레이아웃을 생성하면 다음과 같은 파일들이 자동으로 생성됩니다:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
lib/registry/layouts/your-layout/
|
|
|
|
|
├── index.ts # 레이아웃 정의 및 등록
|
|
|
|
|
├── YourLayoutLayout.tsx # React 컴포넌트
|
|
|
|
|
├── YourLayoutRenderer.tsx # 렌더링 로직
|
|
|
|
|
├── config.ts # 기본 설정
|
|
|
|
|
├── types.ts # 타입 정의
|
|
|
|
|
└── README.md # 문서
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 🔧 각 파일의 역할
|
|
|
|
|
|
|
|
|
|
#### 1. `index.ts` - 레이아웃 정의
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export const YourLayoutDefinition = createLayoutDefinition({
|
|
|
|
|
id: "your-layout",
|
|
|
|
|
name: "yourLayout",
|
|
|
|
|
nameEng: "Your Layout",
|
|
|
|
|
description: "사용자 정의 레이아웃입니다",
|
|
|
|
|
category: "basic",
|
|
|
|
|
component: YourLayoutWrapper,
|
|
|
|
|
defaultConfig: {
|
|
|
|
|
/* 기본 설정 */
|
|
|
|
|
},
|
|
|
|
|
defaultZones: [
|
|
|
|
|
/* 기본 영역들 */
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 2. `YourLayoutLayout.tsx` - React 컴포넌트
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export const YourLayoutLayout: React.FC<YourLayoutProps> = ({ layout, isDesignMode, renderer, ...props }) => {
|
|
|
|
|
// 레이아웃 UI 구현
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 3. `YourLayoutRenderer.tsx` - 렌더링 로직
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export class YourLayoutRenderer extends AutoRegisteringLayoutRenderer {
|
|
|
|
|
static layoutDefinition = YourLayoutDefinition;
|
|
|
|
|
|
|
|
|
|
render(): React.ReactElement {
|
|
|
|
|
return <YourLayoutLayout {...this.props} renderer={this} />;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🎨 레이아웃 커스터마이징
|
|
|
|
|
|
|
|
|
|
### 1. 기본 구조 수정
|
|
|
|
|
|
|
|
|
|
생성된 `YourLayoutLayout.tsx`에서 레이아웃 구조를 정의합니다:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export const YourLayoutLayout: React.FC<YourLayoutProps> = ({
|
|
|
|
|
layout,
|
|
|
|
|
isDesignMode = false,
|
|
|
|
|
renderer,
|
|
|
|
|
}) => {
|
|
|
|
|
const yourLayoutConfig = layout.layoutConfig.yourLayout;
|
|
|
|
|
const containerStyle = renderer.getLayoutContainerStyle();
|
|
|
|
|
|
|
|
|
|
// 레이아웃별 커스텀 스타일
|
|
|
|
|
const yourLayoutStyle: React.CSSProperties = {
|
|
|
|
|
...containerStyle,
|
|
|
|
|
display: "grid",
|
|
|
|
|
gridTemplateColumns: "repeat(3, 1fr)",
|
|
|
|
|
gridTemplateRows: "repeat(2, 200px)",
|
|
|
|
|
gap: "16px",
|
|
|
|
|
padding: "16px",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div style={yourLayoutStyle}>
|
|
|
|
|
{layout.zones.map((zone) => {
|
|
|
|
|
const zoneChildren = renderer.getZoneChildren(zone.id);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div key={zone.id} className="zone-area">
|
|
|
|
|
{/* 존 렌더링 */}
|
|
|
|
|
{renderer.renderZone(zone, zoneChildren)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. 설정 옵션 추가
|
|
|
|
|
|
|
|
|
|
`config.ts`에서 레이아웃별 설정을 정의합니다:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export const YourLayoutConfig = {
|
|
|
|
|
defaultConfig: {
|
|
|
|
|
yourLayout: {
|
|
|
|
|
columns: 3, // 열 개수
|
|
|
|
|
rows: 2, // 행 개수
|
|
|
|
|
gap: 16, // 간격
|
|
|
|
|
aspectRatio: "16:9", // 비율
|
|
|
|
|
backgroundColor: "#ffffff",
|
|
|
|
|
borderRadius: "8px",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. 타입 정의
|
|
|
|
|
|
|
|
|
|
`types.ts`에서 설정 타입을 정의합니다:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export interface YourLayoutConfig {
|
|
|
|
|
columns?: number;
|
|
|
|
|
rows?: number;
|
|
|
|
|
gap?: number;
|
|
|
|
|
aspectRatio?: string;
|
|
|
|
|
backgroundColor?: string;
|
|
|
|
|
borderRadius?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface YourLayoutProps extends LayoutRendererProps {
|
|
|
|
|
renderer: YourLayoutRenderer;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
2025-09-11 12:22:39 +09:00
|
|
|
## 🎴 카드 레이아웃 상세 가이드
|
|
|
|
|
|
|
|
|
|
### 개요
|
|
|
|
|
|
|
|
|
|
카드 레이아웃은 **데이터 기반 레이아웃**으로, 실제 데이터베이스 테이블의 데이터를 카드 형태로 표시합니다. 두 가지 모드로 작동합니다:
|
|
|
|
|
|
|
|
|
|
- **디자인 모드**: 6개의 존이 있는 일반 레이아웃 (편집기에서)
|
|
|
|
|
- **실행 모드**: 테이블 데이터를 기반으로 한 동적 카드 생성
|
|
|
|
|
|
|
|
|
|
### 🔧 설정 방법
|
|
|
|
|
|
|
|
|
|
#### 1. 테이블 컬럼 매핑
|
|
|
|
|
|
|
|
|
|
카드의 각 부분에 어떤 테이블 컬럼을 표시할지 설정합니다:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
interface CardColumnMapping {
|
|
|
|
|
titleColumn?: string; // 카드 제목에 표시할 컬럼
|
|
|
|
|
subtitleColumn?: string; // 카드 서브타이틀에 표시할 컬럼
|
|
|
|
|
descriptionColumn?: string; // 카드 설명에 표시할 컬럼
|
|
|
|
|
imageColumn?: string; // 카드 이미지에 표시할 컬럼
|
|
|
|
|
displayColumns?: string[]; // 추가로 표시할 컬럼들
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 2. 카드 스타일 설정
|
|
|
|
|
|
|
|
|
|
카드의 외관을 세부적으로 조정할 수 있습니다:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
interface CardStyle {
|
|
|
|
|
showTitle?: boolean; // 타이틀 표시 여부
|
|
|
|
|
showSubtitle?: boolean; // 서브타이틀 표시 여부
|
|
|
|
|
showDescription?: boolean; // 설명 표시 여부
|
|
|
|
|
showImage?: boolean; // 이미지 표시 여부
|
|
|
|
|
maxDescriptionLength?: number; // 설명 최대 길이
|
|
|
|
|
cardsPerRow?: number; // 한 행당 카드 수
|
|
|
|
|
cardSpacing?: number; // 카드 간격 (px)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 📋 사용 가이드
|
|
|
|
|
|
|
|
|
|
#### 1. 카드 레이아웃 생성
|
|
|
|
|
|
|
|
|
|
1. **레이아웃 추가**: 템플릿에서 "카드 레이아웃" 선택
|
|
|
|
|
2. **테이블 선택**: 화면에서 사용할 테이블 선택
|
|
|
|
|
3. **컬럼 매핑**: 상세설정 패널에서 컬럼 매핑 설정
|
|
|
|
|
|
|
|
|
|
#### 2. 컬럼 매핑 설정
|
|
|
|
|
|
|
|
|
|
**상세설정 패널**에서 다음을 설정합니다:
|
|
|
|
|
|
|
|
|
|
1. **타이틀 컬럼**: 카드 제목으로 사용할 컬럼 (예: 상품명, 직원명)
|
|
|
|
|
2. **서브타이틀 컬럼**: 카드 부제목으로 사용할 컬럼 (예: 카테고리, 부서)
|
|
|
|
|
3. **설명 컬럼**: 카드 설명으로 사용할 컬럼 (예: 상세설명, 상태)
|
|
|
|
|
4. **이미지 컬럼**: 카드 이미지로 사용할 컬럼 (이미지 URL)
|
|
|
|
|
|
|
|
|
|
#### 3. 표시 컬럼 추가
|
|
|
|
|
|
|
|
|
|
추가로 표시하고 싶은 컬럼들을 동적으로 추가할 수 있습니다:
|
|
|
|
|
|
|
|
|
|
- **"+ 컬럼 추가"** 버튼 클릭
|
|
|
|
|
- 드롭다운에서 원하는 컬럼 선택
|
|
|
|
|
- 각 컬럼마다 **"삭제"** 버튼으로 제거 가능
|
|
|
|
|
|
|
|
|
|
#### 4. 카드 스타일 조정
|
|
|
|
|
|
|
|
|
|
- **한 행당 카드 수**: 1-6개 설정 가능
|
|
|
|
|
- **카드 간격**: 픽셀 단위로 조정
|
|
|
|
|
- **표시 옵션**: 타이틀, 서브타이틀, 설명, 이미지 개별 제어
|
|
|
|
|
- **설명 최대 길이**: 긴 설명 텍스트 자동 축약
|
|
|
|
|
|
|
|
|
|
### 🎨 카드 렌더링 로직
|
|
|
|
|
|
|
|
|
|
#### 라벨 우선 표시
|
|
|
|
|
|
|
|
|
|
컬럼명 대신 **라벨값을 우선 표시**합니다:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 컬럼 라벨 변환 함수
|
|
|
|
|
const getColumnLabel = (columnName: string) => {
|
|
|
|
|
const column = tableColumns.find((col) => col.columnName === columnName);
|
|
|
|
|
return column?.columnLabel || columnName; // 라벨이 있으면 라벨, 없으면 컬럼명
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 사용 예시
|
|
|
|
|
// 컬럼명: "reg_date" → 라벨: "등록일"
|
|
|
|
|
// 컬럼명: "user_name" → 라벨: "사용자명"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 데이터 가져오기
|
|
|
|
|
|
|
|
|
|
카드 레이아웃은 다음과 같이 데이터를 가져옵니다:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 테이블 데이터와 컬럼 정보를 병렬로 로드
|
|
|
|
|
const [dataResponse, columnsResponse] = await Promise.all([
|
|
|
|
|
tableTypeApi.getTableData(tableName, { page: 1, size: 50 }),
|
|
|
|
|
tableTypeApi.getColumns(tableName),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 카드 렌더링에 사용
|
|
|
|
|
setTableData(dataResponse.data);
|
|
|
|
|
setTableColumns(columnsResponse);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 카드 구조
|
|
|
|
|
|
|
|
|
|
각 카드는 다음과 같은 구조로 렌더링됩니다:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 카드 내용 구성
|
|
|
|
|
<div className="card">
|
|
|
|
|
{/* 이미지 (설정된 경우) */}
|
|
|
|
|
{showImage && imageColumn && (
|
|
|
|
|
<img src={getColumnValue(item, imageColumn)} />
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 타이틀 */}
|
|
|
|
|
{showTitle && titleColumn && (
|
|
|
|
|
<h3>{getColumnValue(item, titleColumn)}</h3>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 서브타이틀 */}
|
|
|
|
|
{showSubtitle && subtitleColumn && (
|
|
|
|
|
<p>{getColumnValue(item, subtitleColumn)}</p>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 설명 */}
|
|
|
|
|
{showDescription && descriptionColumn && (
|
|
|
|
|
<p>{truncateText(getColumnValue(item, descriptionColumn), maxLength)}</p>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 추가 표시 컬럼들 */}
|
|
|
|
|
{displayColumns?.map(columnName => (
|
|
|
|
|
<div key={columnName}>
|
|
|
|
|
<span>{getColumnLabel(columnName)}:</span>
|
|
|
|
|
<span>{getColumnValue(item, columnName)}</span>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 💡 활용 예시
|
|
|
|
|
|
|
|
|
|
#### 1. 직원 카드
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"columnMapping": {
|
|
|
|
|
"titleColumn": "emp_name", // 직원명
|
|
|
|
|
"subtitleColumn": "dept_name", // 부서명
|
|
|
|
|
"descriptionColumn": "position", // 직책
|
|
|
|
|
"imageColumn": "profile_image", // 프로필 사진
|
|
|
|
|
"displayColumns": ["email", "phone", "hire_date"] // 이메일, 전화번호, 입사일
|
|
|
|
|
},
|
|
|
|
|
"cardStyle": {
|
|
|
|
|
"cardsPerRow": 3,
|
|
|
|
|
"cardSpacing": 16,
|
|
|
|
|
"showTitle": true,
|
|
|
|
|
"showSubtitle": true,
|
|
|
|
|
"showDescription": true,
|
|
|
|
|
"showImage": true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 2. 상품 카드
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"columnMapping": {
|
|
|
|
|
"titleColumn": "product_name", // 상품명
|
|
|
|
|
"subtitleColumn": "category_name", // 카테고리
|
|
|
|
|
"descriptionColumn": "description", // 상품설명
|
|
|
|
|
"imageColumn": "product_image", // 상품 이미지
|
|
|
|
|
"displayColumns": ["price", "stock", "rating"] // 가격, 재고, 평점
|
|
|
|
|
},
|
|
|
|
|
"cardStyle": {
|
|
|
|
|
"cardsPerRow": 4,
|
|
|
|
|
"cardSpacing": 20,
|
|
|
|
|
"maxDescriptionLength": 80
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 3. 프로젝트 카드
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"columnMapping": {
|
|
|
|
|
"titleColumn": "project_name", // 프로젝트명
|
|
|
|
|
"subtitleColumn": "status", // 상태
|
|
|
|
|
"descriptionColumn": "description", // 프로젝트 설명
|
|
|
|
|
"displayColumns": ["start_date", "end_date", "manager", "progress"] // 시작일, 종료일, 담당자, 진행률
|
|
|
|
|
},
|
|
|
|
|
"cardStyle": {
|
|
|
|
|
"cardsPerRow": 2,
|
|
|
|
|
"cardSpacing": 24,
|
|
|
|
|
"showImage": false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 🔍 주요 특징
|
|
|
|
|
|
|
|
|
|
#### 1. **실시간 설정 반영**
|
|
|
|
|
|
|
|
|
|
- 상세설정 패널에서 설정 변경 시 즉시 카드에 반영
|
|
|
|
|
- 체크박스로 표시 요소 실시간 제어
|
|
|
|
|
- 컬럼 매핑 변경 시 즉시 업데이트
|
|
|
|
|
|
|
|
|
|
#### 2. **라벨 우선 표시 시스템**
|
|
|
|
|
|
|
|
|
|
- 데이터베이스 컬럼명 대신 사용자 친화적인 라벨 표시
|
|
|
|
|
- 라벨이 없는 경우 컬럼명으로 대체
|
|
|
|
|
- 드롭다운에서도 "라벨명 (데이터타입)" 형식으로 표시
|
|
|
|
|
|
|
|
|
|
#### 3. **동적 컬럼 관리**
|
|
|
|
|
|
|
|
|
|
- 표시할 컬럼을 자유롭게 추가/제거
|
|
|
|
|
- 각 컬럼별 개별 라벨 표시
|
|
|
|
|
- 컬럼 순서에 따른 표시 순서 보장
|
|
|
|
|
|
|
|
|
|
#### 4. **반응형 카드 레이아웃**
|
|
|
|
|
|
|
|
|
|
- 한 행당 카드 수 조정으로 반응형 구현
|
|
|
|
|
- 카드 간격 픽셀 단위 조정
|
|
|
|
|
- 자동 높이 조정 및 최소 높이 보장
|
|
|
|
|
|
|
|
|
|
#### 5. **데이터 안전성**
|
|
|
|
|
|
|
|
|
|
- 빈 데이터에 대한 폴백 처리
|
|
|
|
|
- 잘못된 컬럼 참조 시 오류 방지
|
|
|
|
|
- 고유한 key prop으로 React 렌더링 최적화
|
|
|
|
|
|
|
|
|
|
### 🚨 주의사항
|
|
|
|
|
|
|
|
|
|
1. **성능 고려**: 대량 데이터 시 페이징 처리 (현재 최대 50개)
|
|
|
|
|
2. **이미지 처리**: 이미지 URL이 유효하지 않을 경우 대체 이미지 표시
|
|
|
|
|
3. **텍스트 길이**: 긴 텍스트는 자동으로 축약 처리
|
|
|
|
|
4. **데이터 타입**: 날짜/시간 데이터의 경우 적절한 포맷팅 필요
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
2025-09-10 18:36:28 +09:00
|
|
|
## 🔧 고급 설정
|
|
|
|
|
|
2025-09-11 12:22:39 +09:00
|
|
|
### 영역(Zone) 관리 시스템
|
|
|
|
|
|
|
|
|
|
#### 존과 컴포넌트의 관계
|
|
|
|
|
|
|
|
|
|
레이아웃의 존(Zone)에 컴포넌트를 배치하면 다음과 같은 관계가 형성됩니다:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 존에 배치된 컴포넌트의 구조
|
|
|
|
|
interface ComponentInZone {
|
|
|
|
|
id: string;
|
|
|
|
|
parentId: string; // 레이아웃 컴포넌트 ID
|
|
|
|
|
zoneId: string; // 존 ID (새로 추가된 필드)
|
|
|
|
|
position: { x: number; y: number }; // 존 내부 상대 좌표
|
|
|
|
|
// ... 기타 속성들
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 존에 컴포넌트 배치하기
|
|
|
|
|
|
|
|
|
|
1. **드래그앤드롭으로 배치**
|
|
|
|
|
- 테이블이나 컬럼을 레이아웃의 존 영역에 드롭
|
|
|
|
|
- 자동으로 `parentId`와 `zoneId`가 설정됨
|
|
|
|
|
- 존 내부 상대 좌표로 위치 계산
|
|
|
|
|
|
|
|
|
|
2. **레이아웃 이동 시 함께 이동**
|
|
|
|
|
- 레이아웃 컴포넌트를 이동하면 존에 속한 모든 컴포넌트가 함께 이동
|
|
|
|
|
- 상대적 위치 관계 유지
|
|
|
|
|
- 드래그 중에도 실시간으로 함께 이동
|
|
|
|
|
|
|
|
|
|
#### 존 드롭 이벤트 처리
|
|
|
|
|
|
|
|
|
|
각 존은 독립적인 드롭존으로 작동합니다:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 존별 드롭 처리 예시
|
|
|
|
|
const handleZoneDrop = (e: React.DragEvent, zoneId: string) => {
|
|
|
|
|
const { type, table, column } = JSON.parse(e.dataTransfer.getData("application/json"));
|
|
|
|
|
|
|
|
|
|
const newComponent = {
|
|
|
|
|
id: generateId(),
|
|
|
|
|
type: "widget",
|
|
|
|
|
parentId: layoutId, // 레이아웃을 부모로 설정
|
|
|
|
|
zoneId: zoneId, // 존 ID 설정
|
|
|
|
|
position: {
|
|
|
|
|
// 존 내부 상대 좌표
|
|
|
|
|
x: e.clientX - dropZone.getBoundingClientRect().left,
|
|
|
|
|
y: e.clientY - dropZone.getBoundingClientRect().top,
|
|
|
|
|
},
|
|
|
|
|
// ... 기타 설정
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
2025-09-10 18:36:28 +09:00
|
|
|
### 영역(Zone) 커스터마이징
|
|
|
|
|
|
|
|
|
|
영역별로 다른 스타일을 적용하려면:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 영역별 스타일 계산
|
|
|
|
|
const getZoneStyle = (zone: LayoutZone, index: number): React.CSSProperties => {
|
|
|
|
|
const baseStyle = {
|
|
|
|
|
backgroundColor: "#f8f9fa",
|
|
|
|
|
border: "1px solid #e9ecef",
|
|
|
|
|
borderRadius: "4px",
|
|
|
|
|
padding: "12px",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 첫 번째 영역은 다른 스타일
|
|
|
|
|
if (index === 0) {
|
|
|
|
|
return {
|
|
|
|
|
...baseStyle,
|
|
|
|
|
backgroundColor: "#e3f2fd",
|
|
|
|
|
gridColumn: "1 / -1", // 전체 너비
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return baseStyle;
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 반응형 레이아웃
|
|
|
|
|
|
|
|
|
|
미디어 쿼리를 사용한 반응형 구현:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
const getResponsiveStyle = (): React.CSSProperties => {
|
|
|
|
|
return {
|
|
|
|
|
display: "grid",
|
|
|
|
|
gridTemplateColumns: `repeat(auto-fit, minmax(300px, 1fr))`,
|
|
|
|
|
gap: "16px",
|
|
|
|
|
// CSS-in-JS에서는 미디어 쿼리를 직접 사용할 수 없으므로
|
|
|
|
|
// CSS 클래스나 컨테이너 쿼리 사용 권장
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 애니메이션 추가
|
|
|
|
|
|
|
|
|
|
CSS 애니메이션을 포함한 레이아웃:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
const animatedStyle: React.CSSProperties = {
|
|
|
|
|
transition: "all 0.3s ease-in-out",
|
|
|
|
|
opacity: isDesignMode ? 0.9 : 1,
|
|
|
|
|
transform: isDesignMode ? "scale(0.98)" : "scale(1)",
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
2025-09-11 12:22:39 +09:00
|
|
|
### 높이 설정 및 styled-jsx 사용
|
|
|
|
|
|
|
|
|
|
모든 레이아웃에서 부모 높이를 올바르게 따르도록 하는 패턴:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
export const YourLayoutLayout: React.FC<YourLayoutProps> = ({ layout, isDesignMode, ...props }) => {
|
|
|
|
|
return (
|
|
|
|
|
<div className="layout-container">
|
|
|
|
|
{/* 레이아웃 컨텐츠 */}
|
|
|
|
|
|
|
|
|
|
<style jsx>{`
|
|
|
|
|
.layout-container {
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
min-height: 200px !important;
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
display: flex; /* 또는 grid */
|
|
|
|
|
flex-direction: column; /* 필요에 따라 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.zone-area {
|
|
|
|
|
flex: 1; /* 존이 사용 가능한 공간을 채우도록 */
|
|
|
|
|
height: 100%; /* 그리드 레이아웃의 경우 */
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 레이아웃별 높이 적용 패턴
|
|
|
|
|
|
|
|
|
|
**1. Flexbox 레이아웃**
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// FlexboxLayout.tsx
|
|
|
|
|
<style jsx>{`
|
|
|
|
|
.flexbox-container {
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
min-height: 200px !important;
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
display: flex !important;
|
|
|
|
|
flex-direction: ${flexDirection === "row" ? "row" : "column"} !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.flexbox-zone {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0; /* flexbox 오버플로우 방지 */
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**2. Grid 레이아웃**
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// GridLayout.tsx
|
|
|
|
|
<style jsx>{`
|
|
|
|
|
.grid-container {
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
min-height: 200px !important;
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
display: grid !important;
|
|
|
|
|
grid-template-columns: repeat(${columns}, 1fr);
|
|
|
|
|
grid-template-rows: repeat(${rows}, 1fr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.grid-zone {
|
|
|
|
|
height: 100%;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**3. Card 레이아웃**
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// CardLayoutLayout.tsx
|
|
|
|
|
<style jsx>{`
|
|
|
|
|
.card-container {
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
min-height: 200px !important;
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(${cardsPerRow}, 1fr);
|
|
|
|
|
gap: ${cardSpacing}px;
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 중요한 CSS 규칙
|
|
|
|
|
|
|
|
|
|
1. **`!important` 사용**: Tailwind나 다른 CSS 프레임워크의 스타일을 오버라이드
|
|
|
|
|
2. **`height: 100%`**: 부모의 높이를 완전히 따르도록 설정
|
|
|
|
|
3. **`min-height`**: 최소 높이 보장으로 너무 작아지지 않도록 방지
|
|
|
|
|
4. **`flex: 1`**: 플렉스 아이템이 사용 가능한 공간을 채우도록 설정
|
|
|
|
|
|
2025-09-10 18:36:28 +09:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🔄 자동 등록 시스템
|
|
|
|
|
|
|
|
|
|
### Hot Reload 지원
|
|
|
|
|
|
|
|
|
|
새 레이아웃은 다음과 같이 자동으로 등록됩니다:
|
|
|
|
|
|
|
|
|
|
1. **파일 저장 시**: Hot Reload로 즉시 반영
|
|
|
|
|
2. **자동 등록**: `AutoRegisteringLayoutRenderer` 상속으로 자동 등록
|
|
|
|
|
3. **즉시 사용**: 화면편집기에서 바로 사용 가능
|
|
|
|
|
|
|
|
|
|
### 수동 등록 (필요한 경우)
|
|
|
|
|
|
|
|
|
|
`lib/registry/layouts/index.ts`에 직접 추가:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 새 구조 레이아웃들 (자동 등록)
|
|
|
|
|
import "./your-layout/YourLayoutRenderer";
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🛠️ 문제 해결
|
|
|
|
|
|
|
|
|
|
### 자주 발생하는 오류
|
|
|
|
|
|
|
|
|
|
#### 1. "Cannot read properties of undefined" 오류
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ❌ 문제: 설정이 없을 때 오류
|
|
|
|
|
const config = layout.layoutConfig.yourLayout.someProperty;
|
|
|
|
|
|
|
|
|
|
// ✅ 해결: 안전한 접근
|
|
|
|
|
const config = layout.layoutConfig.yourLayout?.someProperty || defaultValue;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 2. "React does not recognize prop" 경고
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ❌ 문제: 모든 props를 DOM에 전달
|
|
|
|
|
<div {...props}>
|
|
|
|
|
|
|
|
|
|
// ✅ 해결: DOM props만 전달
|
|
|
|
|
const { layout, isDesignMode, renderer, ...domProps } = props;
|
|
|
|
|
<div {...domProps}>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 3. 레이아웃이 화면편집기에 나타나지 않음
|
|
|
|
|
|
|
|
|
|
1. **파일 저장 확인**: 모든 파일이 저장되었는지 확인
|
|
|
|
|
2. **자동 등록 확인**: `YourLayoutRenderer.registerSelf()` 호출 여부
|
|
|
|
|
3. **브라우저 새로고침**: 캐시 문제일 수 있음
|
|
|
|
|
4. **개발자 도구**: `window.__LAYOUT_REGISTRY__.list()` 로 등록 상태 확인
|
|
|
|
|
|
2025-09-11 12:22:39 +09:00
|
|
|
#### 4. 레이아웃 높이가 적용되지 않음
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ❌ 문제: 인라인 스타일만 사용
|
|
|
|
|
<div style={{ height: "100%" }}>
|
|
|
|
|
|
|
|
|
|
// ✅ 해결: styled-jsx로 강제 적용
|
|
|
|
|
<div className="layout-container">
|
|
|
|
|
<style jsx>{`
|
|
|
|
|
.layout-container {
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
min-height: 200px !important;
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
</div>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 5. Flexbox 방향 설정이 적용되지 않음
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ❌ 문제: display: block이 flex를 오버라이드
|
|
|
|
|
<div style={{ display: "flex", flexDirection: "row" }}>
|
|
|
|
|
|
|
|
|
|
// ✅ 해결: !important로 강제 적용
|
|
|
|
|
<style jsx>{`
|
|
|
|
|
.flexbox-container {
|
|
|
|
|
display: flex !important;
|
|
|
|
|
flex-direction: ${direction === "row" ? "row" : "column"} !important;
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 6. 카드 레이아웃에서 데이터가 표시되지 않음
|
|
|
|
|
|
|
|
|
|
1. **테이블 선택 확인**: 화면에서 테이블이 선택되었는지 확인
|
|
|
|
|
2. **컬럼 매핑 설정**: 상세설정에서 최소한 타이틀 컬럼은 설정
|
|
|
|
|
3. **체크박스 상태**: "타이틀 표시", "서브타이틀 표시" 등이 체크되었는지 확인
|
|
|
|
|
4. **API 호출 확인**: 브라우저 개발자 도구에서 API 호출 성공 여부 확인
|
|
|
|
|
|
|
|
|
|
#### 7. React Key Prop 경고
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ❌ 문제: 고유하지 않은 key
|
|
|
|
|
{items.map((item, index) => (
|
|
|
|
|
<div key={item.id}> // item.id가 undefined일 수 있음
|
|
|
|
|
|
|
|
|
|
// ✅ 해결: 안전한 key 생성
|
|
|
|
|
{items.map((item, index) => (
|
|
|
|
|
<div key={item.objid || item.id || item.company_code || `item-${index}`}>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 8. 라벨이 컬럼명으로 표시되는 문제
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ❌ 문제: 컬럼명 직접 사용
|
|
|
|
|
<span>{columnName}:</span>
|
|
|
|
|
|
|
|
|
|
// ✅ 해결: 라벨 우선 표시
|
|
|
|
|
const getColumnLabel = (columnName: string) => {
|
|
|
|
|
const column = tableColumns.find(col => col.columnName === columnName);
|
|
|
|
|
return column?.columnLabel || columnName;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
<span>{getColumnLabel(columnName)}:</span>
|
|
|
|
|
```
|
|
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
#### 9. 레이아웃 드롭 시 오류 발생 문제 (9월 11일 해결됨)
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ❌ 문제: 복잡한 존별 드롭 로직으로 인한 오류
|
|
|
|
|
- Runtime TypeError: Cannot read properties of undefined (reading 'width')
|
|
|
|
|
- 다중선택 기능 중단
|
|
|
|
|
- 존별 드롭 이벤트 충돌
|
|
|
|
|
|
|
|
|
|
// ✅ 해결: 드롭 시스템 완전 단순화
|
|
|
|
|
- 모든 존별 드롭 로직 제거
|
|
|
|
|
- 일반 캔버스 드롭만 사용
|
|
|
|
|
- 레이아웃은 시각적 가이드 역할만
|
|
|
|
|
- z-index 기반 레이어 분리 (레이아웃=1, 컴포넌트=2+)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
##### 드롭 시스템 단순화 후 장점
|
|
|
|
|
|
|
|
|
|
- ✅ **안정성**: 복잡한 이벤트 체인 제거로 오류 가능성 감소
|
|
|
|
|
- ✅ **일관성**: 모든 영역에서 동일한 드롭 동작
|
|
|
|
|
- ✅ **성능**: 불필요한 prop 전달 및 매핑 로직 제거
|
|
|
|
|
- ✅ **유지보수**: 단순한 구조로 디버깅 및 수정 용이
|
|
|
|
|
|
|
|
|
|
##### 새로운 레이아웃 개발 시 주의사항
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ✅ 올바른 레이아웃 구현
|
|
|
|
|
export const YourLayoutLayout: React.FC<YourLayoutProps> = ({ layout, isDesignMode, ...props }) => {
|
|
|
|
|
// 🚫 존별 드롭 이벤트 구현 금지
|
|
|
|
|
// onDrop, onDragOver 등 드롭 관련 이벤트 추가하지 않음
|
2025-09-11 18:38:28 +09:00
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
return (
|
|
|
|
|
<div className="your-layout">
|
|
|
|
|
{layout.zones.map((zone) => (
|
2025-09-11 18:38:28 +09:00
|
|
|
<div
|
|
|
|
|
key={zone.id}
|
2025-09-11 16:21:00 +09:00
|
|
|
className="zone-area"
|
|
|
|
|
// 🚫 드롭 이벤트 추가 금지
|
|
|
|
|
// onDrop={...} ❌
|
|
|
|
|
// onDragOver={...} ❌
|
|
|
|
|
>
|
|
|
|
|
{/* 존 내용 */}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
2025-09-11 18:38:28 +09:00
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
<style jsx>{`
|
|
|
|
|
.your-layout {
|
|
|
|
|
/* z-index는 1로 고정 (레이아웃 레이어) */
|
|
|
|
|
z-index: 1;
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
2025-09-10 18:36:28 +09:00
|
|
|
### 디버깅 도구
|
|
|
|
|
|
|
|
|
|
#### 브라우저 개발자 도구
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
// 등록된 레이아웃 목록 확인
|
|
|
|
|
window.__LAYOUT_REGISTRY__.list();
|
|
|
|
|
|
|
|
|
|
// 특정 레이아웃 정보 확인
|
|
|
|
|
window.__LAYOUT_REGISTRY__.get("your-layout");
|
|
|
|
|
|
|
|
|
|
// 레지스트리 통계
|
|
|
|
|
window.__LAYOUT_REGISTRY__.stats();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 📖 예시: 완전한 레이아웃 생성
|
|
|
|
|
|
|
|
|
|
### 1. CLI로 생성
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
node scripts/create-layout.js pricing-table --category=content --zones=4 --description="가격표 레이아웃" --author="개발팀"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. 생성 결과
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
✅ 레이아웃 생성 완료!
|
|
|
|
|
📁 이름: pricingTable
|
|
|
|
|
🔖 ID: pricing-table
|
|
|
|
|
📂 카테고리: content
|
|
|
|
|
🎯 존 개수: 4
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. 커스터마이징
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// PricingTableLayout.tsx
|
|
|
|
|
export const PricingTableLayout: React.FC<PricingTableLayoutProps> = ({
|
|
|
|
|
layout,
|
|
|
|
|
isDesignMode,
|
|
|
|
|
renderer,
|
|
|
|
|
}) => {
|
|
|
|
|
const pricingTableStyle: React.CSSProperties = {
|
|
|
|
|
display: "grid",
|
|
|
|
|
gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))",
|
|
|
|
|
gap: "24px",
|
|
|
|
|
padding: "32px",
|
|
|
|
|
backgroundColor: "#f8f9fa",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div style={pricingTableStyle}>
|
|
|
|
|
{layout.zones.map((zone, index) => (
|
|
|
|
|
<div
|
|
|
|
|
key={zone.id}
|
|
|
|
|
className={`pricing-card ${index === 1 ? 'featured' : ''}`}
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: "white",
|
|
|
|
|
borderRadius: "12px",
|
|
|
|
|
padding: "24px",
|
|
|
|
|
boxShadow: index === 1 ? "0 8px 32px rgba(0,0,0,0.1)" : "0 2px 8px rgba(0,0,0,0.1)",
|
|
|
|
|
border: index === 1 ? "2px solid #3b82f6" : "1px solid #e5e7eb",
|
|
|
|
|
transform: index === 1 ? "scale(1.05)" : "scale(1)",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{renderer.renderZone(zone, renderer.getZoneChildren(zone.id))}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4. 즉시 사용 가능
|
|
|
|
|
|
|
|
|
|
레이아웃이 자동으로 등록되어 화면편집기에서 바로 사용할 수 있습니다!
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
2025-09-11 12:22:39 +09:00
|
|
|
## 🎯 존 관리 시스템의 장점
|
|
|
|
|
|
|
|
|
|
### 1. 구조적 레이아웃 관리
|
|
|
|
|
|
|
|
|
|
- **논리적 그룹화**: 관련된 컴포넌트들을 존별로 그룹화
|
|
|
|
|
- **일관된 이동**: 레이아웃 이동 시 모든 하위 컴포넌트가 함께 이동
|
|
|
|
|
- **상대적 위치**: 존 내부에서의 상대적 위치 관계 유지
|
|
|
|
|
|
|
|
|
|
### 2. 직관적인 사용자 경험
|
|
|
|
|
|
|
|
|
|
- **드래그앤드롭**: 존 영역에 직접 드롭하여 배치
|
|
|
|
|
- **시각적 피드백**: 존 경계와 라벨로 명확한 구분
|
|
|
|
|
- **실시간 미리보기**: 드래그 중에도 함께 이동하는 모습 확인
|
|
|
|
|
|
|
|
|
|
### 3. 개발자 친화적
|
|
|
|
|
|
|
|
|
|
- **자동 관계 설정**: `parentId`와 `zoneId` 자동 할당
|
|
|
|
|
- **타입 안전성**: TypeScript로 완전한 타입 지원
|
|
|
|
|
- **확장 가능성**: 새로운 존 타입 쉽게 추가 가능
|
|
|
|
|
|
|
|
|
|
## 🔍 사용 예시
|
|
|
|
|
|
|
|
|
|
### 대시보드 레이아웃 예시
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# 대시보드 레이아웃 생성
|
|
|
|
|
node scripts/create-layout.js dashboard-layout --category=dashboard --zones=4 --description="대시보드 레이아웃"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
생성된 레이아웃에서:
|
|
|
|
|
|
|
|
|
|
1. **헤더 존**: 제목, 네비게이션 컴포넌트 배치
|
|
|
|
|
2. **사이드바 존**: 메뉴, 필터 컴포넌트 배치
|
|
|
|
|
3. **메인 존**: 차트, 테이블 컴포넌트 배치
|
|
|
|
|
4. **푸터 존**: 상태 정보, 액션 버튼 배치
|
|
|
|
|
|
|
|
|
|
### 폼 레이아웃 예시
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# 폼 레이아웃 생성
|
|
|
|
|
node scripts/create-layout.js form-layout --category=form --zones=3 --description="폼 레이아웃"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
생성된 레이아웃에서:
|
|
|
|
|
|
|
|
|
|
1. **입력 존**: 텍스트 필드, 선택박스 배치
|
|
|
|
|
2. **첨부 존**: 파일 업로드 컴포넌트 배치
|
|
|
|
|
3. **액션 존**: 저장, 취소 버튼 배치
|
|
|
|
|
|
2025-09-10 18:36:28 +09:00
|
|
|
## 🎯 마무리
|
|
|
|
|
|
2025-09-11 12:22:39 +09:00
|
|
|
새로운 CLI 방식과 존 관리 시스템, 그리고 데이터 기반 레이아웃으로 화면관리 시스템이 혁신적으로 개선되었습니다:
|
|
|
|
|
|
|
|
|
|
### 🚀 핵심 기능
|
2025-09-10 18:36:28 +09:00
|
|
|
|
|
|
|
|
1. **한 줄 명령어**로 모든 파일 자동 생성
|
2025-09-11 12:22:39 +09:00
|
|
|
2. **타입 안전성** 보장 (TypeScript 완전 지원)
|
2025-09-10 18:36:28 +09:00
|
|
|
3. **자동 등록**으로 즉시 사용 가능
|
|
|
|
|
4. **Hot Reload** 지원으로 빠른 개발
|
2025-09-11 12:22:39 +09:00
|
|
|
5. **존 관리 시스템**으로 구조적 레이아웃 관리
|
|
|
|
|
6. **데이터 기반 렌더링** (카드 레이아웃)
|
|
|
|
|
7. **동적 존 개수 설정** (Flexbox, Grid)
|
|
|
|
|
8. **높이 자동 적용** (styled-jsx + !important)
|
|
|
|
|
|
|
|
|
|
### 🎨 사용자 경험
|
|
|
|
|
|
|
|
|
|
- **직관적 드래그앤드롭**: 존에 직접 컴포넌트 배치
|
|
|
|
|
- **일관된 이동**: 레이아웃과 하위 컴포넌트가 함께 이동
|
|
|
|
|
- **시각적 피드백**: 명확한 존 경계와 라벨 표시
|
|
|
|
|
- **실시간 설정**: 체크박스로 즉시 표시 제어
|
|
|
|
|
- **라벨 우선 표시**: 컬럼명 대신 사용자 친화적 라벨
|
|
|
|
|
|
|
|
|
|
### 🔧 개발자 경험
|
|
|
|
|
|
|
|
|
|
- **자동화된 설정**: 복잡한 관계 설정이 자동으로 처리
|
|
|
|
|
- **확장 가능한 구조**: 새로운 레이아웃 타입 쉽게 추가
|
|
|
|
|
- **완전한 타입 지원**: TypeScript로 안전한 개발
|
|
|
|
|
- **문제 해결 가이드**: 자주 발생하는 문제들의 해결책 제공
|
|
|
|
|
- **성능 최적화**: React key, 메모이제이션 등 적용
|
|
|
|
|
|
|
|
|
|
### 🆕 최신 업데이트 (2025.09.11)
|
|
|
|
|
|
|
|
|
|
#### 카드 레이아웃 고도화
|
|
|
|
|
|
|
|
|
|
- ✅ **실제 데이터 연동**: 테이블 데이터를 실시간으로 카드에 표시
|
|
|
|
|
- ✅ **컬럼 매핑 시스템**: 타이틀, 서브타이틀, 설명 등 자유로운 매핑
|
|
|
|
|
- ✅ **동적 표시 컬럼**: 필요한 컬럼을 추가/제거하며 동적 관리
|
|
|
|
|
- ✅ **라벨 우선 표시**: 데이터베이스 컬럼명 대신 사용자 친화적 라벨
|
|
|
|
|
- ✅ **실시간 설정 반영**: 체크박스 토글로 즉시 카드 업데이트
|
|
|
|
|
|
|
|
|
|
#### 레이아웃 시스템 개선
|
|
|
|
|
|
|
|
|
|
- ✅ **높이 적용 문제 해결**: 모든 레이아웃에서 부모 높이 완벽 적용
|
|
|
|
|
- ✅ **Flexbox 방향 설정**: 수평/수직 방향 정상 작동
|
|
|
|
|
- ✅ **동적 존 개수**: Grid, Flexbox에서 존 개수 실시간 조정
|
|
|
|
|
- ✅ **시각적 피드백**: 모든 레이아웃에서 존 경계 명확히 표시
|
|
|
|
|
- ✅ **React 경고 해결**: Key prop, DOM prop 전달 등 모든 경고 해결
|
|
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
#### 🔧 드롭 시스템 대폭 단순화 (9월 11일 추가)
|
|
|
|
|
|
|
|
|
|
기존의 복잡한 존별 드롭 시스템을 완전히 제거하고 단순한 캔버스 드롭 방식으로 통일했습니다:
|
|
|
|
|
|
|
|
|
|
##### 변경 사항
|
|
|
|
|
|
|
|
|
|
- ✅ **복잡한 드롭 로직 제거**: `onZoneComponentDrop`, `handleZoneComponentDrop` 등 모든 존별 드롭 이벤트 제거
|
|
|
|
|
- ✅ **일반 캔버스 드롭만 사용**: 모든 드롭이 `handleComponentDrop`으로 통일
|
|
|
|
|
- ✅ **레이아웃은 시각적 가이드 역할**: 레이아웃 존은 배치 가이드라인 역할만 수행
|
|
|
|
|
- ✅ **z-index 기반 레이어링**: 레이아웃 z-index=1, 컴포넌트 z-index=2+로 설정
|
|
|
|
|
- ✅ **prop 전달 체인 단순화**: 불필요한 prop 매핑 및 전달 로직 제거
|
|
|
|
|
|
|
|
|
|
##### 새로운 동작 방식
|
|
|
|
|
|
|
|
|
|
**이전 (복잡한 방식)**:
|
2025-09-11 18:38:28 +09:00
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
```
|
|
|
|
|
컴포넌트 드래그 → 레이아웃 존 감지 → 존별 드롭 이벤트 → 복잡한 매핑 → 오류 발생
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**현재 (단순한 방식)**:
|
2025-09-11 18:38:28 +09:00
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
```
|
|
|
|
|
컴포넌트 드래그 → 캔버스에 드롭 → 일반 handleComponentDrop만 실행 → 안정적 동작
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
##### 해결된 문제들
|
|
|
|
|
|
|
|
|
|
- ✅ **Runtime TypeError 해결**: `selectedComponent.size.width` undefined 오류 완전 해결
|
|
|
|
|
- ✅ **다중선택 복구**: 드래그로 다중선택 기능 정상화
|
|
|
|
|
- ✅ **안정적 드롭**: 레이아웃 위든 어디든 일관된 드롭 동작
|
|
|
|
|
- ✅ **코드 단순화**: 복잡한 존별 로직 제거로 유지보수성 향상
|
|
|
|
|
|
|
|
|
|
##### 기술적 변경점
|
|
|
|
|
|
|
|
|
|
1. **GridLayout, FlexboxLayout**: `onDragOver`, `onDrop` 이벤트 핸들러 제거
|
|
|
|
|
2. **ScreenDesigner**: `onZoneComponentDrop` prop 전달 제거
|
|
|
|
|
3. **DynamicComponentRenderer**: `onComponentDrop` 매핑 로직 제거
|
|
|
|
|
4. **DynamicLayoutRenderer**: 존별 prop 전달 제거
|
|
|
|
|
5. **RealtimePreviewDynamic**: z-index 기반 레이어링 적용
|
|
|
|
|
|
|
|
|
|
##### 개발자 가이드
|
|
|
|
|
|
|
|
|
|
새로운 시스템에서는:
|
2025-09-11 18:38:28 +09:00
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
- 🚫 **존별 드롭 로직 구현 금지**: 모든 드롭은 캔버스 레벨에서 처리
|
|
|
|
|
- ✅ **시각적 가이드만 제공**: 레이아웃은 배치 가이드라인 역할만
|
|
|
|
|
- ✅ **z-index로 레이어 관리**: 레이아웃=1, 컴포넌트=2+ 설정
|
|
|
|
|
- ✅ **단순한 이벤트 처리**: 복잡한 이벤트 체인 대신 직접적인 핸들링
|
|
|
|
|
|
2025-09-11 12:22:39 +09:00
|
|
|
#### 개발자 도구 강화
|
|
|
|
|
|
|
|
|
|
- ✅ **디버깅 로그**: 카드 설정 로드, 데이터 가져오기 등 상세 로깅
|
|
|
|
|
- ✅ **에러 핸들링**: API 함수명 오류, 데이터 타입 불일치 등 완벽 처리
|
|
|
|
|
- ✅ **타입 안전성**: 모든 컴포넌트와 설정에 완전한 TypeScript 지원
|
|
|
|
|
|
|
|
|
|
### 📈 지원하는 레이아웃 현황
|
|
|
|
|
|
|
|
|
|
1. **Grid Layout** - 격자 형태 기본 레이아웃
|
|
|
|
|
2. **Flexbox Layout** - 유연한 방향 설정 가능
|
|
|
|
|
3. **Split Layout** - 좌우/상하 분할
|
|
|
|
|
4. **Accordion Layout** - 접기/펼치기 네비게이션
|
|
|
|
|
5. **Card Layout** - 데이터 기반 카드 표시 ⭐
|
|
|
|
|
6. **Hero Section Layout** - 대형 헤더 섹션
|
|
|
|
|
|
|
|
|
|
### 🔮 향후 계획
|
|
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
#### 새로운 레이아웃 타입
|
2025-09-11 18:38:28 +09:00
|
|
|
|
2025-09-11 12:22:39 +09:00
|
|
|
- **Table Layout**: 데이터 테이블 전용 레이아웃
|
|
|
|
|
- **Form Layout**: 폼 입력에 최적화된 레이아웃
|
|
|
|
|
- **Dashboard Layout**: 위젯 배치에 특화된 레이아웃
|
|
|
|
|
- **Mobile Responsive**: 모바일 대응 반응형 레이아웃
|
2025-09-10 18:36:28 +09:00
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
#### 시스템 개선
|
2025-09-11 18:38:28 +09:00
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
- **레이아웃 테마 시스템**: 다크/라이트 모드 지원
|
|
|
|
|
- **레이아웃 스타일 프리셋**: 미리 정의된 스타일 템플릿
|
|
|
|
|
- **레이아웃 애니메이션**: 전환 효과 및 인터랙션 개선
|
|
|
|
|
- **성능 최적화**: 가상화 및 지연 로딩 적용
|
|
|
|
|
|
|
|
|
|
#### 개발자 도구
|
2025-09-11 18:38:28 +09:00
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
- **레이아웃 빌더 GUI**: 코드 없이 레이아웃 생성 도구
|
|
|
|
|
- **실시간 프리뷰**: 레이아웃 편집 중 실시간 미리보기
|
|
|
|
|
- **레이아웃 디버거**: 시각적 디버깅 도구
|
|
|
|
|
- **성능 모니터링**: 레이아웃 렌더링 성능 분석
|
|
|
|
|
|
|
|
|
|
### 🎯 중요한 변화: 단순화된 드롭 시스템
|
|
|
|
|
|
2025-09-11 18:38:28 +09:00
|
|
|
**2025년 9월 11일**부터 모든 레이아웃에서 **복잡한 존별 드롭 로직이 완전히 제거**되었습니다.
|
2025-09-11 16:21:00 +09:00
|
|
|
|
|
|
|
|
새로운 시스템의 핵심 원칙:
|
2025-09-11 18:38:28 +09:00
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
- 🎯 **레이아웃 = 시각적 가이드**: 배치 참고용으로만 사용
|
|
|
|
|
- 🎯 **캔버스 = 실제 배치**: 모든 컴포넌트는 캔버스에 자유롭게 배치
|
|
|
|
|
- 🎯 **z-index = 레이어 분리**: 레이아웃(1) 위에 컴포넌트(2+) 배치
|
|
|
|
|
- 🎯 **단순함 = 안정성**: 복잡한 로직 제거로 오류 최소화
|
|
|
|
|
|
|
|
|
|
이 변화로 인해:
|
2025-09-11 18:38:28 +09:00
|
|
|
|
2025-09-11 16:21:00 +09:00
|
|
|
- ✅ 모든 드롭 관련 오류 해결
|
|
|
|
|
- ✅ 다중선택 기능 정상화
|
|
|
|
|
- ✅ 레이아웃 개발이 더욱 단순해짐
|
|
|
|
|
- ✅ 시스템 전체 안정성 크게 향상
|
|
|
|
|
|
2025-09-11 12:22:39 +09:00
|
|
|
더 자세한 정보가 필요하면 각 레이아웃의 `README.md` 파일을 참고하거나, 브라우저 개발자 도구에서 `window.__LAYOUT_REGISTRY__.help()`를 실행해보세요! 🚀
|