2025-09-22 17:00:59 +09:00
|
|
|
/**
|
|
|
|
|
* 테이블 생성 모달 컴포넌트
|
|
|
|
|
* 새로운 테이블을 생성하기 위한 모달
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect } from "react";
|
|
|
|
|
import {
|
2025-12-05 10:46:10 +09:00
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogFooter,
|
|
|
|
|
} from "@/components/ui/dialog";
|
2025-09-22 17:00:59 +09:00
|
|
|
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";
|
2025-10-21 15:08:41 +09:00
|
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
|
|
|
import { Loader2, Info, AlertCircle, CheckCircle2, Plus, Activity } from "lucide-react";
|
2025-09-22 17:00:59 +09:00
|
|
|
import { toast } from "sonner";
|
|
|
|
|
import { ColumnDefinitionTable } from "./ColumnDefinitionTable";
|
|
|
|
|
import { ddlApi } from "../../lib/api/ddl";
|
2025-10-21 15:08:41 +09:00
|
|
|
import { tableManagementApi } from "../../lib/api/tableManagement";
|
2025-09-22 17:00:59 +09:00
|
|
|
import {
|
|
|
|
|
CreateTableModalProps,
|
|
|
|
|
CreateColumnDefinition,
|
|
|
|
|
VALIDATION_RULES,
|
|
|
|
|
SYSTEM_TABLES,
|
|
|
|
|
RESERVED_WORDS,
|
|
|
|
|
} from "../../types/ddl";
|
|
|
|
|
|
2025-10-31 17:58:49 +09:00
|
|
|
export function CreateTableModal({
|
|
|
|
|
isOpen,
|
|
|
|
|
onClose,
|
|
|
|
|
onSuccess,
|
|
|
|
|
mode = "create",
|
|
|
|
|
sourceTableName
|
|
|
|
|
}: CreateTableModalProps) {
|
|
|
|
|
const isDuplicateMode = mode === "duplicate" && sourceTableName;
|
|
|
|
|
|
2025-09-22 17:00:59 +09:00
|
|
|
const [tableName, setTableName] = useState("");
|
|
|
|
|
const [description, setDescription] = useState("");
|
|
|
|
|
const [columns, setColumns] = useState<CreateColumnDefinition[]>([
|
|
|
|
|
{
|
|
|
|
|
name: "",
|
|
|
|
|
label: "",
|
2025-09-23 10:40:21 +09:00
|
|
|
inputType: "text",
|
2025-09-22 17:00:59 +09:00
|
|
|
nullable: true,
|
|
|
|
|
order: 1,
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const [validating, setValidating] = useState(false);
|
|
|
|
|
const [tableNameError, setTableNameError] = useState("");
|
|
|
|
|
const [validationResult, setValidationResult] = useState<any>(null);
|
2025-10-21 15:08:41 +09:00
|
|
|
const [useLogTable, setUseLogTable] = useState(false);
|
2025-09-22 17:00:59 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 모달 리셋
|
|
|
|
|
*/
|
|
|
|
|
const resetModal = () => {
|
|
|
|
|
setTableName("");
|
|
|
|
|
setDescription("");
|
|
|
|
|
setColumns([
|
|
|
|
|
{
|
|
|
|
|
name: "",
|
|
|
|
|
label: "",
|
2025-09-23 10:40:21 +09:00
|
|
|
inputType: "text",
|
2025-09-22 17:00:59 +09:00
|
|
|
nullable: true,
|
|
|
|
|
order: 1,
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
setTableNameError("");
|
|
|
|
|
setValidationResult(null);
|
2025-10-21 15:08:41 +09:00
|
|
|
setUseLogTable(false);
|
2025-09-22 17:00:59 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 모달 열림/닫힘 시 리셋
|
|
|
|
|
*/
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (isOpen) {
|
2025-10-31 17:58:49 +09:00
|
|
|
if (!isDuplicateMode) {
|
|
|
|
|
resetModal();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [isOpen, isDuplicateMode]);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 복제 모드: 원본 테이블 정보 로드
|
|
|
|
|
*/
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (isOpen && isDuplicateMode && sourceTableName) {
|
|
|
|
|
loadSourceTableData(sourceTableName);
|
2025-09-22 17:00:59 +09:00
|
|
|
}
|
2025-10-31 17:58:49 +09:00
|
|
|
}, [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);
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-09-22 17:00:59 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 테이블명 검증
|
|
|
|
|
*/
|
|
|
|
|
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: "",
|
2025-09-23 10:40:21 +09:00
|
|
|
inputType: "text",
|
2025-09-22 17:00:59 +09:00
|
|
|
nullable: true,
|
|
|
|
|
order: columns.length + 1,
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 테이블 생성 사전 검증
|
|
|
|
|
*/
|
|
|
|
|
const validateTable = async () => {
|
|
|
|
|
if (tableNameError || !tableName) {
|
|
|
|
|
toast.error("테이블명을 올바르게 입력해주세요.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 10:40:21 +09:00
|
|
|
const validColumns = columns.filter((col) => col.name && col.inputType);
|
2025-09-22 17:00:59 +09:00
|
|
|
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) {
|
2025-10-01 18:17:30 +09:00
|
|
|
// console.error("테이블 검증 실패:", error);
|
2025-09-22 17:00:59 +09:00
|
|
|
toast.error("검증 중 오류가 발생했습니다.");
|
|
|
|
|
} finally {
|
|
|
|
|
setValidating(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 테이블 생성 실행
|
|
|
|
|
*/
|
|
|
|
|
const handleCreateTable = async () => {
|
|
|
|
|
if (tableNameError || !tableName) {
|
|
|
|
|
toast.error("테이블명을 올바르게 입력해주세요.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 10:40:21 +09:00
|
|
|
const validColumns = columns.filter((col) => col.name && col.inputType);
|
2025-09-22 17:00:59 +09:00
|
|
|
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);
|
2025-10-21 15:08:41 +09:00
|
|
|
|
|
|
|
|
// 로그 테이블 생성 옵션이 선택되었다면 로그 테이블 생성
|
|
|
|
|
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("테이블은 생성되었으나 로그 테이블 생성 중 오류가 발생했습니다.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-22 17:00:59 +09:00
|
|
|
onSuccess(result);
|
|
|
|
|
onClose();
|
|
|
|
|
} else {
|
|
|
|
|
toast.error(result.error?.details || result.message);
|
|
|
|
|
}
|
|
|
|
|
} catch (error: any) {
|
2025-10-01 18:17:30 +09:00
|
|
|
// console.error("테이블 생성 실패:", error);
|
2025-09-22 17:00:59 +09:00
|
|
|
toast.error(error.response?.data?.error?.details || "테이블 생성에 실패했습니다.");
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 폼 유효성 확인
|
|
|
|
|
*/
|
2025-09-23 10:40:21 +09:00
|
|
|
const isFormValid = !tableNameError && tableName && columns.some((col) => col.name && col.inputType);
|
2025-09-22 17:00:59 +09:00
|
|
|
|
|
|
|
|
return (
|
2025-12-05 10:46:10 +09:00
|
|
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
|
|
|
<DialogContent className="max-h-[90vh] max-w-6xl overflow-hidden">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle className="flex items-center gap-2">
|
2025-10-31 17:58:49 +09:00
|
|
|
<Plus className="h-5 w-5" />
|
|
|
|
|
{isDuplicateMode ? "테이블 복제" : "새 테이블 생성"}
|
2025-12-05 10:46:10 +09:00
|
|
|
</DialogTitle>
|
|
|
|
|
<DialogDescription>
|
2025-10-31 17:58:49 +09:00
|
|
|
{isDuplicateMode
|
|
|
|
|
? `${sourceTableName} 테이블을 복제하여 새 테이블을 생성합니다. 테이블명을 입력하고 필요시 컬럼을 수정하세요.`
|
|
|
|
|
: "최고 관리자만 새로운 테이블을 생성할 수 있습니다. 테이블명과 컬럼 정의를 입력하고 검증 후 생성하세요."
|
|
|
|
|
}
|
2025-12-05 10:46:10 +09:00
|
|
|
</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
2025-09-22 17:00:59 +09:00
|
|
|
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
{/* 테이블 기본 정보 */}
|
|
|
|
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="tableName">
|
|
|
|
|
테이블명 <span className="text-red-500">*</span>
|
|
|
|
|
</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="tableName"
|
|
|
|
|
value={tableName}
|
|
|
|
|
onChange={(e) => handleTableNameChange(e.target.value)}
|
|
|
|
|
placeholder="예: customer_info"
|
|
|
|
|
className={tableNameError ? "border-red-300" : ""}
|
|
|
|
|
/>
|
2025-10-21 15:08:41 +09:00
|
|
|
{tableNameError && <p className="text-destructive text-sm">{tableNameError}</p>}
|
2025-09-22 17:00:59 +09:00
|
|
|
<p className="text-muted-foreground text-xs">영문자로 시작, 영문자/숫자/언더스코어만 사용 가능</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="description">설명</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="description"
|
|
|
|
|
value={description}
|
|
|
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
|
|
|
placeholder="테이블에 대한 설명"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 컬럼 정의 */}
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<Label>
|
|
|
|
|
컬럼 정의 <span className="text-red-500">*</span>
|
|
|
|
|
</Label>
|
|
|
|
|
<Button type="button" variant="outline" size="sm" onClick={addColumn} disabled={loading}>
|
|
|
|
|
<Plus className="mr-1 h-4 w-4" />
|
|
|
|
|
컬럼 추가
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ColumnDefinitionTable columns={columns} onChange={setColumns} disabled={loading} />
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-10-21 15:08:41 +09:00
|
|
|
{/* 로그 테이블 생성 옵션 */}
|
|
|
|
|
<div className="flex items-start space-x-3 rounded-lg border p-4">
|
|
|
|
|
<Checkbox
|
|
|
|
|
id="useLogTable"
|
|
|
|
|
checked={useLogTable}
|
|
|
|
|
onCheckedChange={(checked) => setUseLogTable(checked as boolean)}
|
|
|
|
|
disabled={loading}
|
|
|
|
|
/>
|
|
|
|
|
<div className="grid gap-1.5 leading-none">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="useLogTable"
|
|
|
|
|
className="flex cursor-pointer items-center gap-2 text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
|
|
|
>
|
|
|
|
|
<Activity className="h-4 w-4" />
|
|
|
|
|
변경 이력 로그 테이블 생성
|
|
|
|
|
</label>
|
|
|
|
|
<p className="text-muted-foreground text-xs">
|
|
|
|
|
선택 시 <code className="bg-muted rounded px-1 py-0.5">{tableName || "table"}_log</code> 테이블이
|
|
|
|
|
자동으로 생성되어 INSERT/UPDATE/DELETE 변경 이력을 기록합니다.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-09-22 17:00:59 +09:00
|
|
|
{/* 자동 추가 컬럼 안내 */}
|
|
|
|
|
<Alert>
|
|
|
|
|
<Info className="h-4 w-4" />
|
|
|
|
|
<AlertTitle>자동 추가 컬럼</AlertTitle>
|
|
|
|
|
<AlertDescription>
|
|
|
|
|
다음 컬럼들이 자동으로 추가됩니다:
|
|
|
|
|
<code className="bg-muted mx-1 rounded px-1 py-0.5 text-sm">id</code>(기본키),
|
|
|
|
|
<code className="bg-muted mx-1 rounded px-1 py-0.5 text-sm">created_date</code>,
|
|
|
|
|
<code className="bg-muted mx-1 rounded px-1 py-0.5 text-sm">updated_date</code>,
|
|
|
|
|
<code className="bg-muted mx-1 rounded px-1 py-0.5 text-sm">company_code</code>
|
|
|
|
|
</AlertDescription>
|
|
|
|
|
</Alert>
|
|
|
|
|
|
|
|
|
|
{/* 검증 결과 */}
|
|
|
|
|
{validationResult && (
|
|
|
|
|
<Alert variant={validationResult.isValid ? "default" : "destructive"}>
|
|
|
|
|
{validationResult.isValid ? <CheckCircle2 className="h-4 w-4" /> : <AlertCircle className="h-4 w-4" />}
|
|
|
|
|
<AlertTitle>{validationResult.isValid ? "검증 성공" : "검증 실패"}</AlertTitle>
|
|
|
|
|
<AlertDescription>
|
|
|
|
|
<div>{validationResult.summary}</div>
|
|
|
|
|
{validationResult.errors && validationResult.errors.length > 0 && (
|
|
|
|
|
<div className="mt-2 space-y-1">
|
|
|
|
|
<div className="font-medium">오류:</div>
|
|
|
|
|
<ul className="list-inside list-disc space-y-1">
|
|
|
|
|
{validationResult.errors.map((error: string, index: number) => (
|
|
|
|
|
<li key={index} className="text-sm">
|
|
|
|
|
{error}
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{validationResult.warnings && validationResult.warnings.length > 0 && (
|
|
|
|
|
<div className="mt-2 space-y-1">
|
|
|
|
|
<div className="font-medium">경고:</div>
|
|
|
|
|
<ul className="list-inside list-disc space-y-1">
|
|
|
|
|
{validationResult.warnings.map((warning: string, index: number) => (
|
|
|
|
|
<li key={index} className="text-sm text-orange-600">
|
|
|
|
|
{warning}
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</AlertDescription>
|
|
|
|
|
</Alert>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-12-05 10:46:10 +09:00
|
|
|
<DialogFooter className="gap-2">
|
2025-09-22 17:00:59 +09:00
|
|
|
<Button variant="outline" onClick={onClose} disabled={loading}>
|
|
|
|
|
취소
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
<Button variant="secondary" onClick={validateTable} disabled={!isFormValid || validating || loading}>
|
|
|
|
|
{validating ? (
|
|
|
|
|
<>
|
|
|
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
|
|
|
검증 중...
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
"검증하기"
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
onClick={handleCreateTable}
|
|
|
|
|
disabled={!isFormValid || loading || (validationResult && !validationResult.isValid)}
|
|
|
|
|
className="bg-green-600 hover:bg-green-700"
|
|
|
|
|
>
|
|
|
|
|
{loading ? (
|
|
|
|
|
<>
|
|
|
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
|
|
|
생성 중...
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
2025-10-31 17:58:49 +09:00
|
|
|
isDuplicateMode ? "복제 생성" : "테이블 생성"
|
2025-09-22 17:00:59 +09:00
|
|
|
)}
|
|
|
|
|
</Button>
|
2025-12-05 10:46:10 +09:00
|
|
|
</DialogFooter>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
2025-09-22 17:00:59 +09:00
|
|
|
);
|
|
|
|
|
}
|