"use client"; import { useEffect, useMemo, useState, useRef } from "react"; import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, ResizableDialogTitle, ResizableDialogFooter, } from "@/components/ui/resizable-dialog"; 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 { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Search, X, Check, ChevronsUpDown, Database, Globe } from "lucide-react"; import Link from "next/link"; import { cn } from "@/lib/utils"; import { screenApi, tableTypeApi } from "@/lib/api/screen"; import { ScreenDefinition } from "@/types/screen"; import { useAuth } from "@/hooks/useAuth"; import { ExternalRestApiConnectionAPI, ExternalRestApiConnection } from "@/lib/api/externalRestApiConnection"; interface CreateScreenModalProps { open: boolean; onOpenChange: (open: boolean) => void; onCreated?: (screen: ScreenDefinition) => void; } export default function CreateScreenModal({ open, onOpenChange, onCreated }: CreateScreenModalProps) { const { user } = useAuth(); const [screenName, setScreenName] = useState(""); const [screenCode, setScreenCode] = useState(""); const [tableName, setTableName] = useState(""); const [description, setDescription] = useState(""); const [tables, setTables] = useState>([]); const [submitting, setSubmitting] = useState(false); const [tableSearchTerm, setTableSearchTerm] = useState(""); const searchInputRef = useRef(null); // 데이터 소스 타입 (database: 데이터베이스, restapi: REST API) const [dataSourceType, setDataSourceType] = useState<"database" | "restapi">("database"); // 외부 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); // REST API 연결 관련 상태 const [restApiConnections, setRestApiConnections] = useState([]); const [selectedRestApiId, setSelectedRestApiId] = useState(null); const [openRestApiCombobox, setOpenRestApiCombobox] = useState(false); const [restApiEndpoint, setRestApiEndpoint] = useState(""); const [restApiJsonPath, setRestApiJsonPath] = useState("data"); // 응답에서 데이터 추출 경로 // 화면 코드 자동 생성 const generateCode = async () => { try { const companyCode = (user as any)?.company_code || (user as any)?.companyCode || "*"; const generatedCode = await screenApi.generateScreenCode(companyCode); setScreenCode(generatedCode); } catch (e) { // console.error("화면 코드 생성 실패", e); } }; // 내부 DB 테이블 목록 로드 useEffect(() => { if (!open) return; let abort = false; const loadTables = async () => { try { const list = await tableTypeApi.getTables(); if (abort) return; setTables(list.map((t) => ({ tableName: t.tableName, displayName: t.displayName || t.tableName }))); } catch (e) { setTables([]); } }; loadTables(); return () => { abort = true; }; }, [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]); // REST API 연결 목록 로드 useEffect(() => { if (!open) return; const loadRestApiConnections = async () => { try { const connections = await ExternalRestApiConnectionAPI.getConnections({ is_active: "Y" }); setRestApiConnections(connections); } catch (error) { console.error("Failed to load REST API connections:", error); setRestApiConnections([]); } }; loadRestApiConnections(); }, [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) { generateCode(); } }, [open, screenCode]); const isValid = useMemo(() => { const baseValid = screenName.trim().length > 0 && screenCode.trim().length > 0; if (dataSourceType === "database") { return baseValid && tableName.trim().length > 0; } else { // REST API: 연결 선택 필수 return baseValid && selectedRestApiId !== null; } }, [screenName, screenCode, tableName, dataSourceType, selectedRestApiId]); // 테이블 필터링 (내부 DB용) const filteredTables = useMemo(() => { if (!tableSearchTerm) return tables; const searchLower = tableSearchTerm.toLowerCase(); return tables.filter( (table) => table.displayName.toLowerCase().includes(searchLower) || table.tableName.toLowerCase().includes(searchLower), ); }, [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 || "*"; // 데이터 소스 타입에 따라 다른 정보 전달 const createData: any = { screenName: screenName.trim(), screenCode: screenCode.trim(), companyCode, description: description.trim() || undefined, createdBy: (user as any)?.userId, dataSourceType: dataSourceType, }; if (dataSourceType === "database") { // 데이터베이스 소스 createData.tableName = tableName.trim(); createData.dbSourceType = selectedDbSource === "internal" ? "internal" : "external"; createData.dbConnectionId = selectedDbSource === "internal" ? undefined : Number(selectedDbSource); } else { // REST API 소스 createData.tableName = `_restapi_${selectedRestApiId}`; // REST API용 가상 테이블명 createData.restApiConnectionId = selectedRestApiId; createData.restApiEndpoint = restApiEndpoint.trim() || undefined; createData.restApiJsonPath = restApiJsonPath.trim() || "data"; } const created = await screenApi.createScreen(createData); // 날짜 필드 보정 const mapped: ScreenDefinition = { ...created, createdDate: created.createdDate ? new Date(created.createdDate as any) : new Date(), updatedDate: created.updatedDate ? new Date(created.updatedDate as any) : new Date(), } as ScreenDefinition; onCreated?.(mapped); onOpenChange(false); // 폼 초기화 setScreenName(""); setScreenCode(""); setTableName(""); setDescription(""); setSelectedDbSource("internal"); setDataSourceType("database"); setSelectedRestApiId(null); setRestApiEndpoint(""); setRestApiJsonPath("data"); } catch (e) { // 필요 시 토스트 추가 가능 } finally { setSubmitting(false); } }; return ( 새 화면 생성
setScreenName(e.target.value)} />
setDescription(e.target.value)} placeholder="화면 설명을 입력하세요 (모달에 표시됨)" />
{/* 데이터 소스 타입 선택 */}
{/* 데이터베이스 소스 설정 */} {dataSourceType === "database" && ( <> {/* 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()}
))}

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

)} {/* REST API 소스 설정 */} {dataSourceType === "restapi" && ( <> {/* REST API 연결 선택 */}
등록된 REST API 연결이 없습니다. {restApiConnections.map((conn) => ( { setSelectedRestApiId(conn.id!); setRestApiEndpoint(conn.endpoint_path || ""); setOpenRestApiCombobox(false); }} >
{conn.connection_name} {conn.base_url}
))}

등록된 REST API 연결을 선택합니다. 새 연결 등록

{/* 엔드포인트 경로 */}
setRestApiEndpoint(e.target.value)} placeholder="/api/data 또는 /users" />

기본 URL 뒤에 추가될 경로 (선택사항)

{/* JSON Path */}
setRestApiJsonPath(e.target.value)} placeholder="data 또는 result.items" />

API 응답에서 데이터 배열을 추출할 경로 (예: data, result.items)

)} {/* 테이블 선택 (데이터베이스 모드일 때만) */} {dataSourceType === "database" && (
{ e.stopPropagation(); setTableSearchTerm(e.target.value); }} onKeyDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()} onFocus={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-8 w-full rounded-md border px-3 py-2 pr-8 pl-10 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" /> {tableSearchTerm && ( )}
{/* 테이블 옵션들 */}
{selectedDbSource === "internal" ? ( // 내부 DB 테이블 목록 filteredTables.length === 0 ? (
{tableSearchTerm ? `"${tableSearchTerm}"에 대한 검색 결과가 없습니다` : "테이블이 없습니다"}
) : ( filteredTables.map((table) => ( {table.displayName} ({table.tableName}) )) ) ) : // 외부 DB 테이블 목록 filteredExternalTables.length === 0 ? (
{tableSearchTerm ? `"${tableSearchTerm}"에 대한 검색 결과가 없습니다` : "테이블이 없습니다"}
) : ( filteredExternalTables.map((tableName) => ( {tableName} )) )}
)}
); }