diff --git a/backend-node/src/services/screenManagementService.ts b/backend-node/src/services/screenManagementService.ts index a984fa85..198c850b 100644 --- a/backend-node/src/services/screenManagementService.ts +++ b/backend-node/src/services/screenManagementService.ts @@ -71,8 +71,9 @@ export class ScreenManagementService { // 화면 생성 (Raw Query) const [screen] = await query( `INSERT INTO screen_definitions ( - screen_name, screen_code, table_name, company_code, description, created_by - ) VALUES ($1, $2, $3, $4, $5, $6) + screen_name, screen_code, table_name, company_code, description, created_by, + db_source_type, db_connection_id + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`, [ screenData.screenName, @@ -81,6 +82,8 @@ export class ScreenManagementService { screenData.companyCode, screenData.description || null, screenData.createdBy, + screenData.dbSourceType || "internal", + screenData.dbConnectionId || null, ] ); @@ -1779,6 +1782,8 @@ export class ScreenManagementService { createdBy: data.created_by, updatedDate: data.updated_date, updatedBy: data.updated_by, + dbSourceType: data.db_source_type || "internal", + dbConnectionId: data.db_connection_id || undefined, }; } diff --git a/backend-node/src/types/screen.ts b/backend-node/src/types/screen.ts index 8075c78c..304c589c 100644 --- a/backend-node/src/types/screen.ts +++ b/backend-node/src/types/screen.ts @@ -151,6 +151,8 @@ export interface ScreenDefinition { createdBy?: string; updatedDate: Date; updatedBy?: string; + dbSourceType?: "internal" | "external"; + dbConnectionId?: number; } // 화면 생성 요청 @@ -161,6 +163,8 @@ export interface CreateScreenRequest { companyCode: string; description?: string; createdBy?: string; + dbSourceType?: "internal" | "external"; + dbConnectionId?: number; } // 화면 수정 요청 diff --git a/frontend/components/screen/CreateScreenModal.tsx b/frontend/components/screen/CreateScreenModal.tsx index 82fd5ecd..30d00556 100644 --- a/frontend/components/screen/CreateScreenModal.tsx +++ b/frontend/components/screen/CreateScreenModal.tsx @@ -6,7 +6,10 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Search, X } from "lucide-react"; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Search, X, Check, ChevronsUpDown, Database } from "lucide-react"; +import { cn } from "@/lib/utils"; import { screenApi, tableTypeApi } from "@/lib/api/screen"; import { ScreenDefinition } from "@/types/screen"; import { useAuth } from "@/hooks/useAuth"; @@ -28,6 +31,13 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre const [submitting, setSubmitting] = useState(false); const [tableSearchTerm, setTableSearchTerm] = useState(""); const searchInputRef = useRef(null); + + // 외부 DB 연결 관련 상태 + const [selectedDbSource, setSelectedDbSource] = useState<"internal" | number>("internal"); + const [externalConnections, setExternalConnections] = useState([]); + const [externalTableList, setExternalTableList] = useState([]); + const [loadingExternalTables, setLoadingExternalTables] = useState(false); + const [openDbSourceCombobox, setOpenDbSourceCombobox] = useState(false); // 화면 코드 자동 생성 const generateCode = async () => { try { @@ -39,6 +49,7 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre } }; + // 내부 DB 테이블 목록 로드 useEffect(() => { if (!open) return; let abort = false; @@ -48,7 +59,6 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre if (abort) return; setTables(list.map((t) => ({ tableName: t.tableName, displayName: t.displayName || t.tableName }))); } catch (e) { - // console.error("테이블 목록 조회 실패", e); setTables([]); } }; @@ -58,6 +68,83 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre }; }, [open]); + // 외부 DB 연결 목록 로드 + useEffect(() => { + if (!open) return; + const loadConnections = async () => { + try { + const token = localStorage.getItem("authToken"); + if (!token) { + console.warn("No auth token found"); + return; + } + + const response = await fetch("/api/external-db-connections/control/active", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (response && response.ok) { + const data = await response.json(); + if (data.success && data.data) { + const filtered = data.data.filter( + (conn: any) => !conn.connection_name.includes("메인") && !conn.connection_name.includes("현재 시스템"), + ); + setExternalConnections(filtered); + } + } + } catch (error) { + console.error("Failed to load external connections:", error); + setExternalConnections([]); + } + }; + loadConnections(); + }, [open]); + + // 외부 DB 테이블 목록 로드 + useEffect(() => { + if (selectedDbSource === "internal" || !selectedDbSource) { + setExternalTableList([]); + return; + } + + const loadExternalTables = async () => { + try { + setLoadingExternalTables(true); + const token = localStorage.getItem("authToken"); + + const response = await fetch(`/api/multi-connection/connections/${selectedDbSource}/tables`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (response && response.ok) { + const data = await response.json(); + if (data.success && data.data) { + const tables = Array.isArray(data.data) ? data.data : []; + const tableNames = tables + .map((t: any) => (typeof t === "string" ? t : t.tableName || t.table_name || t.tablename || t.name)) + .filter(Boolean); + setExternalTableList(tableNames); + } else { + setExternalTableList([]); + } + } else { + setExternalTableList([]); + } + } catch (error) { + console.error("외부 DB 테이블 목록 조회 오류:", error); + setExternalTableList([]); + } finally { + setLoadingExternalTables(false); + } + }; + + loadExternalTables(); + }, [selectedDbSource]); + // 모달이 열릴 때 자동으로 화면 코드 생성 useEffect(() => { if (open && !screenCode) { @@ -69,7 +156,7 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre return screenName.trim().length > 0 && screenCode.trim().length > 0 && tableName.trim().length > 0; }, [screenName, screenCode, tableName]); - // 테이블 필터링 + // 테이블 필터링 (내부 DB용) const filteredTables = useMemo(() => { if (!tableSearchTerm) return tables; const searchLower = tableSearchTerm.toLowerCase(); @@ -79,11 +166,20 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre ); }, [tables, tableSearchTerm]); + // 외부 DB 테이블 필터링 + const filteredExternalTables = useMemo(() => { + if (!tableSearchTerm) return externalTableList; + const searchLower = tableSearchTerm.toLowerCase(); + return externalTableList.filter((table) => table.toLowerCase().includes(searchLower)); + }, [externalTableList, tableSearchTerm]); + const handleSubmit = async () => { if (!isValid || submitting) return; try { setSubmitting(true); const companyCode = (user as any)?.company_code || (user as any)?.companyCode || "*"; + + // DB 소스 정보 추가 const created = await screenApi.createScreen({ screenName: screenName.trim(), screenCode: screenCode.trim(), @@ -91,6 +187,8 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre companyCode, description: description.trim() || undefined, createdBy: (user as any)?.userId, + dbSourceType: selectedDbSource === "internal" ? "internal" : "external", + dbConnectionId: selectedDbSource === "internal" ? undefined : Number(selectedDbSource), } as any); // 날짜 필드 보정 @@ -106,8 +204,8 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre setScreenCode(""); setTableName(""); setDescription(""); + setSelectedDbSource("internal"); } catch (e) { - // console.error("화면 생성 실패", e); // 필요 시 토스트 추가 가능 } finally { setSubmitting(false); @@ -136,14 +234,90 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre className="cursor-not-allowed bg-gray-50" /> + + {/* DB 소스 선택 */} +
+ + + + + + + + + + 데이터베이스를 찾을 수 없습니다. + + { + setSelectedDbSource("internal"); + setTableName(""); + setTableSearchTerm(""); + setOpenDbSourceCombobox(false); + }} + > + + +
+ 내부 데이터베이스 + PostgreSQL (현재 시스템) +
+
+ {externalConnections.map((conn: any) => ( + { + setSelectedDbSource(conn.id); + setTableName(""); + setTableSearchTerm(""); + setOpenDbSourceCombobox(false); + }} + > + + +
+ {conn.connection_name} + {conn.db_type?.toUpperCase()} +
+
+ ))} +
+
+
+
+
+

