ERP-node/frontend/docs/레이아웃_추가_가이드.md

35 KiB

레이아웃 추가 가이드

화면관리 시스템에서 새로운 레이아웃을 추가하는 방법을 설명합니다.

📋 목차

  1. 현재 지원하는 레이아웃
  2. CLI를 이용한 자동 생성
  3. 생성된 파일 구조
  4. 레이아웃 커스터마이징
  5. 카드 레이아웃 상세 가이드
  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개
  • 특징: 대형 헤더 섹션과 하위 영역들
  • 용도: 랜딩 페이지, 제품 소개

🚀 CLI를 이용한 자동 생성

기본 사용법

# 기본 형태
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로 입력하면 자동으로 변환됩니다:

# 입력: 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 - 레이아웃 정의

export const YourLayoutDefinition = createLayoutDefinition({
  id: "your-layout",
  name: "yourLayout",
  nameEng: "Your Layout",
  description: "사용자 정의 레이아웃입니다",
  category: "basic",
  component: YourLayoutWrapper,
  defaultConfig: {
    /* 기본 설정 */
  },
  defaultZones: [
    /* 기본 영역들 */
  ],
});

2. YourLayoutLayout.tsx - React 컴포넌트

export const YourLayoutLayout: React.FC<YourLayoutProps> = ({ layout, isDesignMode, renderer, ...props }) => {
  // 레이아웃 UI 구현
};

3. YourLayoutRenderer.tsx - 렌더링 로직

export class YourLayoutRenderer extends AutoRegisteringLayoutRenderer {
  static layoutDefinition = YourLayoutDefinition;

  render(): React.ReactElement {
    return <YourLayoutLayout {...this.props} renderer={this} />;
  }
}

🎨 레이아웃 커스터마이징

1. 기본 구조 수정

생성된 YourLayoutLayout.tsx에서 레이아웃 구조를 정의합니다:

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에서 레이아웃별 설정을 정의합니다:

export const YourLayoutConfig = {
  defaultConfig: {
    yourLayout: {
      columns: 3, // 열 개수
      rows: 2, // 행 개수
      gap: 16, // 간격
      aspectRatio: "16:9", // 비율
      backgroundColor: "#ffffff",
      borderRadius: "8px",
    },
  },
};

3. 타입 정의

types.ts에서 설정 타입을 정의합니다:

export interface YourLayoutConfig {
  columns?: number;
  rows?: number;
  gap?: number;
  aspectRatio?: string;
  backgroundColor?: string;
  borderRadius?: string;
}

export interface YourLayoutProps extends LayoutRendererProps {
  renderer: YourLayoutRenderer;
}

🎴 카드 레이아웃 상세 가이드

개요

카드 레이아웃은 데이터 기반 레이아웃으로, 실제 데이터베이스 테이블의 데이터를 카드 형태로 표시합니다. 두 가지 모드로 작동합니다:

  • 디자인 모드: 6개의 존이 있는 일반 레이아웃 (편집기에서)
  • 실행 모드: 테이블 데이터를 기반으로 한 동적 카드 생성

🔧 설정 방법

1. 테이블 컬럼 매핑

카드의 각 부분에 어떤 테이블 컬럼을 표시할지 설정합니다:

interface CardColumnMapping {
  titleColumn?: string; // 카드 제목에 표시할 컬럼
  subtitleColumn?: string; // 카드 서브타이틀에 표시할 컬럼
  descriptionColumn?: string; // 카드 설명에 표시할 컬럼
  imageColumn?: string; // 카드 이미지에 표시할 컬럼
  displayColumns?: string[]; // 추가로 표시할 컬럼들
}

2. 카드 스타일 설정

카드의 외관을 세부적으로 조정할 수 있습니다:

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개 설정 가능
  • 카드 간격: 픽셀 단위로 조정
  • 표시 옵션: 타이틀, 서브타이틀, 설명, 이미지 개별 제어
  • 설명 최대 길이: 긴 설명 텍스트 자동 축약

🎨 카드 렌더링 로직

라벨 우선 표시

컬럼명 대신 라벨값을 우선 표시합니다:

// 컬럼 라벨 변환 함수
const getColumnLabel = (columnName: string) => {
  const column = tableColumns.find((col) => col.columnName === columnName);
  return column?.columnLabel || columnName; // 라벨이 있으면 라벨, 없으면 컬럼명
};

