# 테이블 복제 기능 구현 계획서

## 📋 개요

테이블 타입 관리 시스템에 기존 테이블을 복제하여 새로운 테이블을 생성하는 기능을 추가합니다. 복제 시 원본 테이블의 컬럼 구조와 설정을 가져와 사용자가 수정 후 저장할 수 있도록 합니다.

---

## 🎯 주요 요구사항

### 1. 권한 제한

- **최고 관리자(company_code = "\*" && user_type = "SUPER_ADMIN")만 사용 가능**
- 일반 회사 사용자는 테이블 복제 버튼이 보이지 않음

### 2. 복제 프로세스

1. 테이블 목록에서 복제할 테이블 선택 (체크박스)
2. "복제" 버튼 클릭
3. 기존 "테이블 생성" 모달이 "테이블 복제" 모드로 열림
4. 원본 테이블의 설정이 자동으로 채워짐:
   - 테이블명: 빈 상태 (사용자가 새로 입력)
   - 테이블 설명: 원본 설명 복사 (수정 가능)
   - 컬럼 정의: 원본 컬럼들이 모두 로드됨 (수정/삭제 가능)
5. 사용자가 수정 후 저장
6. 테이블명 중복 검증 (실시간)
7. DDL 실행 및 테이블 생성

### 3. 제약사항

- **테이블명 중복 불가** (실시간 검증)
- 시스템 테이블은 복제 불가 (선택 불가 처리)
- 최소 1개 이상의 컬럼 필요

---

## 🏗️ 구현 아키텍처

### 컴포넌트 구조

```
📦 테이블 타입 관리 페이지 (tableMng/page.tsx)
├── 📄 테이블 목록 (기존)
│   ├── 체크박스 (단일 선택 또는 다중 선택)
│   └── 복제 버튼 (최고 관리자만 표시)
│
└── 🔧 CreateTableModal 확장 (CreateTableModal.tsx)
    ├── 모드: "create" | "duplicate"
    ├── Props 추가: duplicateTableName?, isDuplicateMode?
    ├── 복제 모드 시 자동 로드:
    │   ├── 원본 테이블 설명
    │   └── 원본 컬럼 목록 (전체 설정 포함)
    └── 실시간 테이블명 중복 검증
```

---

## 📝 상세 구현 계획

### Phase 1: UI 추가 (테이블 목록 페이지)

#### 1.1. 체크박스 추가

**파일**: `frontend/app/(main)/admin/tableMng/page.tsx`

**변경사항**:

- 각 테이블 행에 체크박스 추가
- 선택된 테이블 상태 관리: `selectedTableIds` (Set<string>)
- 시스템 테이블은 체크박스 비활성화

```typescript
// 상태 추가
const [selectedTableIds, setSelectedTableIds] = useState<Set<string>>(
  new Set()
);

// 최고 관리자 여부 확인 수정 (기존 코드 수정 필요)
// ❌ 기존: const isSuperAdmin = user?.companyCode === "*";
// ✅ 수정: const isSuperAdmin = user?.companyCode === "*" && user?.userType === "SUPER_ADMIN";

// 시스템 테이블 목록 (복제 불가)
const SYSTEM_TABLES = [
  "user_info",
  "company_info",
  "menu_info",
  "auth_group",
  "menu_auth_group",
  "user_auth",
  "dept_info",
  "code_info",
  "code_category",
  // ... 기타 시스템 테이블
];

// 체크박스 핸들러
const handleTableSelect = (tableName: string) => {
  setSelectedTableIds((prev) => {
    const newSet = new Set(prev);
    if (newSet.has(tableName)) {
      newSet.delete(tableName);
    } else {
      newSet.add(tableName);
    }
    return newSet;
  });
};
```

#### 1.2. 복제 버튼 추가

**위치**: 테이블 목록 상단 (새 테이블 생성 버튼 옆)

```typescript
{
  isSuperAdmin && (
    <Button
      variant="outline"
      onClick={handleDuplicateTable}
      disabled={selectedTableIds.size !== 1}
      className="h-9 text-xs sm:h-10 sm:text-sm"
    >
      <Copy className="mr-2 h-4 w-4" />
      테이블 복제
    </Button>
  );
}
```

**조건**:

- 최고 관리자만 표시
- 정확히 1개의 테이블이 선택된 경우만 활성화
- 선택된 테이블이 시스템 테이블인 경우 비활성화