화면에서 사용할 데이터베이스를 선택합니다

+
+ + {/* 테이블 선택 */}
{ - // 이벤트가 Select로 전파되지 않도록 완전 차단 - e.stopPropagation(); - }} + onKeyDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()} onFocus={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} @@ -200,14 +365,28 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre {/* 테이블 옵션들 */}
- {filteredTables.length === 0 ? ( + {selectedDbSource === "internal" ? ( + // 내부 DB 테이블 목록 + filteredTables.length === 0 ? ( +
+ {tableSearchTerm ? `"${tableSearchTerm}"에 대한 검색 결과가 없습니다` : "테이블이 없습니다"} +
+ ) : ( + filteredTables.map((table) => ( + + {table.displayName} ({table.tableName}) + + )) + ) + ) : // 외부 DB 테이블 목록 + filteredExternalTables.length === 0 ? (
{tableSearchTerm ? `"${tableSearchTerm}"에 대한 검색 결과가 없습니다` : "테이블이 없습니다"}
) : ( - filteredTables.map((table) => ( - - {table.displayName} ({table.tableName}) + filteredExternalTables.map((tableName) => ( + + {tableName} )) )} @@ -215,6 +394,7 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre
+
setDescription(e.target.value)} /> diff --git a/frontend/types/screen-management.ts b/frontend/types/screen-management.ts index f52e00f8..2d1e6015 100644 --- a/frontend/types/screen-management.ts +++ b/frontend/types/screen-management.ts @@ -359,6 +359,8 @@ export interface ScreenDefinition { updatedDate: Date; createdBy?: string; updatedBy?: string; + dbSourceType?: "internal" | "external"; + dbConnectionId?: number; } /** @@ -371,6 +373,8 @@ export interface CreateScreenRequest { tableLabel?: string; companyCode: CompanyCode; description?: string; + dbSourceType?: "internal" | "external"; + dbConnectionId?: number; } /**