"use client"; import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { ArrowLeft, Loader2, Play } from "lucide-react"; import { YardPlacement, YardDataSourceConfig, YardDataBinding, QueryResult } from "./types"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Card } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { AlertCircle } from "lucide-react"; import { dashboardApi } from "@/lib/api/dashboard"; import { ExternalDbConnectionAPI, type ExternalDbConnection } from "@/lib/api/externalDbConnection"; import { Textarea } from "@/components/ui/textarea"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; interface YardElementConfigPanelProps { placement: YardPlacement; onSave: (updatedData: Partial) => void; // Promise 제거 (즉시 로컬 상태 업데이트) onCancel: () => void; } export default function YardElementConfigPanel({ placement, onSave, onCancel }: YardElementConfigPanelProps) { // 데이터 소스 설정 const [dataSourceType, setDataSourceType] = useState<"database" | "external_db" | "rest_api">( (placement.data_source_config?.type as "database" | "external_db" | "rest_api") || "database", ); const [query, setQuery] = useState(placement.data_source_config?.query || ""); const [externalConnectionId, setExternalConnectionId] = useState( placement.data_source_config?.connectionId?.toString() || "", ); const [externalConnections, setExternalConnections] = useState([]); // REST API 설정 const [apiUrl, setApiUrl] = useState(placement.data_source_config?.url || ""); const [apiMethod, setApiMethod] = useState<"GET" | "POST">(placement.data_source_config?.method || "GET"); const [apiDataPath, setApiDataPath] = useState(placement.data_source_config?.dataPath || ""); // 쿼리 결과 및 매핑 const [queryResult, setQueryResult] = useState(null); const [isExecuting, setIsExecuting] = useState(false); const [selectedRowIndex, setSelectedRowIndex] = useState(placement.data_binding?.selectedRowIndex ?? 0); const [materialNameField, setMaterialNameField] = useState(placement.data_binding?.materialNameField || ""); const [quantityField, setQuantityField] = useState(placement.data_binding?.quantityField || ""); const [unit, setUnit] = useState(placement.data_binding?.unit || "EA"); // 배치 설정 const [color, setColor] = useState(placement.color || "#3b82f6"); const [sizeX, setSizeX] = useState(placement.size_x); const [sizeY, setSizeY] = useState(placement.size_y); const [sizeZ, setSizeZ] = useState(placement.size_z); // 에러 const [error, setError] = useState(null); // 외부 DB 커넥션 목록 로드 useEffect(() => { const loadConnections = async () => { try { const connections = await ExternalDbConnectionAPI.getConnections(); setExternalConnections(connections || []); } catch (err) { console.error("외부 DB 커넥션 로드 실패:", err); } }; if (dataSourceType === "external_db") { loadConnections(); } }, [dataSourceType]); // 쿼리 실행 const executeQuery = async () => { if (!query.trim()) { setError("쿼리를 입력해주세요."); return; } if (dataSourceType === "external_db" && !externalConnectionId) { setError("외부 DB 커넥션을 선택해주세요."); return; } setIsExecuting(true); setError(null); try { let apiResult: { columns: string[]; rows: Record[]; rowCount: number }; if (dataSourceType === "external_db" && externalConnectionId) { const result = await ExternalDbConnectionAPI.executeQuery(parseInt(externalConnectionId), query.trim()); if (!result.success) { throw new Error(result.message || "외부 DB 쿼리 실행에 실패했습니다."); } apiResult = { columns: result.data?.[0] ? Object.keys(result.data[0]) : [], rows: result.data || [], rowCount: result.data?.length || 0, }; } else { apiResult = await dashboardApi.executeQuery(query.trim()); } setQueryResult({ columns: apiResult.columns, rows: apiResult.rows, totalRows: apiResult.rowCount, }); // 자동으로 첫 번째 필드 선택 if (apiResult.columns.length > 0) { if (!materialNameField) setMaterialNameField(apiResult.columns[0]); if (!quantityField && apiResult.columns.length > 1) setQuantityField(apiResult.columns[1]); } } catch (err) { const errorMessage = err instanceof Error ? err.message : "쿼리 실행 중 오류가 발생했습니다."; setError(errorMessage); } finally { setIsExecuting(false); } }; // REST API 호출 const executeRestApi = async () => { if (!apiUrl.trim()) { setError("API URL을 입력해주세요."); return; } setIsExecuting(true); setError(null); try { const response = await fetch(apiUrl, { method: apiMethod, }); if (!response.ok) { throw new Error(`API 호출 실패: ${response.status}`); } let data = await response.json(); // dataPath가 있으면 해당 경로의 데이터 추출 if (apiDataPath) { const pathParts = apiDataPath.split("."); for (const part of pathParts) { data = data[part]; } } // 배열이 아니면 배열로 변환 if (!Array.isArray(data)) { data = [data]; } const columns = data.length > 0 ? Object.keys(data[0]) : []; setQueryResult({ columns, rows: data, totalRows: data.length, }); // 자동으로 첫 번째 필드 선택 if (columns.length > 0) { if (!materialNameField) setMaterialNameField(columns[0]); if (!quantityField && columns.length > 1) setQuantityField(columns[1]); } } catch (err) { const errorMessage = err instanceof Error ? err.message : "API 호출 중 오류가 발생했습니다."; setError(errorMessage); } finally { setIsExecuting(false); } }; // 적용 (로컬 상태만 업데이트, 서버 저장은 나중에 일괄 처리) const handleApply = () => { // 검증 if (!queryResult) { setError("먼저 데이터를 조회해주세요."); return; } if (!materialNameField || !quantityField) { setError("자재명과 수량 필드를 선택해주세요."); return; } if (!unit.trim()) { setError("단위를 입력해주세요."); return; } if (selectedRowIndex >= queryResult.rows.length) { setError("선택한 행이 유효하지 않습니다."); return; } const selectedRow = queryResult.rows[selectedRowIndex]; const materialName = selectedRow[materialNameField]; const quantity = selectedRow[quantityField]; const dataSourceConfig: YardDataSourceConfig = { type: dataSourceType, query: dataSourceType !== "rest_api" ? query : undefined, connectionId: dataSourceType === "external_db" ? parseInt(externalConnectionId) : undefined, url: dataSourceType === "rest_api" ? apiUrl : undefined, method: dataSourceType === "rest_api" ? apiMethod : undefined, dataPath: dataSourceType === "rest_api" && apiDataPath ? apiDataPath : undefined, }; const dataBinding: YardDataBinding = { selectedRowIndex, materialNameField, quantityField, unit: unit.trim(), }; const updatedData: Partial = { material_name: String(materialName), quantity: Number(quantity), unit: unit.trim(), color, size_x: sizeX, size_y: sizeY, size_z: sizeZ, data_source_type: dataSourceType, data_source_config: dataSourceConfig, data_binding: dataBinding, }; onSave(updatedData); // 동기적으로 즉시 로컬 상태 업데이트 }; return (
{/* 헤더 */}

데이터 바인딩 설정

{/* 에러 메시지 */} {error && ( {error} )} {/* 컨텐츠 */}
{/* 1단계: 데이터 소스 선택 */}

1단계: 데이터 소스 선택

setDataSourceType(value as "database" | "external_db" | "rest_api")} >
{/* 현재 DB 또는 외부 DB */} {dataSourceType !== "rest_api" && ( <> {dataSourceType === "external_db" && (
)}