"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"; import { useMultiLang } from "@/hooks/useMultiLang"; import { TABLE_MANAGEMENT_KEYS, WEB_TYPE_OPTIONS_WITH_KEYS } from "@/constants/tableManagement"; import { apiClient } from "@/lib/api/client"; 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 { userLang, getText } = useMultiLang({ companyCode: "*" }); 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 [uiTexts, setUiTexts] = useState>({}); // 다국어 텍스트 로드 useEffect(() => { const loadTexts = async () => { if (!userLang) return; try { const response = await apiClient.post( "/multilang/batch", { langKeys: Object.values(TABLE_MANAGEMENT_KEYS), }, { params: { companyCode: "*", menuCode: "TABLE_MANAGEMENT", userLang: userLang, }, }, ); if (response.data.success) { setUiTexts(response.data.data); } } catch (error) { console.error("다국어 텍스트 로드 실패:", error); } }; loadTexts(); }, [userLang]); // 텍스트 가져오기 함수 const getTextFromUI = (key: string, fallback?: string) => { return uiTexts[key] || fallback || key; }; // 웹 타입 옵션 (다국어 적용) const webTypeOptions = WEB_TYPE_OPTIONS_WITH_KEYS.map((option) => ({ value: option.value, label: getTextFromUI(option.labelKey, option.value), description: getTextFromUI(option.descriptionKey, option.value), })); // 웹타입 옵션 확인 (디버깅용) useEffect(() => { console.log("테이블 타입관리 - 웹타입 옵션 로드됨:", webTypeOptions); console.log("테이블 타입관리 - 웹타입 옵션 개수:", webTypeOptions.length); webTypeOptions.forEach((option, index) => { console.log(`${index + 1}. ${option.value}: ${option.label}`); }); }, [webTypeOptions]); // 참조 테이블 옵션 (실제 테이블 목록에서 가져옴) const referenceTableOptions = [ { value: "none", label: getTextFromUI(TABLE_MANAGEMENT_KEYS.LABEL_NONE, "선택 안함") }, ...tables.map((table) => ({ value: table.tableName, label: table.displayName || table.tableName })), ]; // 공통 코드 옵션 (예시 - 실제로는 API에서 가져와야 함) const commonCodeOptions = [ { value: "none", label: getTextFromUI(TABLE_MANAGEMENT_KEYS.SELECT_CODE_PLACEHOLDER, "코드 선택") }, { value: "USER_STATUS", label: "사용자 상태" }, { value: "DEPT_TYPE", label: "부서 유형" }, { value: "PRODUCT_CATEGORY", label: "제품 카테고리" }, ]; // 테이블 목록 로드 const loadTables = async () => { setLoading(true); try { const response = await apiClient.get("/table-management/tables"); // 응답 상태 확인 if (response.data.success) { setTables(response.data.data); toast.success("테이블 목록을 성공적으로 로드했습니다."); } else { toast.error(response.data.message || "테이블 목록 로드에 실패했습니다."); } } catch (error) { console.error("테이블 목록 로드 실패:", error); toast.error("테이블 목록 로드 중 오류가 발생했습니다."); } finally { setLoading(false); } }; // 컬럼 타입 정보 로드 const loadColumnTypes = async (tableName: string) => { setColumnsLoading(true); try { const response = await apiClient.get(`/table-management/tables/${tableName}/columns`); // 응답 상태 확인 if (response.data.success) { setColumns(response.data.data); setOriginalColumns(response.data.data); // 원본 데이터 저장 toast.success("컬럼 정보를 성공적으로 로드했습니다."); } else { toast.error(response.data.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 handleColumnChange = (index: number, field: keyof ColumnTypeInfo, value: any) => { setColumns((prev) => prev.map((col, i) => { if (i === index) { return { ...col, [field]: value, }; } return col; }), ); }; // 개별 컬럼 저장 const handleSaveColumn = async (column: ColumnTypeInfo) => { if (!selectedTable) return; try { const columnSetting = { columnName: column.columnName, // 실제 DB 컬럼명 (변경 불가) columnLabel: column.displayName, // 사용자가 입력한 표시명 webType: column.webType || "text", detailSettings: column.detailSettings || "", codeCategory: column.codeCategory || "", codeValue: column.codeValue || "", referenceTable: column.referenceTable || "", referenceColumn: column.referenceColumn || "", }; console.log("저장할 컬럼 설정:", columnSetting); const response = await apiClient.post(`/table-management/tables/${selectedTable}/columns/settings`, [ columnSetting, ]); if (response.data.success) { toast.success("컬럼 설정이 성공적으로 저장되었습니다."); // 원본 데이터 업데이트 setOriginalColumns((prev) => prev.map((col) => (col.columnName === column.columnName ? column : col))); // 저장 후 데이터 확인을 위해 다시 로드 setTimeout(() => { loadColumnTypes(selectedTable); }, 1000); } else { toast.error(response.data.message || "컬럼 설정 저장에 실패했습니다."); } } catch (error) { console.error("컬럼 설정 저장 실패:", error); toast.error("컬럼 설정 저장 중 오류가 발생했습니다."); } }; // 모든 컬럼 설정 저장 const saveAllColumnSettings = async () => { if (!selectedTable || columns.length === 0) return; try { // 모든 컬럼의 설정 데이터 준비 const columnSettings = columns.map((column) => ({ columnName: column.columnName, // 실제 DB 컬럼명 (변경 불가) columnLabel: column.displayName, // 사용자가 입력한 표시명 webType: column.webType || "text", detailSettings: column.detailSettings || "", codeCategory: column.codeCategory || "", codeValue: column.codeValue || "", referenceTable: column.referenceTable || "", referenceColumn: column.referenceColumn || "", })); console.log("저장할 전체 컬럼 설정:", columnSettings); // 전체 테이블 설정을 한 번에 저장 const response = await apiClient.post( `/table-management/tables/${selectedTable}/columns/settings`, columnSettings, ); if (response.data.success) { // 저장 성공 후 원본 데이터 업데이트 setOriginalColumns([...columns]); toast.success(`${columns.length}개의 컬럼 설정이 성공적으로 저장되었습니다.`); // 저장 후 데이터 확인을 위해 다시 로드 setTimeout(() => { loadColumnTypes(selectedTable); }, 1000); } else { toast.error(response.data.message || "컬럼 설정 저장에 실패했습니다."); } } 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 (
{/* 페이지 제목 */}