// 사용 예시
// 컬럼명: "reg_date" → 라벨: "등록일"
// 컬럼명: "user_name" → 라벨: "사용자명"

데이터 가져오기

카드 레이아웃은 다음과 같이 데이터를 가져옵니다:

// 테이블 데이터와 컬럼 정보를 병렬로 로드
const [dataResponse, columnsResponse] = await Promise.all([
  tableTypeApi.getTableData(tableName, { page: 1, size: 50 }),
  tableTypeApi.getColumns(tableName),
]);

// 카드 렌더링에 사용
setTableData(dataResponse.data);
setTableColumns(columnsResponse);

카드 구조

각 카드는 다음과 같은 구조로 렌더링됩니다:

// 카드 내용 구성
<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. 직원 카드

{
  "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. 상품 카드

{
  "columnMapping": {
    "titleColumn": "product_name", // 상품명
    "subtitleColumn": "category_name", // 카테고리
    "descriptionColumn": "description", // 상품설명
    "imageColumn": "product_image", // 상품 이미지
    "displayColumns": ["price", "stock", "rating"] // 가격, 재고, 평점
  },
  "cardStyle": {
    "cardsPerRow": 4,
    "cardSpacing": 20,
    "maxDescriptionLength": 80
  }
}

3. 프로젝트 카드

{
  "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. 데이터 타입: 날짜/시간 데이터의 경우 적절한 포맷팅 필요

🔧 고급 설정

영역(Zone) 관리 시스템

존과 컴포넌트의 관계

레이아웃의 존(Zone)에 컴포넌트를 배치하면 다음과 같은 관계가 형성됩니다:

// 존에 배치된 컴포넌트의 구조
interface ComponentInZone {
  id: string;
  parentId: string; // 레이아웃 컴포넌트 ID
  zoneId: string; // 존 ID (새로 추가된 필드)
  position: { x: number; y: number }; // 존 내부 상대 좌표
  // ... 기타 속성들
}

존에 컴포넌트 배치하기

  1. 드래그앤드롭으로 배치

    • 테이블이나 컬럼을 레이아웃의 존 영역에 드롭
    • 자동으로 parentIdzoneId가 설정됨
    • 존 내부 상대 좌표로 위치 계산
  2. 레이아웃 이동 시 함께 이동

    • 레이아웃 컴포넌트를 이동하면 존에 속한 모든 컴포넌트가 함께 이동
    • 상대적 위치 관계 유지
    • 드래그 중에도 실시간으로 함께 이동

존 드롭 이벤트 처리

각 존은 독립적인 드롭존으로 작동합니다:

// 존별 드롭 처리 예시
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,
    },
    // ... 기타 설정
  };
};

영역(Zone) 커스터마이징

영역별로 다른 스타일을 적용하려면:

// 영역별 스타일 계산
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;
};

반응형 레이아웃

미디어 쿼리를 사용한 반응형 구현:

const getResponsiveStyle = (): React.CSSProperties => {
  return {
    display: "grid",
    gridTemplateColumns: `repeat(auto-fit, minmax(300px, 1fr))`,
    gap: "16px",
    // CSS-in-JS에서는 미디어 쿼리를 직접 사용할 수 없으므로
    // CSS 클래스나 컨테이너 쿼리 사용 권장
  };
};

애니메이션 추가

CSS 애니메이션을 포함한 레이아웃:

const animatedStyle: React.CSSProperties = {
  transition: "all 0.3s ease-in-out",
  opacity: isDesignMode ? 0.9 : 1,
  transform: isDesignMode ? "scale(0.98)" : "scale(1)",
};

높이 설정 및 styled-jsx 사용

모든 레이아웃에서 부모 높이를 올바르게 따르도록 하는 패턴:

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 레이아웃

// 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 레이아웃

// 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 레이아웃

// 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: 플렉스 아이템이 사용 가능한 공간을 채우도록 설정

🔄 자동 등록 시스템

Hot Reload 지원

새 레이아웃은 다음과 같이 자동으로 등록됩니다:

  1. 파일 저장 시: Hot Reload로 즉시 반영
  2. 자동 등록: AutoRegisteringLayoutRenderer 상속으로 자동 등록
  3. 즉시 사용: 화면편집기에서 바로 사용 가능

