/** * 테이블 생성 모달 컴포넌트 * 새로운 테이블을 생성하기 위한 모달 */ "use client"; import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, } from "@/components/ui/resizable-dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Checkbox } from "@/components/ui/checkbox"; import { Loader2, Info, AlertCircle, CheckCircle2, Plus, Activity } from "lucide-react"; import { toast } from "sonner"; import { ColumnDefinitionTable } from "./ColumnDefinitionTable"; import { ddlApi } from "../../lib/api/ddl"; import { tableManagementApi } from "../../lib/api/tableManagement"; import { CreateTableModalProps, CreateColumnDefinition, VALIDATION_RULES, SYSTEM_TABLES, RESERVED_WORDS, } from "../../types/ddl"; export function CreateTableModal({ isOpen, onClose, onSuccess, mode = "create", sourceTableName }: CreateTableModalProps) { const isDuplicateMode = mode === "duplicate" && sourceTableName; const [tableName, setTableName] = useState(""); const [description, setDescription] = useState(""); const [columns, setColumns] = useState([ { name: "", label: "", inputType: "text", nullable: true, order: 1, }, ]); const [loading, setLoading] = useState(false); const [validating, setValidating] = useState(false); const [tableNameError, setTableNameError] = useState(""); const [validationResult, setValidationResult] = useState(null); const [useLogTable, setUseLogTable] = useState(false); /** * 모달 리셋 */ const resetModal = () => { setTableName(""); setDescription(""); setColumns([ { name: "", label: "", inputType: "text", nullable: true, order: 1, }, ]); setTableNameError(""); setValidationResult(null); setUseLogTable(false); }; /** * 모달 열림/닫힘 시 리셋 */ useEffect(() => { if (isOpen) { if (!isDuplicateMode) { resetModal(); } } }, [isOpen, isDuplicateMode]); /** * 복제 모드: 원본 테이블 정보 로드 */ useEffect(() => { if (isOpen && isDuplicateMode && sourceTableName) { loadSourceTableData(sourceTableName); } }, [isOpen, isDuplicateMode, sourceTableName]); /** * 원본 테이블 데이터 로드 함수 */ const loadSourceTableData = async (tableName: string) => { setLoading(true); try { // 1. 테이블 컬럼 정보 조회 const columnsResponse = await tableManagementApi.getColumnList(tableName); console.log("🔍 컬럼 조회 응답:", columnsResponse); if (columnsResponse.success && columnsResponse.data) { // API는 { columns, total, page, size } 형태로 반환 const columnsList = columnsResponse.data.columns; console.log("🔍 컬럼 리스트:", columnsList); if (columnsList && columnsList.length > 0) { // 첫 번째 컬럼에서 테이블 설명 가져오기 (모든 컬럼이 같은 테이블 설명을 가짐) const firstColumn = columnsList[0]; setDescription(firstColumn.description || ""); // 2. 컬럼 정보 변환 const loadedColumns: CreateColumnDefinition[] = columnsList.map((col, idx) => ({ name: col.columnName, label: col.displayName || col.columnName, inputType: col.webType || col.inputType || "text", nullable: col.isNullable === "YES", order: idx + 1, description: col.description, })); setColumns(loadedColumns); // 3. 테이블명은 비워둠 (사용자가 입력해야 함) setTableName(""); setTableNameError(""); toast.success(`${tableName} 테이블 정보를 불러왔습니다.`); } else { console.error("❌ 컬럼 배열이 비어있거나 유효하지 않음:", columnsList); toast.error("테이블에 컬럼이 없습니다."); onClose(); } } else { console.error("❌ API 응답 실패:", columnsResponse); toast.error("테이블 정보를 불러올 수 없습니다."); onClose(); } } catch (error: any) { console.error("❌ 원본 테이블 정보 로드 실패:", error); toast.error("원본 테이블 정보를 불러오는데 실패했습니다."); onClose(); } finally { setLoading(false); } }; /** * 테이블명 검증 */ const validateTableName = (name: string): string => { if (!name) { return "테이블명은 필수입니다."; } if (!VALIDATION_RULES.tableName.pattern.test(name)) { return VALIDATION_RULES.tableName.errorMessage; } if (name.length < VALIDATION_RULES.tableName.minLength || name.length > VALIDATION_RULES.tableName.maxLength) { return `테이블명은 ${VALIDATION_RULES.tableName.minLength}-${VALIDATION_RULES.tableName.maxLength}자여야 합니다.`; } if (SYSTEM_TABLES.includes(name.toLowerCase() as any)) { return "시스템 테이블명으로 사용할 수 없습니다."; } if (RESERVED_WORDS.includes(name.toLowerCase() as any)) { return "SQL 예약어는 테이블명으로 사용할 수 없습니다."; } if (name.startsWith("_") || name.endsWith("_")) { return "테이블명은 언더스코어로 시작하거나 끝날 수 없습니다."; } if (name.includes("__")) { return "테이블명에 연속된 언더스코어는 사용할 수 없습니다."; } return ""; }; /** * 테이블명 변경 처리 */ const handleTableNameChange = (value: string) => { setTableName(value); const error = validateTableName(value); setTableNameError(error); // 검증 결과 초기화 if (validationResult) { setValidationResult(null); } }; /** * 컬럼 추가 */ const addColumn = () => { setColumns([ ...columns, { name: "", label: "", inputType: "text", nullable: true, order: columns.length + 1, }, ]); }; /** * 테이블 생성 사전 검증 */ const validateTable = async () => { if (tableNameError || !tableName) { toast.error("테이블명을 올바르게 입력해주세요."); return; } const validColumns = columns.filter((col) => col.name && col.inputType); if (validColumns.length === 0) { toast.error("최소 1개의 유효한 컬럼이 필요합니다."); return; } setValidating(true); try { const result = await ddlApi.validateTableCreation({ tableName, columns: validColumns, description, }); setValidationResult(result); if (result.isValid) { toast.success("검증 완료! 테이블을 생성할 수 있습니다."); } else { toast.error("검증 실패. 오류를 확인해주세요."); } } catch (error: any) { // console.error("테이블 검증 실패:", error); toast.error("검증 중 오류가 발생했습니다."); } finally { setValidating(false); } }; /** * 테이블 생성 실행 */ const handleCreateTable = async () => { if (tableNameError || !tableName) { toast.error("테이블명을 올바르게 입력해주세요."); return; } const validColumns = columns.filter((col) => col.name && col.inputType); if (validColumns.length === 0) { toast.error("최소 1개의 유효한 컬럼이 필요합니다."); return; } setLoading(true); try { const result = await ddlApi.createTable({ tableName, columns: validColumns, description, }); if (result.success) { toast.success(result.message); // 로그 테이블 생성 옵션이 선택되었다면 로그 테이블 생성 if (useLogTable) { try { const pkColumn = { columnName: "id", dataType: "integer" }; const logResult = await tableManagementApi.createLogTable(tableName, pkColumn); if (logResult.success) { toast.success(`${tableName}_log 테이블이 생성되었습니다.`); } else { toast.warning(`테이블은 생성되었으나 로그 테이블 생성 실패: ${logResult.message}`); } } catch (logError) { toast.warning("테이블은 생성되었으나 로그 테이블 생성 중 오류가 발생했습니다."); } } onSuccess(result); onClose(); } else { toast.error(result.error?.details || result.message); } } catch (error: any) { // console.error("테이블 생성 실패:", error); toast.error(error.response?.data?.error?.details || "테이블 생성에 실패했습니다."); } finally { setLoading(false); } }; /** * 폼 유효성 확인 */ const isFormValid = !tableNameError && tableName && columns.some((col) => col.name && col.inputType); return ( {isDuplicateMode ? "테이블 복제" : "새 테이블 생성"} {isDuplicateMode ? `${sourceTableName} 테이블을 복제하여 새 테이블을 생성합니다. 테이블명을 입력하고 필요시 컬럼을 수정하세요.` : "최고 관리자만 새로운 테이블을 생성할 수 있습니다. 테이블명과 컬럼 정의를 입력하고 검증 후 생성하세요." }
{/* 테이블 기본 정보 */}
handleTableNameChange(e.target.value)} placeholder="예: customer_info" className={tableNameError ? "border-red-300" : ""} /> {tableNameError &&

{tableNameError}

}

영문자로 시작, 영문자/숫자/언더스코어만 사용 가능

setDescription(e.target.value)} placeholder="테이블에 대한 설명" />
{/* 컬럼 정의 */}
{/* 로그 테이블 생성 옵션 */}
setUseLogTable(checked as boolean)} disabled={loading} />

선택 시 {tableName || "table"}_log 테이블이 자동으로 생성되어 INSERT/UPDATE/DELETE 변경 이력을 기록합니다.

{/* 자동 추가 컬럼 안내 */} 자동 추가 컬럼 다음 컬럼들이 자동으로 추가됩니다: id(기본키), created_date, updated_date, company_code {/* 검증 결과 */} {validationResult && ( {validationResult.isValid ? : } {validationResult.isValid ? "검증 성공" : "검증 실패"}
{validationResult.summary}
{validationResult.errors && validationResult.errors.length > 0 && (
오류:
    {validationResult.errors.map((error: string, index: number) => (
  • {error}
  • ))}
)} {validationResult.warnings && validationResult.warnings.length > 0 && (
경고:
    {validationResult.warnings.map((warning: string, index: number) => (
  • {warning}
  • ))}
)}
)}
); }