From d33daf0a3de1fb72c847a9ab452d79de06acb7d5 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 19 Dec 2025 16:40:40 +0900 Subject: [PATCH] =?UTF-8?q?=EB=89=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screen/panels/ComponentsPanel.tsx | 18 +- .../config-panels/UnifiedBizConfigPanel.tsx | 273 +++++++++++++++--- .../UnifiedHierarchyConfigPanel.tsx | 229 ++++++++++++--- .../config-panels/UnifiedListConfigPanel.tsx | 133 +++++++-- 4 files changed, 527 insertions(+), 126 deletions(-) diff --git a/frontend/components/screen/panels/ComponentsPanel.tsx b/frontend/components/screen/panels/ComponentsPanel.tsx index 4ef7afda..1544e096 100644 --- a/frontend/components/screen/panels/ComponentsPanel.tsx +++ b/frontend/components/screen/panels/ComponentsPanel.tsx @@ -86,22 +86,8 @@ export function ComponentsPanel({ tags: ["table", "list", "card", "kanban", "unified"], defaultSize: { width: 600, height: 400 }, }, - { - id: "unified-layout", - name: "통합 레이아웃", - description: "그리드, 분할 패널, 플렉스 등 다양한 레이아웃 지원", - category: "layout" as ComponentCategory, - tags: ["grid", "split", "flex", "unified"], - defaultSize: { width: 400, height: 300 }, - }, - { - id: "unified-group", - name: "통합 그룹", - description: "탭, 아코디언, 섹션, 카드섹션, 모달 등 다양한 그룹핑 지원", - category: "layout" as ComponentCategory, - tags: ["tabs", "accordion", "section", "modal", "unified"], - defaultSize: { width: 400, height: 300 }, - }, + // unified-layout: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리 + // unified-group: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리 { id: "unified-media", name: "통합 미디어", diff --git a/frontend/components/unified/config-panels/UnifiedBizConfigPanel.tsx b/frontend/components/unified/config-panels/UnifiedBizConfigPanel.tsx index 89096208..20cdab2c 100644 --- a/frontend/components/unified/config-panels/UnifiedBizConfigPanel.tsx +++ b/frontend/components/unified/config-panels/UnifiedBizConfigPanel.tsx @@ -5,27 +5,151 @@ * 통합 비즈니스 컴포넌트의 세부 설정을 관리합니다. */ -import React from "react"; +import React, { useState, useEffect } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Checkbox } from "@/components/ui/checkbox"; +import { tableTypeApi } from "@/lib/api/screen"; interface UnifiedBizConfigPanelProps { config: Record; onChange: (config: Record) => void; } +interface TableOption { + tableName: string; + displayName: string; +} + +interface ColumnOption { + columnName: string; + displayName: string; +} + export const UnifiedBizConfigPanel: React.FC = ({ config, onChange, }) => { + // 테이블 목록 + const [tables, setTables] = useState([]); + const [loadingTables, setLoadingTables] = useState(false); + + // 컬럼 목록 (소스/대상/관련 테이블용) + const [sourceColumns, setSourceColumns] = useState([]); + const [targetColumns, setTargetColumns] = useState([]); + const [relatedColumns, setRelatedColumns] = useState([]); + const [categoryColumns, setCategoryColumns] = useState([]); + const [loadingColumns, setLoadingColumns] = useState(false); + // 설정 업데이트 핸들러 const updateConfig = (field: string, value: any) => { onChange({ ...config, [field]: value }); }; + // 테이블 목록 로드 + useEffect(() => { + const loadTables = async () => { + setLoadingTables(true); + try { + const data = await tableTypeApi.getTables(); + setTables(data.map(t => ({ + tableName: t.tableName, + displayName: t.displayName || t.tableName + }))); + } catch (error) { + console.error("테이블 목록 로드 실패:", error); + } finally { + setLoadingTables(false); + } + }; + loadTables(); + }, []); + + // 소스 테이블 선택 시 컬럼 목록 로드 + useEffect(() => { + const loadColumns = async () => { + if (!config.sourceTable) { + setSourceColumns([]); + return; + } + try { + const data = await tableTypeApi.getColumns(config.sourceTable); + setSourceColumns(data.map((c: any) => ({ + columnName: c.columnName || c.column_name, + displayName: c.displayName || c.columnName || c.column_name + }))); + } catch (error) { + console.error("소스 컬럼 로드 실패:", error); + } + }; + loadColumns(); + }, [config.sourceTable]); + + // 대상 테이블 선택 시 컬럼 목록 로드 + useEffect(() => { + const loadColumns = async () => { + if (!config.targetTable) { + setTargetColumns([]); + return; + } + try { + const data = await tableTypeApi.getColumns(config.targetTable); + setTargetColumns(data.map((c: any) => ({ + columnName: c.columnName || c.column_name, + displayName: c.displayName || c.columnName || c.column_name + }))); + } catch (error) { + console.error("대상 컬럼 로드 실패:", error); + } + }; + loadColumns(); + }, [config.targetTable]); + + // 관련 테이블 선택 시 컬럼 목록 로드 + useEffect(() => { + const loadColumns = async () => { + if (!config.relatedTable) { + setRelatedColumns([]); + return; + } + try { + const data = await tableTypeApi.getColumns(config.relatedTable); + setRelatedColumns(data.map((c: any) => ({ + columnName: c.columnName || c.column_name, + displayName: c.displayName || c.columnName || c.column_name + }))); + } catch (error) { + console.error("관련 컬럼 로드 실패:", error); + } + }; + loadColumns(); + }, [config.relatedTable]); + + // 카테고리 테이블 선택 시 컬럼 목록 로드 + useEffect(() => { + const loadColumns = async () => { + if (!config.tableName) { + setCategoryColumns([]); + return; + } + setLoadingColumns(true); + try { + const data = await tableTypeApi.getColumns(config.tableName); + setCategoryColumns(data.map((c: any) => ({ + columnName: c.columnName || c.column_name, + displayName: c.displayName || c.columnName || c.column_name + }))); + } catch (error) { + console.error("카테고리 컬럼 로드 실패:", error); + } finally { + setLoadingColumns(false); + } + }; + loadColumns(); + }, [config.tableName]); + return (
{/* 비즈니스 타입 */} @@ -173,23 +297,48 @@ export const UnifiedBizConfigPanel: React.FC = ({
- updateConfig("tableName", e.target.value)} - placeholder="카테고리 테이블명" - className="h-8 text-xs" - /> + onValueChange={(value) => { + updateConfig("tableName", value); + updateConfig("columnName", ""); + }} + disabled={loadingTables} + > + + + + + {tables.map((table) => ( + + {table.displayName} + + ))} + +
-
- - updateConfig("columnName", e.target.value)} - placeholder="컬럼명" - className="h-8 text-xs" - /> -
+ {config.tableName && ( +
+ + +
+ )}
)} @@ -200,22 +349,42 @@ export const UnifiedBizConfigPanel: React.FC = ({
- updateConfig("sourceTable", e.target.value)} - placeholder="소스 테이블명" - className="h-8 text-xs" - /> + onValueChange={(value) => updateConfig("sourceTable", value)} + disabled={loadingTables} + > + + + + + {tables.map((table) => ( + + {table.displayName} + + ))} + +
- updateConfig("targetTable", e.target.value)} - placeholder="대상 테이블명" - className="h-8 text-xs" - /> + onValueChange={(value) => updateConfig("targetTable", value)} + disabled={loadingTables} + > + + + + + {tables.map((table) => ( + + {table.displayName} + + ))} + +
)} @@ -227,23 +396,47 @@ export const UnifiedBizConfigPanel: React.FC = ({
- updateConfig("relatedTable", e.target.value)} - placeholder="관련 테이블명" - className="h-8 text-xs" - /> + onValueChange={(value) => { + updateConfig("relatedTable", value); + updateConfig("linkColumn", ""); + }} + disabled={loadingTables} + > + + + + + {tables.map((table) => ( + + {table.displayName} + + ))} + +
-
- - updateConfig("linkColumn", e.target.value)} - placeholder="연결 컬럼명" - className="h-8 text-xs" - /> -
+ {config.relatedTable && ( +
+ + +
+ )}
@@ -263,5 +456,3 @@ export const UnifiedBizConfigPanel: React.FC = ({ UnifiedBizConfigPanel.displayName = "UnifiedBizConfigPanel"; export default UnifiedBizConfigPanel; - - diff --git a/frontend/components/unified/config-panels/UnifiedHierarchyConfigPanel.tsx b/frontend/components/unified/config-panels/UnifiedHierarchyConfigPanel.tsx index 7ed1a406..1f170879 100644 --- a/frontend/components/unified/config-panels/UnifiedHierarchyConfigPanel.tsx +++ b/frontend/components/unified/config-panels/UnifiedHierarchyConfigPanel.tsx @@ -5,27 +5,89 @@ * 통합 계층 컴포넌트의 세부 설정을 관리합니다. */ -import React from "react"; +import React, { useState, useEffect } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Checkbox } from "@/components/ui/checkbox"; +import { tableTypeApi } from "@/lib/api/screen"; interface UnifiedHierarchyConfigPanelProps { config: Record; onChange: (config: Record) => void; } +interface TableOption { + tableName: string; + displayName: string; +} + +interface ColumnOption { + columnName: string; + displayName: string; +} + export const UnifiedHierarchyConfigPanel: React.FC = ({ config, onChange, }) => { + // 테이블 목록 + const [tables, setTables] = useState([]); + const [loadingTables, setLoadingTables] = useState(false); + + // 컬럼 목록 + const [columns, setColumns] = useState([]); + const [loadingColumns, setLoadingColumns] = useState(false); + // 설정 업데이트 핸들러 const updateConfig = (field: string, value: any) => { onChange({ ...config, [field]: value }); }; + // 테이블 목록 로드 + useEffect(() => { + const loadTables = async () => { + setLoadingTables(true); + try { + const data = await tableTypeApi.getTables(); + setTables(data.map(t => ({ + tableName: t.tableName, + displayName: t.displayName || t.tableName + }))); + } catch (error) { + console.error("테이블 목록 로드 실패:", error); + } finally { + setLoadingTables(false); + } + }; + loadTables(); + }, []); + + // 테이블 선택 시 컬럼 목록 로드 + useEffect(() => { + const loadColumns = async () => { + if (!config.tableName) { + setColumns([]); + return; + } + + setLoadingColumns(true); + try { + const data = await tableTypeApi.getColumns(config.tableName); + setColumns(data.map((c: any) => ({ + columnName: c.columnName || c.column_name, + displayName: c.displayName || c.columnName || c.column_name + }))); + } catch (error) { + console.error("컬럼 목록 로드 실패:", error); + } finally { + setLoadingColumns(false); + } + }; + loadColumns(); + }, [config.tableName]); + return (
{/* 계층 타입 */} @@ -91,44 +153,97 @@ export const UnifiedHierarchyConfigPanel: React.FC + {/* 테이블 선택 */}
- - 테이블 + updateConfig("idColumn", e.target.value)} - placeholder="id" - className="h-8 text-xs" - /> -
-
- - updateConfig("parentIdColumn", e.target.value)} - placeholder="parent_id" - className="h-8 text-xs" - /> -
-
-
- - updateConfig("labelColumn", e.target.value)} - placeholder="name" - className="h-8 text-xs" - /> + onValueChange={(value) => { + updateConfig("tableName", value); + // 테이블 변경 시 컬럼 초기화 + updateConfig("idColumn", ""); + updateConfig("parentIdColumn", ""); + updateConfig("labelColumn", ""); + }} + disabled={loadingTables} + > + + + + + {tables.map((table) => ( + + {table.displayName} + + ))} + +
+ + {/* 컬럼 선택 */} + {config.tableName && ( + <> +
+
+ + +
+
+ + +
+
+
+ + +
+ + )}
)} @@ -227,12 +342,22 @@ export const UnifiedHierarchyConfigPanel: React.FC - updateConfig("quantityColumn", e.target.value)} - placeholder="quantity" - className="h-8 text-xs" - /> + onValueChange={(value) => updateConfig("quantityColumn", value)} + disabled={loadingColumns || !config.tableName} + > + + + + + {columns.map((col) => ( + + {col.displayName} + + ))} + + @@ -247,12 +372,22 @@ export const UnifiedHierarchyConfigPanel: React.FC - updateConfig("parentField", e.target.value)} - placeholder="부모 필드명" - className="h-8 text-xs" - /> + onValueChange={(value) => updateConfig("parentField", value)} + disabled={loadingColumns || !config.tableName} + > + + + + + {columns.map((col) => ( + + {col.displayName} + + ))} + +
@@ -273,5 +408,3 @@ export const UnifiedHierarchyConfigPanel: React.FC; onChange: (config: Record) => void; } +interface TableOption { + tableName: string; + displayName: string; +} + +interface ColumnOption { + columnName: string; + displayName: string; +} + export const UnifiedListConfigPanel: React.FC = ({ config, onChange, }) => { + // 테이블 목록 + const [tables, setTables] = useState([]); + const [loadingTables, setLoadingTables] = useState(false); + + // 컬럼 목록 + const [columns, setColumns] = useState([]); + const [loadingColumns, setLoadingColumns] = useState(false); + // 설정 업데이트 핸들러 const updateConfig = (field: string, value: any) => { onChange({ ...config, [field]: value }); }; + // 테이블 목록 로드 + useEffect(() => { + const loadTables = async () => { + setLoadingTables(true); + try { + const data = await tableTypeApi.getTables(); + setTables(data.map(t => ({ + tableName: t.tableName, + displayName: t.displayName || t.tableName + }))); + } catch (error) { + console.error("테이블 목록 로드 실패:", error); + } finally { + setLoadingTables(false); + } + }; + loadTables(); + }, []); + + // 테이블 선택 시 컬럼 목록 로드 + useEffect(() => { + const loadColumns = async () => { + if (!config.tableName) { + setColumns([]); + return; + } + + setLoadingColumns(true); + try { + const data = await tableTypeApi.getColumns(config.tableName); + setColumns(data.map((c: any) => ({ + columnName: c.columnName || c.column_name, + displayName: c.displayName || c.columnName || c.column_name + }))); + } catch (error) { + console.error("컬럼 목록 로드 실패:", error); + } finally { + setLoadingColumns(false); + } + }; + loadColumns(); + }, [config.tableName]); + // 컬럼 관리 - const columns = config.columns || []; + const configColumns = config.columns || []; const addColumn = () => { - const newColumns = [...columns, { key: "", title: "", width: "" }]; + const newColumns = [...configColumns, { key: "", title: "", width: "" }]; updateConfig("columns", newColumns); }; const updateColumn = (index: number, field: string, value: string) => { - const newColumns = [...columns]; + const newColumns = [...configColumns]; newColumns[index] = { ...newColumns[index], [field]: value }; updateConfig("columns", newColumns); }; const removeColumn = (index: number) => { - const newColumns = columns.filter((_: any, i: number) => i !== index); + const newColumns = configColumns.filter((_: any, i: number) => i !== index); updateConfig("columns", newColumns); }; @@ -91,13 +153,26 @@ export const UnifiedListConfigPanel: React.FC = ({ {/* DB 설정 */} {config.source === "db" && (
- - 테이블 +
)} @@ -132,14 +207,32 @@ export const UnifiedListConfigPanel: React.FC = ({
- {columns.map((column: any, index: number) => ( + {configColumns.map((column: any, index: number) => (
- updateColumn(index, "key", e.target.value)} - placeholder="키" - className="h-7 text-xs flex-1" - /> + onValueChange={(value) => { + const selectedCol = columns.find(c => c.columnName === value); + updateColumn(index, "key", value); + // 제목을 자동으로 설정 + if (selectedCol && !column.title) { + updateColumn(index, "title", selectedCol.displayName); + } + }} + disabled={loadingColumns || !config.tableName} + > + + + + + {columns.map((col) => ( + + {col.displayName} + + ))} + + updateColumn(index, "title", e.target.value)} @@ -163,7 +256,7 @@ export const UnifiedListConfigPanel: React.FC = ({
))} - {columns.length === 0 && ( + {configColumns.length === 0 && (

컬럼을 추가해주세요

@@ -242,5 +335,3 @@ export const UnifiedListConfigPanel: React.FC = ({ UnifiedListConfigPanel.displayName = "UnifiedListConfigPanel"; export default UnifiedListConfigPanel; - -