수동 등록 (필요한 경우)

lib/registry/layouts/index.ts에 직접 추가:

// 새 구조 레이아웃들 (자동 등록)
import "./your-layout/YourLayoutRenderer";

🛠️ 문제 해결

자주 발생하는 오류

1. "Cannot read properties of undefined" 오류

// ❌ 문제: 설정이 없을 때 오류
const config = layout.layoutConfig.yourLayout.someProperty;

// ✅ 해결: 안전한 접근
const config = layout.layoutConfig.yourLayout?.someProperty || defaultValue;

2. "React does not recognize prop" 경고

// ❌ 문제: 모든 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() 로 등록 상태 확인

4. 레이아웃 높이가 적용되지 않음

// ❌ 문제: 인라인 스타일만 사용
<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 방향 설정이 적용되지 않음

// ❌ 문제: 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 경고

// ❌ 문제: 고유하지 않은 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. 라벨이 컬럼명으로 표시되는 문제

// ❌ 문제: 컬럼명 직접 사용
<span>{columnName}:</span>

// ✅ 해결: 라벨 우선 표시
const getColumnLabel = (columnName: string) => {
  const column = tableColumns.find(col => col.columnName === columnName);
  return column?.columnLabel || columnName;
};

<span>{getColumnLabel(columnName)}:</span>

9. 레이아웃 드롭 시 오류 발생 문제 (9월 11일 해결됨)

// ❌ 문제: 복잡한 존별 드롭 로직으로 인한 오류
- Runtime TypeError: Cannot read properties of undefined (reading 'width')
- 다중선택 기능 중단
- 존별 드롭 이벤트 충돌

// ✅ 해결: 드롭 시스템 완전 단순화
- 모든 존별 드롭 로직 제거
- 일반 캔버스 드롭만 사용
- 레이아웃은 시각적 가이드 역할만
- z-index 기반 레이어 분리 (레이아웃=1, 컴포넌트=2+)
드롭 시스템 단순화 후 장점
  • 안정성: 복잡한 이벤트 체인 제거로 오류 가능성 감소
  • 일관성: 모든 영역에서 동일한 드롭 동작
  • 성능: 불필요한 prop 전달 및 매핑 로직 제거
  • 유지보수: 단순한 구조로 디버깅 및 수정 용이
새로운 레이아웃 개발 시 주의사항
// ✅ 올바른 레이아웃 구현
export const YourLayoutLayout: React.FC<YourLayoutProps> = ({ layout, isDesignMode, ...props }) => {
  // 🚫 존별 드롭 이벤트 구현 금지
  // onDrop, onDragOver 등 드롭 관련 이벤트 추가하지 않음

  return (
    <div className="your-layout">
      {layout.zones.map((zone) => (
        <div
          key={zone.id}
          className="zone-area"
          // 🚫 드롭 이벤트 추가 금지
          // onDrop={...} 
          // onDragOver={...} 
        >
          {/* 존 내용 */}
        </div>
      ))}

      <style jsx>{`
        .your-layout {
          /* z-index는 1로 고정 (레이아웃 레이어) */
          z-index: 1;
          height: 100% !important;
        }
      `}</style>
    </div>
  );
};

디버깅 도구

브라우저 개발자 도구

// 등록된 레이아웃 목록 확인
window.__LAYOUT_REGISTRY__.list();

// 특정 레이아웃 정보 확인
window.__LAYOUT_REGISTRY__.get("your-layout");

// 레지스트리 통계
window.__LAYOUT_REGISTRY__.stats();

📖 예시: 완전한 레이아웃 생성

1. CLI로 생성

node scripts/create-layout.js pricing-table --category=content --zones=4 --description="가격표 레이아웃" --author="개발팀"

2. 생성 결과

✅ 레이아웃 생성 완료!
📁 이름: pricingTable
🔖 ID: pricing-table
📂 카테고리: content
🎯 존 개수: 4

3. 커스터마이징

// 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. 즉시 사용 가능

레이아웃이 자동으로 등록되어 화면편집기에서 바로 사용할 수 있습니다!


🎯 존 관리 시스템의 장점

1. 구조적 레이아웃 관리

  • 논리적 그룹화: 관련된 컴포넌트들을 존별로 그룹화
  • 일관된 이동: 레이아웃 이동 시 모든 하위 컴포넌트가 함께 이동
  • 상대적 위치: 존 내부에서의 상대적 위치 관계 유지

