"use client"; import { useState, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Search, Database, RefreshCw, Settings, Menu, X } from "lucide-react"; import { LoadingSpinner } from "@/components/common/LoadingSpinner"; import { toast } from "sonner"; interface TableInfo { tableName: string; displayName: string; description: string; columnCount: number; } interface ColumnTypeInfo { columnName: string; displayName: string; dbType: string; webType: string; detailSettings: string; description: string; isNullable: string; defaultValue?: string; maxLength?: number; numericPrecision?: number; numericScale?: number; codeCategory?: string; codeValue?: string; referenceTable?: string; referenceColumn?: string; } export default function TableManagementPage() { const [tables, setTables] = useState([]); const [columns, setColumns] = useState([]); const [selectedTable, setSelectedTable] = useState(null); const [searchTerm, setSearchTerm] = useState(""); const [loading, setLoading] = useState(false); const [columnsLoading, setColumnsLoading] = useState(false); const [originalColumns, setOriginalColumns] = useState([]); // 원본 데이터 저장 // 웹 타입 옵션 const webTypeOptions = [ { value: "text", label: "text", description: "일반 텍스트 입력" }, { value: "number", label: "number", description: "숫자 입력" }, { value: "date", label: "date", description: "날짜 선택기" }, { value: "code", label: "code", description: "코드 선택 (공통코드 지정)" }, { value: "entity", label: "entity", description: "엔티티 참조 (참조테이블 지정)" }, ]; // 참조 테이블 옵션 (실제 테이블 목록에서 가져옴) const referenceTableOptions = [ { value: "none", label: "테이블 선택" }, ...tables.map((table) => ({ value: table.tableName, label: table.displayName || table.tableName })), ]; // 공통 코드 옵션 (예시 - 실제로는 API에서 가져와야 함) const commonCodeOptions = [ { value: "none", label: "코드 선택" }, { value: "USER_STATUS", label: "사용자 상태" }, { value: "DEPT_TYPE", label: "부서 유형" }, { value: "PRODUCT_CATEGORY", label: "제품 카테고리" }, ]; // 테이블 목록 로드 const loadTables = async () => { setLoading(true); try { const response = await fetch("http://localhost:8080/api/table-management/tables"); // 응답 상태 확인 if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // 응답 텍스트를 먼저 확인 const responseText = await response.text(); console.log("Raw response:", responseText); // JSON 파싱 시도 let result; try { result = JSON.parse(responseText); } catch (parseError) { console.error("JSON 파싱 오류:", parseError); console.error("응답 텍스트:", responseText); throw new Error("JSON 파싱에 실패했습니다."); } if (result.success) { setTables(result.data); toast.success("테이블 목록을 성공적으로 로드했습니다."); } else { toast.error(result.message || "테이블 목록 로드에 실패했습니다."); } } catch (error) { console.error("테이블 목록 로드 실패:", error); toast.error("테이블 목록 로드 중 오류가 발생했습니다."); } finally { setLoading(false); } }; // 컬럼 타입 정보 로드 const loadColumnTypes = async (tableName: string) => { setColumnsLoading(true); try { const response = await fetch(`http://localhost:8080/api/table-management/tables/${tableName}/columns`); // 응답 상태 확인 if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // 응답 텍스트를 먼저 확인 const responseText = await response.text(); console.log("Raw column response:", responseText); // JSON 파싱 시도 let result; try { result = JSON.parse(responseText); } catch (parseError) { console.error("JSON 파싱 오류:", parseError); console.error("응답 텍스트:", responseText); throw new Error("JSON 파싱에 실패했습니다."); } if (result.success) { setColumns(result.data); setOriginalColumns(result.data); // 원본 데이터 저장 toast.success("컬럼 정보를 성공적으로 로드했습니다."); } else { toast.error(result.message || "컬럼 정보 로드에 실패했습니다."); } } catch (error) { console.error("컬럼 타입 정보 로드 실패:", error); toast.error("컬럼 정보 로드 중 오류가 발생했습니다."); } finally { setColumnsLoading(false); } }; // 테이블 선택 const handleTableSelect = (tableName: string) => { setSelectedTable(tableName); loadColumnTypes(tableName); }; // 웹 타입 변경 const handleWebTypeChange = (columnName: string, newWebType: string) => { setColumns((prev) => prev.map((col) => { if (col.columnName === columnName) { const webTypeOption = webTypeOptions.find((option) => option.value === newWebType); return { ...col, webType: newWebType, detailSettings: webTypeOption?.description || col.detailSettings, }; } return col; }), ); }; // 상세 설정 변경 (코드/엔티티 타입용) const handleDetailSettingsChange = (columnName: string, settingType: string, value: string) => { setColumns((prev) => prev.map((col) => { if (col.columnName === columnName) { let newDetailSettings = col.detailSettings; let codeCategory = col.codeCategory; let codeValue = col.codeValue; let referenceTable = col.referenceTable; let referenceColumn = col.referenceColumn; if (settingType === "code") { if (value === "none") { newDetailSettings = ""; codeCategory = undefined; codeValue = undefined; } else { const codeOption = commonCodeOptions.find((option) => option.value === value); newDetailSettings = codeOption ? `공통코드: ${codeOption.label}` : ""; codeCategory = value; codeValue = value; } } else if (settingType === "entity") { if (value === "none") { newDetailSettings = ""; referenceTable = undefined; referenceColumn = undefined; } else { const tableOption = referenceTableOptions.find((option) => option.value === value); newDetailSettings = tableOption ? `참조테이블: ${tableOption.label}` : ""; referenceTable = value; referenceColumn = "id"; // 기본값, 나중에 선택할 수 있도록 개선 가능 } } return { ...col, detailSettings: newDetailSettings, codeCategory, codeValue, referenceTable, referenceColumn, }; } return col; }), ); }; // 라벨 변경 핸들러 추가 const handleLabelChange = (columnName: string, newLabel: string) => { setColumns((prev) => prev.map((col) => { if (col.columnName === columnName) { return { ...col, displayName: newLabel, }; } return col; }), ); }; // 모든 컬럼 설정 저장 const saveAllColumnSettings = async () => { if (!selectedTable || columns.length === 0) return; try { // 모든 컬럼의 설정 데이터 준비 const columnSettings = columns.map((column) => ({ columnName: column.columnName, columnLabel: column.displayName, // 라벨 추가 webType: column.webType, detailSettings: column.detailSettings, codeCategory: column.codeCategory, codeValue: column.codeValue, referenceTable: column.referenceTable, referenceColumn: column.referenceColumn, })); // 전체 테이블 설정을 한 번에 저장 const response = await fetch( `http://localhost:8080/api/table-management/tables/${selectedTable}/columns/settings`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(columnSettings), }, ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || "컬럼 설정 저장에 실패했습니다."); } // 저장 성공 후 원본 데이터 업데이트 setOriginalColumns([...columns]); toast.success(`${columns.length}개의 컬럼 설정이 성공적으로 저장되었습니다.`); } catch (error) { console.error("컬럼 설정 저장 실패:", error); toast.error("컬럼 설정 저장 중 오류가 발생했습니다."); } }; // 필터링된 테이블 목록 const filteredTables = tables.filter( (table) => table.tableName.toLowerCase().includes(searchTerm.toLowerCase()) || table.displayName.toLowerCase().includes(searchTerm.toLowerCase()), ); // 선택된 테이블 정보 const selectedTableInfo = tables.find((table) => table.tableName === selectedTable); useEffect(() => { loadTables(); }, []); return (
{/* 헤더 영역 */}

데이터베이스 테이블 목록

데이터베이스 테이블과 컬럼 타입을 관리합니다.

{/* 검색 필터 */}
setSearchTerm(e.target.value)} className="pl-10" />
{/* 메인 컨텐츠 영역 */}
{/* 좌측: 테이블 목록 */}
데이터베이스 테이블 목록 테이블 목록 {loading ? (
) : (
{filteredTables.length === 0 ? (
{searchTerm ? "검색 결과가 없습니다." : "테이블이 없습니다."}
) : ( filteredTables.map((table) => (
handleTableSelect(table.tableName)} className={`cursor-pointer rounded-lg border p-3 transition-colors ${ selectedTable === table.tableName ? "bg-primary/10 border-primary" : "hover:bg-muted/50 border-border" }`} >

{table.displayName}

{table.tableName}

{table.description}

{table.columnCount}개
)) )}
)}
{/* 우측: 컬럼 타입 설정 */}
{selectedTable ? (
컬럼 타입 설정 - {selectedTableInfo?.displayName} ({selectedTableInfo?.tableName})
) : ( "컬럼 타입 설정" )}
{selectedTable && (

{selectedTableInfo?.description}

)}
{!selectedTable ? (
좌측에서 테이블을 선택하세요
) : columnsLoading ? (
) : (
{/* 모바일: 카드 형태 */}
{columns.map((column) => (

{column.columnName}

{column.dbType}
handleLabelChange(column.columnName, e.target.value)} placeholder="라벨 입력" className="text-sm" />
{column.webType === "code" && (
)} {column.webType === "entity" && (
)}
))}
{/* 태블릿/PC: 테이블 형태 */}
컬럼명 라벨 DB 타입 웹 타입 상세 설정 {columns.length === 0 ? ( 컬럼 정보가 없습니다. ) : ( columns.map((column) => ( {column.columnName} handleLabelChange(column.columnName, e.target.value)} placeholder="라벨 입력" className="w-24 text-xs md:w-32 md:text-sm" /> {column.dbType} {column.webType === "code" ? ( ) : column.webType === "entity" ? ( ) : ( {column.detailSettings} )} )) )}
)}
); }