---

### Phase 2: CreateTableModal 확장

#### 2.1. Props 확장

**파일**: `frontend/components/admin/CreateTableModal.tsx`

```typescript
export interface CreateTableModalProps {
  isOpen: boolean;
  onClose: () => void;
  onSuccess: (result: any) => void;

  // 🆕 복제 모드 관련
  mode?: "create" | "duplicate";
  sourceTableName?: string; // 복제 대상 테이블명
}
```

#### 2.2. 복제 모드 감지 및 데이터 로드

```typescript
export function CreateTableModal({
  isOpen,
  onClose,
  onSuccess,
  mode = "create",
  sourceTableName,
}: CreateTableModalProps) {
  const isDuplicateMode = mode === "duplicate" && sourceTableName;

  // 복제 모드일 때 원본 테이블 정보 로드
  useEffect(() => {
    if (isOpen && isDuplicateMode && sourceTableName) {
      loadSourceTableData(sourceTableName);
    }
  }, [isOpen, isDuplicateMode, sourceTableName]);

  const loadSourceTableData = async (tableName: string) => {
    setLoading(true);
    try {
      // 1. 테이블 기본 정보 조회
      const tableResponse = await apiClient.get(
        `/table-management/tables/${tableName}`
      );

      if (tableResponse.data.success) {
        const tableInfo = tableResponse.data.data;
        setDescription(tableInfo.description || "");
      }

      // 2. 컬럼 정보 조회
      const columnsResponse = await tableManagementApi.getColumnList(tableName);

      if (columnsResponse.success && columnsResponse.data) {
        const loadedColumns: CreateColumnDefinition[] =
          columnsResponse.data.map((col, idx) => ({
            name: col.columnName,
            label: col.displayName || col.columnName,
            inputType: col.webType as any,
            nullable: col.isNullable === "YES",
            order: idx + 1,
            description: col.description,
            codeCategory: col.codeCategory || undefined,
            referenceTable: col.referenceTable || undefined,
            referenceColumn: col.referenceColumn || undefined,
            displayColumn: col.displayColumn || undefined,
          }));

        setColumns(loadedColumns);
        toast.success(`${tableName} 테이블 정보를 불러왔습니다.`);
      }
    } catch (error: any) {
      console.error("원본 테이블 정보 로드 실패:", error);
      toast.error("원본 테이블 정보를 불러오는데 실패했습니다.");
      onClose();
    } finally {
      setLoading(false);
    }
  };
}
```

#### 2.3. 모달 제목 및 버튼 텍스트 변경

```typescript
return (
  <Dialog open={isOpen} onOpenChange={onClose}>
    <DialogContent className="max-w-[95vw] sm:max-w-[800px]">
      <DialogHeader>
        <DialogTitle className="text-base sm:text-lg">
          {isDuplicateMode ? "테이블 복제" : "새 테이블 생성"}
        </DialogTitle>
        <DialogDescription className="text-xs sm:text-sm">
          {isDuplicateMode
            ? `${sourceTableName} 테이블을 복제하여 새 테이블을 생성합니다.`
            : "데이터베이스에 새로운 테이블을 생성합니다."}
        </DialogDescription>
      </DialogHeader>

      {/* ... 폼 내용 ... */}

      <DialogFooter className="gap-2 sm:gap-0">
        <Button
          variant="outline"
          onClick={onClose}
          className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
        >
          취소
        </Button>
        <Button
          onClick={handleCreateTable}
          disabled={!isFormValid || loading}
          className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
        >
          {loading ? (
            <>
              <Loader2 className="mr-2 h-4 w-4 animate-spin" />
              생성 중...
            </>
          ) : isDuplicateMode ? (
            "복제 생성"
          ) : (
            "생성"
          )}
        </Button>
      </DialogFooter>
    </DialogContent>
  </Dialog>
);
```

#### 2.4. 테이블명 실시간 중복 검증 강화

```typescript
// 테이블명 변경 시 실시간 검증
const handleTableNameChange = async (name: string) => {
  setTableName(name);

  // 기본 검증
  const error = validateTableName(name);
  if (error) {
    setTableNameError(error);
    return;
  }

  // 중복 검증
  setValidating(true);
  try {
    const result = await tableManagementApi.checkTableExists(name);
    if (result.success && result.data?.exists) {
      setTableNameError("이미 존재하는 테이블명입니다.");
    } else {
      setTableNameError("");
    }
  } catch (error) {
    console.error("테이블명 검증 오류:", error);
  } finally {
    setValidating(false);
  }
};
```