2. 직관적인 사용자 경험

  • 드래그앤드롭: 존 영역에 직접 드롭하여 배치
  • 시각적 피드백: 존 경계와 라벨로 명확한 구분
  • 실시간 미리보기: 드래그 중에도 함께 이동하는 모습 확인

3. 개발자 친화적

  • 자동 관계 설정: parentIdzoneId 자동 할당
  • 타입 안전성: TypeScript로 완전한 타입 지원
  • 확장 가능성: 새로운 존 타입 쉽게 추가 가능

🔍 사용 예시

대시보드 레이아웃 예시

# 대시보드 레이아웃 생성
node scripts/create-layout.js dashboard-layout --category=dashboard --zones=4 --description="대시보드 레이아웃"

생성된 레이아웃에서:

  1. 헤더 존: 제목, 네비게이션 컴포넌트 배치
  2. 사이드바 존: 메뉴, 필터 컴포넌트 배치
  3. 메인 존: 차트, 테이블 컴포넌트 배치
  4. 푸터 존: 상태 정보, 액션 버튼 배치

폼 레이아웃 예시

# 폼 레이아웃 생성
node scripts/create-layout.js form-layout --category=form --zones=3 --description="폼 레이아웃"

생성된 레이아웃에서:

  1. 입력 존: 텍스트 필드, 선택박스 배치
  2. 첨부 존: 파일 업로드 컴포넌트 배치
  3. 액션 존: 저장, 취소 버튼 배치

🎯 마무리

새로운 CLI 방식과 존 관리 시스템, 그리고 데이터 기반 레이아웃으로 화면관리 시스템이 혁신적으로 개선되었습니다:

🚀 핵심 기능

  1. 한 줄 명령어로 모든 파일 자동 생성
  2. 타입 안전성 보장 (TypeScript 완전 지원)
  3. 자동 등록으로 즉시 사용 가능
  4. Hot Reload 지원으로 빠른 개발
  5. 존 관리 시스템으로 구조적 레이아웃 관리
  6. 데이터 기반 렌더링 (카드 레이아웃)
  7. 동적 존 개수 설정 (Flexbox, Grid)
  8. 높이 자동 적용 (styled-jsx + !important)

🎨 사용자 경험

  • 직관적 드래그앤드롭: 존에 직접 컴포넌트 배치
  • 일관된 이동: 레이아웃과 하위 컴포넌트가 함께 이동
  • 시각적 피드백: 명확한 존 경계와 라벨 표시
  • 실시간 설정: 체크박스로 즉시 표시 제어
  • 라벨 우선 표시: 컬럼명 대신 사용자 친화적 라벨

🔧 개발자 경험

  • 자동화된 설정: 복잡한 관계 설정이 자동으로 처리
  • 확장 가능한 구조: 새로운 레이아웃 타입 쉽게 추가
  • 완전한 타입 지원: TypeScript로 안전한 개발
  • 문제 해결 가이드: 자주 발생하는 문제들의 해결책 제공
  • 성능 최적화: React key, 메모이제이션 등 적용

🆕 최신 업데이트 (2025.09.11)

카드 레이아웃 고도화

  • 실제 데이터 연동: 테이블 데이터를 실시간으로 카드에 표시
  • 컬럼 매핑 시스템: 타이틀, 서브타이틀, 설명 등 자유로운 매핑
  • 동적 표시 컬럼: 필요한 컬럼을 추가/제거하며 동적 관리
  • 라벨 우선 표시: 데이터베이스 컬럼명 대신 사용자 친화적 라벨
  • 실시간 설정 반영: 체크박스 토글로 즉시 카드 업데이트

레이아웃 시스템 개선

  • 높이 적용 문제 해결: 모든 레이아웃에서 부모 높이 완벽 적용
  • Flexbox 방향 설정: 수평/수직 방향 정상 작동
  • 동적 존 개수: Grid, Flexbox에서 존 개수 실시간 조정
  • 시각적 피드백: 모든 레이아웃에서 존 경계 명확히 표시
  • React 경고 해결: Key prop, DOM prop 전달 등 모든 경고 해결

🔧 드롭 시스템 대폭 단순화 (9월 11일 추가)

