restapi도 가능하게 구현
This commit is contained in:
@@ -838,18 +838,46 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
// 화면의 기본 테이블/REST API 정보 로드
|
||||
useEffect(() => {
|
||||
const loadScreenDataSource = async () => {
|
||||
console.log("🔍 [ScreenDesigner] 데이터 소스 로드 시작:", {
|
||||
screenId: selectedScreen?.screenId,
|
||||
screenName: selectedScreen?.screenName,
|
||||
dataSourceType: selectedScreen?.dataSourceType,
|
||||
tableName: selectedScreen?.tableName,
|
||||
restApiConnectionId: selectedScreen?.restApiConnectionId,
|
||||
restApiEndpoint: selectedScreen?.restApiEndpoint,
|
||||
restApiJsonPath: selectedScreen?.restApiJsonPath,
|
||||
});
|
||||
|
||||
// REST API 데이터 소스인 경우
|
||||
if (selectedScreen?.dataSourceType === "restapi" && selectedScreen?.restApiConnectionId) {
|
||||
// tableName이 restapi_로 시작하면 REST API로 간주
|
||||
const isRestApi = selectedScreen?.dataSourceType === "restapi" ||
|
||||
selectedScreen?.tableName?.startsWith("restapi_") ||
|
||||
selectedScreen?.tableName?.startsWith("_restapi_");
|
||||
|
||||
if (isRestApi && (selectedScreen?.restApiConnectionId || selectedScreen?.tableName)) {
|
||||
try {
|
||||
// 연결 ID 추출 (restApiConnectionId가 없으면 tableName에서 추출)
|
||||
let connectionId = selectedScreen?.restApiConnectionId;
|
||||
if (!connectionId && selectedScreen?.tableName) {
|
||||
const match = selectedScreen.tableName.match(/restapi_(\d+)/);
|
||||
connectionId = match ? parseInt(match[1]) : undefined;
|
||||
}
|
||||
|
||||
if (!connectionId) {
|
||||
throw new Error("REST API 연결 ID를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
console.log("🌐 [ScreenDesigner] REST API 데이터 로드:", { connectionId });
|
||||
|
||||
const restApiData = await ExternalRestApiConnectionAPI.fetchData(
|
||||
selectedScreen.restApiConnectionId,
|
||||
selectedScreen.restApiEndpoint,
|
||||
selectedScreen.restApiJsonPath || "data",
|
||||
connectionId,
|
||||
selectedScreen?.restApiEndpoint,
|
||||
selectedScreen?.restApiJsonPath || "response", // 기본값을 response로 변경
|
||||
);
|
||||
|
||||
// REST API 응답에서 컬럼 정보 생성
|
||||
const columns: ColumnInfo[] = restApiData.columns.map((col) => ({
|
||||
tableName: `restapi_${selectedScreen.restApiConnectionId}`,
|
||||
tableName: `restapi_${connectionId}`,
|
||||
columnName: col.columnName,
|
||||
columnLabel: col.columnLabel,
|
||||
dataType: col.dataType === "string" ? "varchar" : col.dataType === "number" ? "numeric" : col.dataType,
|
||||
@@ -861,10 +889,17 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
}));
|
||||
|
||||
const tableInfo: TableInfo = {
|
||||
tableName: `restapi_${selectedScreen.restApiConnectionId}`,
|
||||
tableName: `restapi_${connectionId}`,
|
||||
tableLabel: restApiData.connectionInfo.connectionName || "REST API 데이터",
|
||||
columns,
|
||||
};
|
||||
|
||||
console.log("✅ [ScreenDesigner] REST API 컬럼 로드 완료:", {
|
||||
tableName: tableInfo.tableName,
|
||||
tableLabel: tableInfo.tableLabel,
|
||||
columnsCount: columns.length,
|
||||
columns: columns.map(c => c.columnName),
|
||||
});
|
||||
|
||||
setTables([tableInfo]);
|
||||
console.log("REST API 데이터 소스 로드 완료:", {
|
||||
|
||||
@@ -41,6 +41,7 @@ import { cn } from "@/lib/utils";
|
||||
import { MoreHorizontal, Edit, Trash2, Copy, Eye, Plus, Search, Palette, RotateCcw, Trash, Check, ChevronsUpDown } from "lucide-react";
|
||||
import { ScreenDefinition } from "@/types/screen";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import { ExternalRestApiConnectionAPI, ExternalRestApiConnection } from "@/lib/api/externalRestApiConnection";
|
||||
import CreateScreenModal from "./CreateScreenModal";
|
||||
import CopyScreenModal from "./CopyScreenModal";
|
||||
import dynamic from "next/dynamic";
|
||||
@@ -132,10 +133,18 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
description: "",
|
||||
isActive: "Y",
|
||||
tableName: "",
|
||||
dataSourceType: "database" as "database" | "restapi",
|
||||
restApiConnectionId: null as number | null,
|
||||
restApiEndpoint: "",
|
||||
restApiJsonPath: "data",
|
||||
});
|
||||
const [tables, setTables] = useState<Array<{ tableName: string; tableLabel: string }>>([]);
|
||||
const [loadingTables, setLoadingTables] = useState(false);
|
||||
const [tableComboboxOpen, setTableComboboxOpen] = useState(false);
|
||||
|
||||
// REST API 연결 관련 상태 (편집용)
|
||||
const [editRestApiConnections, setEditRestApiConnections] = useState<ExternalRestApiConnection[]>([]);
|
||||
const [editRestApiComboboxOpen, setEditRestApiComboboxOpen] = useState(false);
|
||||
|
||||
// 미리보기 관련 상태
|
||||
const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
|
||||
@@ -272,11 +281,19 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
|
||||
const handleEdit = async (screen: ScreenDefinition) => {
|
||||
setScreenToEdit(screen);
|
||||
|
||||
// 데이터 소스 타입 결정
|
||||
const isRestApi = screen.dataSourceType === "restapi" || screen.tableName?.startsWith("_restapi_");
|
||||
|
||||
setEditFormData({
|
||||
screenName: screen.screenName,
|
||||
description: screen.description || "",
|
||||
isActive: screen.isActive,
|
||||
tableName: screen.tableName || "",
|
||||
dataSourceType: isRestApi ? "restapi" : "database",
|
||||
restApiConnectionId: (screen as any).restApiConnectionId || null,
|
||||
restApiEndpoint: (screen as any).restApiEndpoint || "",
|
||||
restApiJsonPath: (screen as any).restApiJsonPath || "data",
|
||||
});
|
||||
setEditDialogOpen(true);
|
||||
|
||||
@@ -298,14 +315,50 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
} finally {
|
||||
setLoadingTables(false);
|
||||
}
|
||||
|
||||
// REST API 연결 목록 로드
|
||||
try {
|
||||
const connections = await ExternalRestApiConnectionAPI.getConnections({ is_active: "Y" });
|
||||
setEditRestApiConnections(connections);
|
||||
} catch (error) {
|
||||
console.error("REST API 연결 목록 조회 실패:", error);
|
||||
setEditRestApiConnections([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSave = async () => {
|
||||
if (!screenToEdit) return;
|
||||
|
||||
try {
|
||||
// 데이터 소스 타입에 따라 업데이트 데이터 구성
|
||||
const updateData: any = {
|
||||
screenName: editFormData.screenName,
|
||||
description: editFormData.description,
|
||||
isActive: editFormData.isActive,
|
||||
dataSourceType: editFormData.dataSourceType,
|
||||
};
|
||||
|
||||
if (editFormData.dataSourceType === "database") {
|
||||
updateData.tableName = editFormData.tableName;
|
||||
updateData.restApiConnectionId = null;
|
||||
updateData.restApiEndpoint = null;
|
||||
updateData.restApiJsonPath = null;
|
||||
} else {
|
||||
// REST API
|
||||
updateData.tableName = `_restapi_${editFormData.restApiConnectionId}`;
|
||||
updateData.restApiConnectionId = editFormData.restApiConnectionId;
|
||||
updateData.restApiEndpoint = editFormData.restApiEndpoint;
|
||||
updateData.restApiJsonPath = editFormData.restApiJsonPath || "data";
|
||||
}
|
||||
|
||||
console.log("📤 화면 편집 저장 요청:", {
|
||||
screenId: screenToEdit.screenId,
|
||||
editFormData,
|
||||
updateData,
|
||||
});
|
||||
|
||||
// 화면 정보 업데이트 API 호출
|
||||
await screenApi.updateScreenInfo(screenToEdit.screenId, editFormData);
|
||||
await screenApi.updateScreenInfo(screenToEdit.screenId, updateData);
|
||||
|
||||
// 선택된 테이블의 라벨 찾기
|
||||
const selectedTable = tables.find((t) => t.tableName === editFormData.tableName);
|
||||
@@ -318,10 +371,11 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
? {
|
||||
...s,
|
||||
screenName: editFormData.screenName,
|
||||
tableName: editFormData.tableName,
|
||||
tableName: updateData.tableName,
|
||||
tableLabel: tableLabel,
|
||||
description: editFormData.description,
|
||||
isActive: editFormData.isActive,
|
||||
dataSourceType: editFormData.dataSourceType,
|
||||
}
|
||||
: s,
|
||||
),
|
||||
@@ -1216,65 +1270,184 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
placeholder="화면명을 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 데이터 소스 타입 선택 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-tableName">테이블 *</Label>
|
||||
<Popover open={tableComboboxOpen} onOpenChange={setTableComboboxOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={tableComboboxOpen}
|
||||
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
||||
disabled={loadingTables}
|
||||
>
|
||||
{loadingTables
|
||||
? "로딩 중..."
|
||||
: editFormData.tableName
|
||||
? tables.find((table) => table.tableName === editFormData.tableName)?.tableLabel || editFormData.tableName
|
||||
: "테이블을 선택하세요"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
style={{ width: "var(--radix-popover-trigger-width)" }}
|
||||
align="start"
|
||||
>
|
||||
<Command>
|
||||
<CommandInput placeholder="테이블 검색..." className="text-xs sm:text-sm" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs sm:text-sm">
|
||||
테이블을 찾을 수 없습니다.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{tables.map((table) => (
|
||||
<CommandItem
|
||||
key={table.tableName}
|
||||
value={`${table.tableName} ${table.tableLabel}`}
|
||||
onSelect={() => {
|
||||
setEditFormData({ ...editFormData, tableName: table.tableName });
|
||||
setTableComboboxOpen(false);
|
||||
}}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
editFormData.tableName === table.tableName ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{table.tableLabel}</span>
|
||||
<span className="text-[10px] text-gray-500">{table.tableName}</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Label>데이터 소스 타입</Label>
|
||||
<Select
|
||||
value={editFormData.dataSourceType}
|
||||
onValueChange={(value: "database" | "restapi") => {
|
||||
setEditFormData({
|
||||
...editFormData,
|
||||
dataSourceType: value,
|
||||
tableName: "",
|
||||
restApiConnectionId: null,
|
||||
restApiEndpoint: "",
|
||||
restApiJsonPath: "data",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="database">데이터베이스</SelectItem>
|
||||
<SelectItem value="restapi">REST API</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 데이터베이스 선택 (database 타입인 경우) */}
|
||||
{editFormData.dataSourceType === "database" && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-tableName">테이블 *</Label>
|
||||
<Popover open={tableComboboxOpen} onOpenChange={setTableComboboxOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={tableComboboxOpen}
|
||||
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
||||
disabled={loadingTables}
|
||||
>
|
||||
{loadingTables
|
||||
? "로딩 중..."
|
||||
: editFormData.tableName
|
||||
? tables.find((table) => table.tableName === editFormData.tableName)?.tableLabel || editFormData.tableName
|
||||
: "테이블을 선택하세요"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
style={{ width: "var(--radix-popover-trigger-width)" }}
|
||||
align="start"
|
||||
>
|
||||
<Command>
|
||||
<CommandInput placeholder="테이블 검색..." className="text-xs sm:text-sm" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs sm:text-sm">
|
||||
테이블을 찾을 수 없습니다.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{tables.map((table) => (
|
||||
<CommandItem
|
||||
key={table.tableName}
|
||||
value={`${table.tableName} ${table.tableLabel}`}
|
||||
onSelect={() => {
|
||||
setEditFormData({ ...editFormData, tableName: table.tableName });
|
||||
setTableComboboxOpen(false);
|
||||
}}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
editFormData.tableName === table.tableName ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{table.tableLabel}</span>
|
||||
<span className="text-[10px] text-gray-500">{table.tableName}</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* REST API 선택 (restapi 타입인 경우) */}
|
||||
{editFormData.dataSourceType === "restapi" && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label>REST API 연결 *</Label>
|
||||
<Popover open={editRestApiComboboxOpen} onOpenChange={setEditRestApiComboboxOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={editRestApiComboboxOpen}
|
||||
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
||||
>
|
||||
{editFormData.restApiConnectionId
|
||||
? editRestApiConnections.find((c) => c.id === editFormData.restApiConnectionId)?.connection_name || "선택된 연결"
|
||||
: "REST API 연결 선택"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
style={{ width: "var(--radix-popover-trigger-width)" }}
|
||||
align="start"
|
||||
>
|
||||
<Command>
|
||||
<CommandInput placeholder="연결 검색..." className="text-xs sm:text-sm" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs sm:text-sm">
|
||||
연결을 찾을 수 없습니다.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{editRestApiConnections.map((conn) => (
|
||||
<CommandItem
|
||||
key={conn.id}
|
||||
value={conn.connection_name}
|
||||
onSelect={() => {
|
||||
setEditFormData({ ...editFormData, restApiConnectionId: conn.id || null });
|
||||
setEditRestApiComboboxOpen(false);
|
||||
}}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
editFormData.restApiConnectionId === conn.id ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{conn.connection_name}</span>
|
||||
<span className="text-[10px] text-gray-500">{conn.base_url}</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-restApiEndpoint">API 엔드포인트</Label>
|
||||
<Input
|
||||
id="edit-restApiEndpoint"
|
||||
value={editFormData.restApiEndpoint}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, restApiEndpoint: e.target.value })}
|
||||
placeholder="예: /api/data/list"
|
||||
/>
|
||||
<p className="text-muted-foreground text-[10px]">
|
||||
데이터를 조회할 API 엔드포인트 경로입니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-restApiJsonPath">JSON 경로</Label>
|
||||
<Input
|
||||
id="edit-restApiJsonPath"
|
||||
value={editFormData.restApiJsonPath}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, restApiJsonPath: e.target.value })}
|
||||
placeholder="예: data 또는 result.items"
|
||||
/>
|
||||
<p className="text-muted-foreground text-[10px]">
|
||||
응답 JSON에서 데이터 배열의 경로입니다 (기본: data)
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-description">설명</Label>
|
||||
<Textarea
|
||||
@@ -1305,7 +1478,14 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
<Button variant="outline" onClick={() => setEditDialogOpen(false)}>
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleEditSave} disabled={!editFormData.screenName.trim() || !editFormData.tableName.trim()}>
|
||||
<Button
|
||||
onClick={handleEditSave}
|
||||
disabled={
|
||||
!editFormData.screenName.trim() ||
|
||||
(editFormData.dataSourceType === "database" && !editFormData.tableName.trim()) ||
|
||||
(editFormData.dataSourceType === "restapi" && !editFormData.restApiConnectionId)
|
||||
}
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
Reference in New Issue
Block a user