---

### Phase 3: 백엔드 API 구현 (필요 시)

#### 3.1. 테이블 기본 정보 조회 API

**엔드포인트**: `GET /api/table-management/tables/:tableName`

**응답**:

```json
{
  "success": true,
  "data": {
    "tableName": "contracts",
    "displayName": "계약 관리",
    "description": "계약 정보를 관리하는 테이블",
    "columnCount": 15
  }
}
```

**구현 위치**: `backend-node/src/controllers/tableManagementController.ts`

```typescript
/**
 * 특정 테이블의 기본 정보 조회
 */
async getTableInfo(req: Request, res: Response) {
  const { tableName } = req.params;

  try {
    const query = `
      SELECT
        t.table_name as "tableName",
        dt.display_name as "displayName",
        dt.description as "description",
        COUNT(c.column_name) as "columnCount"
      FROM information_schema.tables t
      LEFT JOIN db_table_types dt ON dt.table_name = t.table_name
      LEFT JOIN information_schema.columns c ON c.table_name = t.table_name
      WHERE t.table_schema = 'public'
        AND t.table_name = $1
      GROUP BY t.table_name, dt.display_name, dt.description
    `;

    const result = await pool.query(query, [tableName]);

    if (result.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: `테이블 '${tableName}'을 찾을 수 없습니다.`
      });
    }

    return res.json({
      success: true,
      data: result.rows[0]
    });

  } catch (error: any) {
    logger.error("테이블 정보 조회 실패", { tableName, error: error.message });
    return res.status(500).json({
      success: false,
      message: "테이블 정보를 조회하는데 실패했습니다."
    });
  }
}
```

#### 3.2. 라우트 추가

**파일**: `backend-node/src/routes/tableManagement.ts`

```typescript
// 테이블 기본 정보 조회 (새로 추가)
router.get("/tables/:tableName", controller.getTableInfo);
```

---

### Phase 4: 통합 및 테스트

#### 4.1. 테이블 목록 페이지에서 복제 흐름 구현

```typescript
// tableMng/page.tsx

const handleDuplicateTable = async () => {
  // 선택된 테이블 1개 확인
  if (selectedTableIds.size !== 1) {
    toast.error("복제할 테이블을 1개만 선택해주세요.");
    return;
  }

  const sourceTable = Array.from(selectedTableIds)[0];

  // 시스템 테이블 체크
  if (SYSTEM_TABLES.includes(sourceTable)) {
    toast.error("시스템 테이블은 복제할 수 없습니다.");
    return;
  }

  // 복제 모달 열기
  setDuplicateSourceTable(sourceTable);
  setCreateTableModalMode("duplicate");
  setCreateTableModalOpen(true);
};

// 모달 컴포넌트
<CreateTableModal
  isOpen={createTableModalOpen}
  onClose={() => {
    setCreateTableModalOpen(false);
    setDuplicateSourceTable(null);
    setCreateTableModalMode("create");
  }}
  onSuccess={(result) => {
    loadTables(); // 테이블 목록 새로고침
    setSelectedTableIds(new Set()); // 선택 초기화
  }}
  mode={createTableModalMode}
  sourceTableName={duplicateSourceTable}
/>;
```

---

## 🧪 테스트 시나리오

### 테스트 케이스 1: 정상 복제 흐름

1. 최고 관리자로 로그인
2. 테이블 타입 관리 페이지 접속
3. 복제할 테이블 1개 선택 (예: `contracts`)
4. "테이블 복제" 버튼 클릭
5. 모달이 열리고 원본 테이블 정보가 자동으로 로드됨
6. 새 테이블명 입력 (예: `contracts_backup`)
7. 컬럼 정보 확인 및 필요 시 수정
8. "복제 생성" 버튼 클릭
9. 테이블이 생성되고 목록에 표시됨
10. 성공 토스트 메시지 확인

### 테스트 케이스 2: 테이블명 중복 검증