기존의 복잡한 존별 드롭 시스템을 완전히 제거하고 단순한 캔버스 드롭 방식으로 통일했습니다:

변경 사항
  • 복잡한 드롭 로직 제거: onZoneComponentDrop, handleZoneComponentDrop 등 모든 존별 드롭 이벤트 제거
  • 일반 캔버스 드롭만 사용: 모든 드롭이 handleComponentDrop으로 통일
  • 레이아웃은 시각적 가이드 역할: 레이아웃 존은 배치 가이드라인 역할만 수행
  • z-index 기반 레이어링: 레이아웃 z-index=1, 컴포넌트 z-index=2+로 설정
  • prop 전달 체인 단순화: 불필요한 prop 매핑 및 전달 로직 제거
새로운 동작 방식

이전 (복잡한 방식):

컴포넌트 드래그 → 레이아웃 존 감지 → 존별 드롭 이벤트 → 복잡한 매핑 → 오류 발생

현재 (단순한 방식):

컴포넌트 드래그 → 캔버스에 드롭 → 일반 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 기반 레이어링 적용
개발자 가이드

새로운 시스템에서는:

  • 🚫 존별 드롭 로직 구현 금지: 모든 드롭은 캔버스 레벨에서 처리
  • 시각적 가이드만 제공: 레이아웃은 배치 가이드라인 역할만
  • z-index로 레이어 관리: 레이아웃=1, 컴포넌트=2+ 설정
  • 단순한 이벤트 처리: 복잡한 이벤트 체인 대신 직접적인 핸들링

개발자 도구 강화

  • 디버깅 로그: 카드 설정 로드, 데이터 가져오기 등 상세 로깅
  • 에러 핸들링: API 함수명 오류, 데이터 타입 불일치 등 완벽 처리
  • 타입 안전성: 모든 컴포넌트와 설정에 완전한 TypeScript 지원

📈 지원하는 레이아웃 현황

  1. Grid Layout - 격자 형태 기본 레이아웃
  2. Flexbox Layout - 유연한 방향 설정 가능
  3. Split Layout - 좌우/상하 분할
  4. Accordion Layout - 접기/펼치기 네비게이션
  5. Card Layout - 데이터 기반 카드 표시
  6. Hero Section Layout - 대형 헤더 섹션

🔮 향후 계획

새로운 레이아웃 타입

  • Table Layout: 데이터 테이블 전용 레이아웃
  • Form Layout: 폼 입력에 최적화된 레이아웃
  • Dashboard Layout: 위젯 배치에 특화된 레이아웃
  • Mobile Responsive: 모바일 대응 반응형 레이아웃

시스템 개선

  • 레이아웃 테마 시스템: 다크/라이트 모드 지원
  • 레이아웃 스타일 프리셋: 미리 정의된 스타일 템플릿
  • 레이아웃 애니메이션: 전환 효과 및 인터랙션 개선
  • 성능 최적화: 가상화 및 지연 로딩 적용

개발자 도구

  • 레이아웃 빌더 GUI: 코드 없이 레이아웃 생성 도구
  • 실시간 프리뷰: 레이아웃 편집 중 실시간 미리보기
  • 레이아웃 디버거: 시각적 디버깅 도구
  • 성능 모니터링: 레이아웃 렌더링 성능 분석

🎯 중요한 변화: 단순화된 드롭 시스템

2025년 9월 11일부터 모든 레이아웃에서 복잡한 존별 드롭 로직이 완전히 제거되었습니다.

새로운 시스템의 핵심 원칙:

  • 🎯 레이아웃 = 시각적 가이드: 배치 참고용으로만 사용
  • 🎯 캔버스 = 실제 배치: 모든 컴포넌트는 캔버스에 자유롭게 배치
  • 🎯 z-index = 레이어 분리: 레이아웃(1) 위에 컴포넌트(2+) 배치
  • 🎯 단순함 = 안정성: 복잡한 로직 제거로 오류 최소화

이 변화로 인해:

  • 모든 드롭 관련 오류 해결
  • 다중선택 기능 정상화
  • 레이아웃 개발이 더욱 단순해짐
  • 시스템 전체 안정성 크게 향상

더 자세한 정보가 필요하면 각 레이아웃의 README.md 파일을 참고하거나, 브라우저 개발자 도구에서 window.__LAYOUT_REGISTRY__.help()를 실행해보세요! 🚀