{getTextFromUI(TABLE_MANAGEMENT_KEYS.PAGE_TITLE, "테이블 타입 관리")}

{getTextFromUI(TABLE_MANAGEMENT_KEYS.PAGE_DESCRIPTION, "데이터베이스 테이블과 컬럼의 타입을 관리합니다")}

{/* 테이블 목록 */} {getTextFromUI(TABLE_MANAGEMENT_KEYS.TABLE_NAME, "테이블 목록")} {/* 검색 */}
setSearchTerm(e.target.value)} className="pl-10" />
{/* 테이블 목록 */}
{loading ? (
{getTextFromUI(TABLE_MANAGEMENT_KEYS.MESSAGE_LOADING_TABLES, "테이블 로딩 중...")}
) : tables.length === 0 ? (
{getTextFromUI(TABLE_MANAGEMENT_KEYS.MESSAGE_NO_TABLES, "테이블이 없습니다")}
) : ( tables .filter( (table) => table.tableName.toLowerCase().includes(searchTerm.toLowerCase()) || (table.displayName && table.displayName.toLowerCase().includes(searchTerm.toLowerCase())), ) .map((table) => (
handleTableSelect(table.tableName)} >

{table.displayName || table.tableName}

{table.description || getTextFromUI(TABLE_MANAGEMENT_KEYS.TABLE_DESCRIPTION, "설명 없음")}

{table.columnCount} {getTextFromUI(TABLE_MANAGEMENT_KEYS.TABLE_COLUMN_COUNT, "컬럼")}
)) )}
{/* 컬럼 타입 관리 */} {selectedTable ? ( <> {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_NAME, "컬럼")} - {selectedTable} ) : ( getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_NAME, "컬럼 타입 관리") )} {!selectedTable ? (
{getTextFromUI(TABLE_MANAGEMENT_KEYS.SELECT_TABLE_PLACEHOLDER, "테이블을 선택해주세요")}
) : ( <> {columnsLoading ? (
{getTextFromUI(TABLE_MANAGEMENT_KEYS.MESSAGE_LOADING_COLUMNS, "컬럼 정보 로딩 중...")}
) : columns.length === 0 ? (
{getTextFromUI(TABLE_MANAGEMENT_KEYS.MESSAGE_NO_COLUMNS, "컬럼이 없습니다")}
) : (
{getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_NAME, "컬럼명")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_DISPLAY_NAME, "표시명")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_DB_TYPE, "DB 타입")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_WEB_TYPE, "웹 타입")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_DETAIL_SETTINGS, "상세 설정")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_DESCRIPTION, "설명")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_NULLABLE, "NULL 허용")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_DEFAULT_VALUE, "기본값")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_MAX_LENGTH, "최대 길이")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_NUMERIC_PRECISION, "정밀도")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_NUMERIC_SCALE, "소수점")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_CODE_CATEGORY, "코드 카테고리")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_CODE_VALUE, "코드 값")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_REFERENCE_TABLE, "참조 테이블")} {getTextFromUI(TABLE_MANAGEMENT_KEYS.COLUMN_REFERENCE_COLUMN, "참조 컬럼")} Actions {columns.map((column, index) => ( {column.columnName} handleColumnChange(index, "displayName", e.target.value)} placeholder={column.columnName} className="w-32" /> {column.dbType}
{/* 웹타입 옵션 개수 표시 */}
사용 가능한 웹타입: {webTypeOptions.length}개
handleColumnChange(index, "detailSettings", e.target.value)} placeholder="상세 설정" className="w-32" /> handleColumnChange(index, "description", e.target.value)} placeholder="설명" className="w-32" /> {column.isNullable === "YES" ? getTextFromUI(TABLE_MANAGEMENT_KEYS.LABEL_YES, "예") : getTextFromUI(TABLE_MANAGEMENT_KEYS.LABEL_NO, "아니오")} {column.defaultValue || "-"} {column.maxLength || "-"} {column.numericPrecision || "-"} {column.numericScale || "-"} handleColumnChange(index, "codeValue", e.target.value)} placeholder="코드 값" className="w-32" /> handleColumnChange(index, "referenceColumn", e.target.value)} placeholder="참조 컬럼" className="w-32" />
))}
)} )}
); }