1. 테이블 복제 흐름 시작
2. 기존에 존재하는 테이블명 입력 (예: `user_info`)
3. 실시간으로 에러 메시지 표시: "이미 존재하는 테이블명입니다."
4. "복제 생성" 버튼 비활성화 확인

### 테스트 케이스 3: 시스템 테이블 복제 제한

1. 시스템 테이블 선택 시도 (예: `user_info`)
2. 체크박스가 비활성화되어 있음
3. 또는 선택 시 경고 메시지: "시스템 테이블은 복제할 수 없습니다."

### 테스트 케이스 4: 권한 제한

1. 일반 회사 사용자로 로그인
2. 테이블 타입 관리 페이지 접속
3. "테이블 복제" 버튼이 보이지 않음
4. 체크박스도 표시되지 않음 (선택 사항)

### 테스트 케이스 5: 컬럼 수정 후 복제

1. 테이블 복제 모달 열기
2. 원본 컬럼 중 일부 삭제
3. 새 컬럼 추가
4. 컬럼 라벨 변경
5. 저장 시 수정된 구조로 테이블 생성됨

---

## 📊 데이터 흐름도

```
┌─────────────────────────────────────────────────────────────┐
│ 1. 테이블 선택 (체크박스)                                    │
│    - 사용자가 복제할 테이블 1개 선택                        │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 2. "테이블 복제" 버튼 클릭                                   │
│    - handleDuplicateTable() 실행                            │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 3. CreateTableModal 열림 (복제 모드)                        │
│    - mode: "duplicate"                                      │
│    - sourceTableName: 선택된 테이블명                       │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 4. 원본 테이블 정보 자동 로드                                │
│    - GET /api/table-management/tables/:tableName           │
│      → 테이블 설명 가져오기                                 │
│    - GET /api/table-management/tables/:tableName/columns   │
│      → 모든 컬럼 정보 가져오기                              │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 5. 사용자 편집                                              │
│    - 새 테이블명 입력 (실시간 중복 검증)                    │
│    - 테이블 설명 수정                                       │
│    - 컬럼 추가/삭제/수정                                    │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 6. "복제 생성" 버튼 클릭                                     │
│    - POST /api/ddl/create-table                             │
│      (기존 테이블 생성 API 재사용)                          │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 7. DDL 실행 및 테이블 생성                                   │
│    - CREATE TABLE ... (새 테이블명)                         │
│    - db_column_types 레코드 생성                            │
│    - db_table_types 레코드 생성                             │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 8. 성공 처리                                                │
│    - 모달 닫기                                              │
│    - 테이블 목록 새로고침                                   │
│    - 성공 토스트 메시지 표시                                │
└─────────────────────────────────────────────────────────────┘
```

---

## 🚀 구현 순서 (우선순위)

### Step 0: 기존 코드 수정 (최고 관리자 체크 로직)

1. ✅ `frontend/app/(main)/admin/tableMng/page.tsx` - line 101
   - 기존: `const isSuperAdmin = user?.companyCode === "*";`
   - 수정: `const isSuperAdmin = user?.companyCode === "*" && user?.userType === "SUPER_ADMIN";`

**예상 소요 시간**: 5분

### Step 1: UI 기반 구조 (프론트엔드)

1. ✅ 테이블 목록에 체크박스 추가
2. ✅ 복제 버튼 추가 (최고 관리자만 표시)
3. ✅ 선택된 테이블 상태 관리
4. ✅ 시스템 테이블 복제 제한

**예상 소요 시간**: 1-2시간

### Step 2: CreateTableModal 확장

1. ✅ Props 확장 (mode, sourceTableName)
2. ✅ 복제 모드 감지 로직
3. ✅ 원본 테이블 정보 로드 함수
4. ✅ 모달 제목 및 버튼 텍스트 동적 변경
5. ✅ 테이블명 실시간 중복 검증

**예상 소요 시간**: 2-3시간

### Step 3: 백엔드 API 추가 (필요 시)

1. ✅ 테이블 기본 정보 조회 API 추가
2. ✅ 라우트 추가
3. ✅ 권한 검증 (최고 관리자만)

**예상 소요 시간**: 1시간

### Step 4: 통합 및 테스트

1. ✅ 전체 흐름 통합
2. ✅ 각 테스트 케이스 실행
3. ✅ 버그 수정 및 최적화

**예상 소요 시간**: 2-3시간

**전체 예상 소요 시간**: **6-9시간** (기존 코드 수정 포함)

---

## 🔒 보안 고려사항

### 1. 권한 체크

- 프론트엔드: `isSuperAdmin` 플래그로 UI 제어
  - `user?.companyCode === "*" && user?.userType === "SUPER_ADMIN"`
- 백엔드: 모든 API 호출 시 두 가지 조건 모두 검증

```typescript
// 백엔드 미들웨어 (기존 requireSuperAdmin 재사용)
import { requireSuperAdmin } from "@/middleware/superAdminMiddleware";

// 또는 인라인 체크
const requireSuperAdmin = (req: Request, res: Response, next: NextFunction) => {
  if (req.user?.companyCode !== "*" || req.user?.userType !== "SUPER_ADMIN") {
    return res.status(403).json({
      success: false,
      message: "최고 관리자만 접근할 수 있습니다.",
    });
  }
  next();
};

// 라우트 적용
router.post(
  "/tables/:tableName/duplicate",
  requireSuperAdmin,
  controller.duplicateTable
);
```

### 2. 시스템 테이블 보호

- SYSTEM_TABLES 상수로 관리
- 복제 시도 시 서버 측에서도 검증

### 3. 테이블명 검증

- SQL Injection 방지: 정규식 검증 (`^[a-z][a-z0-9_]*$`)
- 예약어 체크
- 길이 제한 (3-63자)

---

## 📌 주요 체크포인트

### 사용자 경험 (UX)

- ✅ 복제 버튼은 1개 테이블 선택 시에만 활성화
- ✅ 로딩 상태 표시 (모달 열릴 때)
- ✅ 실시간 테이블명 중복 검증
- ✅ 명확한 에러 메시지
- ✅ 성공/실패 토스트 알림

### 데이터 일관성

- ✅ 원본 테이블의 모든 컬럼 정보 정확히 복사
- ✅ webType, codeCategory, referenceTable 등 관계 정보 포함
- ✅ 컬럼 순서 유지

### 성능

- ✅ 컬럼이 많은 테이블도 빠르게 로드
- ✅ 불필요한 API 호출 최소화
- ✅ 로딩 중 중복 요청 방지

---

## 🎨 UI 스타일 가이드

### 복제 버튼

```tsx
<Button
  variant="outline"
  onClick={handleDuplicateTable}
  disabled={selectedTableIds.size !== 1}
  className="h-9 text-xs sm:h-10 sm:text-sm"
>
  <Copy className="mr-2 h-4 w-4" />
  테이블 복제
</Button>
```

### 체크박스 (테이블 행)

```tsx
<Checkbox
  checked={selectedTableIds.has(table.tableName)}
  onCheckedChange={() => handleTableSelect(table.tableName)}
  disabled={SYSTEM_TABLES.includes(table.tableName)}
  className="h-4 w-4"
/>
```

### 모달 제목 (복제 모드)

```tsx
<DialogTitle className="text-base sm:text-lg">
  테이블 복제
</DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
  {sourceTableName} 테이블을 복제하여 새 테이블을 생성합니다.
</DialogDescription>
```

---

## 📚 참고 자료

### 기존 구현 파일

- `frontend/app/(main)/admin/tableMng/page.tsx` - 테이블 타입 관리 페이지
- `frontend/components/admin/CreateTableModal.tsx` - 테이블 생성 모달
- `frontend/components/admin/ColumnDefinitionTable.tsx` - 컬럼 정의 테이블
- `frontend/lib/api/tableManagement.ts` - 테이블 관리 API
- `backend-node/src/controllers/tableManagementController.ts` - 백엔드 컨트롤러

### 유사 기능 참고

- Yard 레이아웃 복제 기능: `YardLayoutList.tsx` (onDuplicate 핸들러)
- 메뉴 복제 기능 (있다면)

---

## ✅ 완료 기준

### Phase 1 완료 조건

- [ ] 테이블 목록에 체크박스 추가됨
- [ ] 복제 버튼 추가 (최고 관리자만 보임)
- [ ] 1개 테이블 선택 시에만 버튼 활성화
- [ ] 시스템 테이블은 선택 불가

### Phase 2 완료 조건

- [ ] CreateTableModal이 복제 모드를 지원
- [ ] 원본 테이블 정보가 자동으로 로드됨
- [ ] 모달 제목과 버튼 텍스트가 모드에 따라 변경됨
- [ ] 테이블명 실시간 중복 검증 작동

### Phase 3 완료 조건

- [ ] 테이블 기본 정보 조회 API 추가
- [ ] 최고 관리자 권한 검증
- [ ] 라우트 등록

### Phase 4 완료 조건

- [ ] 모든 테스트 케이스 통과
- [ ] 버그 수정 완료
- [ ] 사용자 가이드 문서 작성

---

## 🐛 예상 이슈 및 해결 방안

### 이슈 1: 컬럼이 너무 많은 테이블 복제 시 느림

**해결방안**:

- 페이지네이션 없이 전체 컬럼 한 번에 로드 (`size=9999`)
- 로딩 스피너 표시
- 필요시 API에서 캐싱 적용

### 이슈 2: 복제 후 테이블명 입력 잊음

**해결방안**:

- 테이블명 필드를 비워두고 포커스 자동 이동
- placeholder에 예시 제공 (예: `contracts_copy`)

### 이슈 3: 시스템 테이블 실수로 복제

**해결방안**:

- 프론트엔드와 백엔드 양쪽에서 검증
- SYSTEM_TABLES 리스트 관리

### 이슈 4: 참조 무결성 (Foreign Key) 복사 여부

**해결방안**:

- 현재는 컬럼 구조만 복사
- Foreign Key는 사용자가 수동으로 설정
- 향후 고급 기능으로 제약조건 복사 추가 가능

---

## 🔮 향후 개선 방향 (Optional)

### 1. 다중 테이블 복제

- 여러 테이블을 한 번에 복제
- 접두사 또는 접미사 자동 추가 (예: `_copy`)

### 2. 데이터 포함 복제

- 테이블 구조 + 데이터도 함께 복제
- `INSERT INTO ... SELECT` 방식

### 3. 제약조건 복사

- Primary Key, Foreign Key, Unique, Check 등
- 인덱스도 함께 복사

### 4. 복제 템플릿

- 자주 사용하는 테이블 구조를 템플릿으로 저장
- 빠른 복제 기능

### 5. 복제 이력 관리

- 어떤 테이블이 어디서 복제되었는지 추적
- DDL 로그에 복제 정보 기록

---

## 📞 문의 및 지원

- 기술 문의: 개발팀
- 사용 방법: 사용자 가이드 참조
- 버그 제보: 이슈 트래커

---

**작성일**: 2025-10-31  
**작성자**: AI Assistant  
**버전**: 1.0  
**상태**: ✅ 구현 완료

---

## 🐛 버그 수정 내역

### API 응답 구조 불일치 문제

**문제**: 모달이 뜨자마자 바로 닫히면서 "테이블 정보를 불러올 수 없습니다" 토스트 표시

**원인**:

- 백엔드 API는 `{ columns: [], total, page, size }` 형태로 반환
- 프론트엔드는 `data`를 직접 배열로 취급
- 타입 정의: `ColumnListResponse extends ApiResponse<ColumnTypeInfo[]>` (잘못됨)

**해결**:

1. **API 타입 수정** (`frontend/lib/api/tableManagement.ts`)

   ```typescript
   // 추가된 타입
   export interface ColumnListData {
     columns: ColumnTypeInfo[];
     total: number;
     page: number;
     size: number;
     totalPages: number;
   }

   // 수정된 타입
   export interface ColumnListResponse extends ApiResponse<ColumnListData> {}
   ```

2. **CreateTableModal 데이터 파싱 수정**

   ```typescript
   // Before (잘못됨)
   if (
     columnsResponse.success &&
     columnsResponse.data &&
     columnsResponse.data.length > 0
   ) {
     const firstColumn = columnsResponse.data[0];
   }

   // After (올바름)
   if (columnsResponse.success && columnsResponse.data) {
     const columnsList = columnsResponse.data.columns;
     if (columnsList && columnsList.length > 0) {
       const firstColumn = columnsList[0];
     }
   }
   ```

3. **디버그 로그 추가**
   - API 응답 전체 로그
   - 컬럼 리스트 추출 후 로그
   - 에러 상황별 상세 로그

**결과**:

- ✅ 복제 모드에서 테이블 정보 정상 로드
- ✅ 타입 안전성 향상
- ✅ 에러 핸들링 개선
