From 718182283289ddc8c1f0efefa084618ff1b1e9c7 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 15 Jan 2026 17:07:18 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EB=94=94=EC=8A=A4?= =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=20=EC=84=A4=EC=A0=95=20=ED=8C=A8?= =?UTF-8?q?=EB=84=90=EC=97=90=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EA=B3=A0,=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=82=AC=EC=9A=A9=20=EC=97=AC=EB=B6=80?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EC=BB=AC=EB=9F=BC=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=9D=84=20=EB=8F=99=EC=A0=81=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=ED=96=88=EC=8A=B5=EB=8B=88=EB=8B=A4.=20=EB=98=90?= =?UTF-8?q?=ED=95=9C,=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20=EC=A7=80=EC=9B=90?= =?UTF-8?q?=20=EB=B0=8F=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=ED=98=84=ED=99=A9=20=EB=AC=B8=EC=84=9C=EC=9D=98=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=A0=81=EC=9A=A9=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=A5=BC=20=EB=AA=85=ED=99=95=ED=9E=88=20=ED=95=98=EC=98=80?= =?UTF-8?q?=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/컴포넌트_기능_현황.md | 124 +++++----- .../card-display/CardDisplayConfigPanel.tsx | 215 +++++++++++++++++- .../registry/components/card-display/types.ts | 6 + 3 files changed, 273 insertions(+), 72 deletions(-) diff --git a/docs/컴포넌트_기능_현황.md b/docs/컴포넌트_기능_현황.md index bf59454a..7778b3bc 100644 --- a/docs/컴포넌트_기능_현황.md +++ b/docs/컴포넌트_기능_현황.md @@ -7,10 +7,10 @@ ## 요약 -| 기능 | 적용 완료 | 미적용 | 해당없음 | -|------|----------|--------|---------| -| **다국어 지원** | 3개 | 10개 | 3개 | -| **컴포넌트별 테이블 설정** | 4개 | 6개 | 6개 | +| 기능 | 적용 완료 | 미적용 | 해당없음 | +| -------------------------- | --------- | ------ | -------- | +| **다국어 지원** | 3개 | 10개 | 3개 | +| **컴포넌트별 테이블 설정** | 5개 | 5개 | 6개 | --- @@ -18,52 +18,52 @@ ### 데이터 표시 (Display) - 4개 -| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | -|---------|:----------:|:----------:|------| -| **테이블 리스트** | ✅ 적용 | ✅ 적용 | `customTableName`, `useCustomTable` 지원 | -| **카드 디스플레이** | ❌ 미적용 | ⚠️ 부분 | `screenTableName`만 사용, 컴포넌트별 테이블 선택 UI 없음 | -| **텍스트 표시** | ❌ 미적용 | ➖ 해당없음 | 정적 텍스트 표시용 | -| **피벗 그리드** | ❌ 미적용 | ⚠️ 부분 | `tableName` 설정 가능하나 Combobox UI 없음 | +| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | +| ------------------- | :---------: | :---------: | ------------------------------------------------------------------- | +| **테이블 리스트** | ✅ 적용 | ✅ 적용 | `customTableName`, `useCustomTable` 지원 | +| **카드 디스플레이** | ❌ 미적용 | ✅ 적용 | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable` 지원 | +| **텍스트 표시** | ❌ 미적용 | ➖ 해당없음 | 정적 텍스트 표시용 | +| **피벗 그리드** | ❌ 미적용 | ⚠️ 부분 | `tableName` 설정 가능하나 Combobox UI 없음 | --- ### 데이터 입력 (Data) - 2개 -| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | -|---------|:----------:|:----------:|------| -| **통합 반복 데이터** | ❌ 미적용 | ✅ 적용 | `mainTableName`, `foreignKeyColumn` 지원, Combobox UI 적용 | -| **반복 화면 모달** | ❌ 미적용 | ⚠️ 부분 | `tableName` 설정 가능하나 Combobox UI 없음 | +| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | +| -------------------- | :---------: | :---------: | ---------------------------------------------------------- | +| **통합 반복 데이터** | ❌ 미적용 | ✅ 적용 | `mainTableName`, `foreignKeyColumn` 지원, Combobox UI 적용 | +| **반복 화면 모달** | ❌ 미적용 | ⚠️ 부분 | `tableName` 설정 가능하나 Combobox UI 없음 | --- ### 액션 (Action) - 1개 -| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | -|---------|:----------:|:----------:|------| -| **기본 버튼** | ✅ 적용 | ➖ 해당없음 | `langKeyId`, `langKey` 지원 | +| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | +| ------------- | :---------: | :---------: | --------------------------- | +| **기본 버튼** | ✅ 적용 | ➖ 해당없음 | `langKeyId`, `langKey` 지원 | --- ### 레이아웃 (Layout) - 5개 -| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | -|---------|:----------:|:----------:|------| -| **분할 패널** | ✅ 적용 | ⚠️ 부분 | 다국어 지원, 테이블 설정은 하위 패널에서 처리 | -| **탭 컴포넌트** | ❌ 미적용 | ➖ 해당없음 | 화면 전환용 컨테이너 | -| **Section Card** | ❌ 미적용 | ➖ 해당없음 | 그룹화 컨테이너 | -| **Section Paper** | ❌ 미적용 | ➖ 해당없음 | 그룹화 컨테이너 | -| **구분선** | ❌ 미적용 | ➖ 해당없음 | 시각적 구분용 | +| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | +| ----------------- | :---------: | :---------: | --------------------------------------------- | +| **분할 패널** | ✅ 적용 | ⚠️ 부분 | 다국어 지원, 테이블 설정은 하위 패널에서 처리 | +| **탭 컴포넌트** | ❌ 미적용 | ➖ 해당없음 | 화면 전환용 컨테이너 | +| **Section Card** | ❌ 미적용 | ➖ 해당없음 | 그룹화 컨테이너 | +| **Section Paper** | ❌ 미적용 | ➖ 해당없음 | 그룹화 컨테이너 | +| **구분선** | ❌ 미적용 | ➖ 해당없음 | 시각적 구분용 | --- ### 유틸리티 (Utility) - 4개 -| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | -|---------|:----------:|:----------:|------| -| **코드 채번 규칙** | ❌ 미적용 | ➖ 해당없음 | 채번 규칙 관리 전용 | -| **렉 구조 설정** | ❌ 미적용 | ➖ 해당없음 | 창고 렉 설정 전용 | -| **출발지/도착지 선택** | ❌ 미적용 | ⚠️ 부분 | `customTableName` 지원하나 Combobox UI 없음 | -| **검색 필터** | ❌ 미적용 | ⚠️ 부분 | `screenTableName` 자동 감지 | +| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | +| ---------------------- | :---------: | :---------: | ------------------------------------------- | +| **코드 채번 규칙** | ❌ 미적용 | ➖ 해당없음 | 채번 규칙 관리 전용 | +| **렉 구조 설정** | ❌ 미적용 | ➖ 해당없음 | 창고 렉 설정 전용 | +| **출발지/도착지 선택** | ❌ 미적용 | ⚠️ 부분 | `customTableName` 지원하나 Combobox UI 없음 | +| **검색 필터** | ❌ 미적용 | ⚠️ 부분 | `screenTableName` 자동 감지 | --- @@ -74,11 +74,13 @@ 다국어 지원이란 컴포넌트의 라벨, 플레이스홀더 등 텍스트 속성에 다국어 키를 연결하여 언어별로 다른 텍스트를 표시하는 기능입니다. **적용 완료 (3개)** + - `table-list`: 컬럼 라벨 다국어 지원 - `button-primary`: 버튼 텍스트 다국어 지원 - `split-panel-layout`: 패널 제목 다국어 지원 **미적용 (10개)** + - `card-display`, `text-display`, `pivot-grid` - `unified-repeater`, `repeat-screen-modal` - `tabs`, `section-card`, `section-paper`, `divider-line` @@ -90,26 +92,27 @@ 컴포넌트별 테이블 설정이란 화면의 메인 테이블과 별개로 컴포넌트가 자체적으로 사용할 테이블을 지정할 수 있는 기능입니다. -**완전 적용 (4개)** +**완전 적용 (5개)** -| 컴포넌트 | 적용 방식 | -|---------|----------| -| `table-list` | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable`, `isReadOnly` 지원 | +| 컴포넌트 | 적용 방식 | +| ------------------ | --------------------------------------------------------------------------------- | +| `table-list` | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable`, `isReadOnly` 지원 | | `unified-repeater` | Combobox UI로 테이블 선택, `mainTableName`, `foreignKeyColumn` 지원, FK 자동 연결 | -| `unified-list` | `TableListConfigPanel` 래핑하여 동일 기능 제공 | +| `unified-list` | `TableListConfigPanel` 래핑하여 동일 기능 제공 | +| `card-display` | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable` 지원 | -**부분 적용 (6개)** +**부분 적용 (5개)** -| 컴포넌트 | 현재 상태 | 필요 작업 | -|---------|----------|----------| -| `card-display` | `screenTableName` 사용 | Combobox UI 추가 필요 | -| `pivot-grid` | `tableName` 설정 가능 | Combobox UI 추가 필요 | -| `repeat-screen-modal` | `tableName` 설정 가능 | Combobox UI 추가 필요 | -| `split-panel-layout` | 하위 패널에서 처리 | 하위 컴포넌트에 위임 | -| `location-swap-selector` | `customTableName` 지원 | Combobox UI 추가 필요 | -| `table-search-widget` | `screenTableName` 자동 감지 | 현재 방식 유지 가능 | +| 컴포넌트 | 현재 상태 | 필요 작업 | +| ------------------------ | --------------------------- | --------------------- | +| `pivot-grid` | `tableName` 설정 가능 | Combobox UI 추가 필요 | +| `repeat-screen-modal` | `tableName` 설정 가능 | Combobox UI 추가 필요 | +| `split-panel-layout` | 하위 패널에서 처리 | 하위 컴포넌트에 위임 | +| `location-swap-selector` | `customTableName` 지원 | Combobox UI 추가 필요 | +| `table-search-widget` | `screenTableName` 자동 감지 | 현재 방식 유지 가능 | **해당없음 (6개)** + - `text-display`, `divider-line`: 정적 컴포넌트 - `tabs`, `section-card`, `section-paper`: 레이아웃 컨테이너 - `numbering-rule`, `rack-structure`: 특수 목적 컴포넌트 @@ -120,29 +123,28 @@ ### 1순위: 데이터 컴포넌트 테이블 설정 UI 통일 -| 컴포넌트 | 작업 내용 | -|---------|----------| -| `card-display` | Combobox UI 추가, `customTableName` 지원 | -| `pivot-grid` | Combobox UI 추가 | -| `repeat-screen-modal` | Combobox UI 추가 | +| 컴포넌트 | 작업 내용 | 상태 | +| --------------------- | ---------------------------------------- | ------- | +| `card-display` | Combobox UI 추가, `customTableName` 지원 | ✅ 완료 | +| `pivot-grid` | Combobox UI 추가 | 대기 | +| `repeat-screen-modal` | Combobox UI 추가 | 대기 | ### 2순위: 다국어 지원 확대 -| 컴포넌트 | 작업 내용 | -|---------|----------| +| 컴포넌트 | 작업 내용 | +| ------------------ | -------------------------- | | `unified-repeater` | 컬럼 라벨 `langKeyId` 지원 | -| `card-display` | 필드 라벨 `langKeyId` 지원 | -| `tabs` | 탭 이름 `langKeyId` 지원 | -| `section-card` | 제목 `langKeyId` 지원 | +| `card-display` | 필드 라벨 `langKeyId` 지원 | +| `tabs` | 탭 이름 `langKeyId` 지원 | +| `section-card` | 제목 `langKeyId` 지원 | --- ## 범례 -| 기호 | 의미 | -|-----|------| -| ✅ | 완전 적용 | -| ⚠️ | 부분 적용 (기능은 있으나 UI 미비) | -| ❌ | 미적용 | -| ➖ | 해당없음 (기능 불필요) | - +| 기호 | 의미 | +| ---- | --------------------------------- | +| ✅ | 완전 적용 | +| ⚠️ | 부분 적용 (기능은 있으나 UI 미비) | +| ❌ | 미적용 | +| ➖ | 해당없음 (기능 불필요) | diff --git a/frontend/lib/registry/components/card-display/CardDisplayConfigPanel.tsx b/frontend/lib/registry/components/card-display/CardDisplayConfigPanel.tsx index 73bb79a9..b13a5946 100644 --- a/frontend/lib/registry/components/card-display/CardDisplayConfigPanel.tsx +++ b/frontend/lib/registry/components/card-display/CardDisplayConfigPanel.tsx @@ -1,7 +1,8 @@ "use client"; -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useMemo } from "react"; import { entityJoinApi } from "@/lib/api/entityJoin"; +import { tableManagementApi } from "@/lib/api/tableManagement"; import { Select, SelectContent, @@ -11,11 +12,14 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; -import { Trash2 } from "lucide-react"; +import { Trash2, Database, ChevronsUpDown, Check } from "lucide-react"; +import { cn } from "@/lib/utils"; interface CardDisplayConfigPanelProps { config: any; @@ -57,6 +61,13 @@ export const CardDisplayConfigPanel: React.FC = ({ screenTableName, tableColumns = [], }) => { + // 테이블 선택 상태 + const [tableComboboxOpen, setTableComboboxOpen] = useState(false); + const [allTables, setAllTables] = useState>([]); + const [loadingTables, setLoadingTables] = useState(false); + const [availableColumns, setAvailableColumns] = useState([]); + const [loadingColumns, setLoadingColumns] = useState(false); + // 엔티티 조인 컬럼 상태 const [entityJoinColumns, setEntityJoinColumns] = useState<{ availableColumns: EntityJoinColumn[]; @@ -64,18 +75,80 @@ export const CardDisplayConfigPanel: React.FC = ({ }>({ availableColumns: [], joinTables: [] }); const [loadingEntityJoins, setLoadingEntityJoins] = useState(false); + // 현재 사용할 테이블명 + const targetTableName = useMemo(() => { + if (config.useCustomTable && config.customTableName) { + return config.customTableName; + } + return config.tableName || screenTableName; + }, [config.useCustomTable, config.customTableName, config.tableName, screenTableName]); + + // 전체 테이블 목록 로드 + useEffect(() => { + const loadAllTables = async () => { + setLoadingTables(true); + try { + const response = await tableManagementApi.getTableList(); + if (response.success && response.data) { + setAllTables(response.data.map((t: any) => ({ + tableName: t.tableName || t.table_name, + displayName: t.tableLabel || t.displayName || t.tableName || t.table_name, + }))); + } + } catch (error) { + console.error("테이블 목록 로드 실패:", error); + } finally { + setLoadingTables(false); + } + }; + loadAllTables(); + }, []); + + // 선택된 테이블의 컬럼 로드 + useEffect(() => { + const loadColumns = async () => { + if (!targetTableName) { + setAvailableColumns([]); + return; + } + + // 커스텀 테이블이 아니면 props로 받은 tableColumns 사용 + if (!config.useCustomTable && tableColumns && tableColumns.length > 0) { + setAvailableColumns(tableColumns); + return; + } + + setLoadingColumns(true); + try { + const result = await tableManagementApi.getColumnList(targetTableName); + if (result.success && result.data?.columns) { + setAvailableColumns(result.data.columns.map((col: any) => ({ + columnName: col.columnName, + columnLabel: col.displayName || col.columnLabel || col.columnName, + dataType: col.dataType, + }))); + } + } catch (error) { + console.error("컬럼 목록 로드 실패:", error); + setAvailableColumns([]); + } finally { + setLoadingColumns(false); + } + }; + loadColumns(); + }, [targetTableName, config.useCustomTable, tableColumns]); + // 엔티티 조인 컬럼 정보 가져오기 useEffect(() => { const fetchEntityJoinColumns = async () => { - const tableName = config.tableName || screenTableName; - if (!tableName) { + if (!targetTableName) { setEntityJoinColumns({ availableColumns: [], joinTables: [] }); return; } setLoadingEntityJoins(true); try { - const result = await entityJoinApi.getEntityJoinColumns(tableName); + const result = await entityJoinApi.getEntityJoinColumns(targetTableName); setEntityJoinColumns({ availableColumns: result.availableColumns || [], joinTables: result.joinTables || [], @@ -89,7 +162,38 @@ export const CardDisplayConfigPanel: React.FC = ({ }; fetchEntityJoinColumns(); - }, [config.tableName, screenTableName]); + }, [targetTableName]); + + // 테이블 선택 핸들러 + const handleTableSelect = (tableName: string, isScreenTable: boolean) => { + if (isScreenTable) { + // 화면 기본 테이블 선택 + onChange({ + ...config, + useCustomTable: false, + customTableName: undefined, + tableName: tableName, + columnMapping: { displayColumns: [] }, // 컬럼 매핑 초기화 + }); + } else { + // 다른 테이블 선택 + onChange({ + ...config, + useCustomTable: true, + customTableName: tableName, + tableName: tableName, + columnMapping: { displayColumns: [] }, // 컬럼 매핑 초기화 + }); + } + setTableComboboxOpen(false); + }; + + // 현재 선택된 테이블 표시명 가져오기 + const getSelectedTableDisplay = () => { + if (!targetTableName) return "테이블을 선택하세요"; + const found = allTables.find(t => t.tableName === targetTableName); + return found?.displayName || targetTableName; + }; const handleChange = (key: string, value: any) => { onChange({ ...config, [key]: value }); @@ -219,6 +323,9 @@ export const CardDisplayConfigPanel: React.FC = ({ joinColumnsByTable[col.tableName].push(col); }); + // 현재 사용할 컬럼 목록 (커스텀 테이블이면 로드한 컬럼, 아니면 props) + const currentTableColumns = config.useCustomTable ? availableColumns : (tableColumns.length > 0 ? tableColumns : availableColumns); + // 컬럼 선택 셀렉트 박스 렌더링 (Shadcn UI) const renderColumnSelect = ( value: string, @@ -240,12 +347,12 @@ export const CardDisplayConfigPanel: React.FC = ({ {/* 기본 테이블 컬럼 */} - {tableColumns.length > 0 && ( + {currentTableColumns.length > 0 && ( 기본 컬럼 - {tableColumns.map((column) => ( + {currentTableColumns.map((column: any) => ( = ({
카드 디스플레이 설정
+ {/* 테이블 선택 */} +
+ + + + + + + + + + + 테이블을 찾을 수 없습니다. + + + {/* 화면 기본 테이블 */} + {screenTableName && ( + + handleTableSelect(screenTableName, true)} + className="text-xs" + > + + + {allTables.find(t => t.tableName === screenTableName)?.displayName || screenTableName} + + + )} + + {/* 전체 테이블 */} + + {allTables + .filter(t => t.tableName !== screenTableName) + .map((table) => ( + handleTableSelect(table.tableName, false)} + className="text-xs" + > + + + {table.displayName} + + ))} + + + + + + {config.useCustomTable && ( +

+ 화면 기본 테이블이 아닌 다른 테이블의 데이터를 표시합니다. +

+ )} +
+ {/* 테이블이 선택된 경우 컬럼 매핑 설정 */} - {tableColumns && tableColumns.length > 0 && ( + {(currentTableColumns.length > 0 || loadingColumns) && (
컬럼 매핑
- {loadingEntityJoins && ( -
조인 컬럼 로딩 중...
+ {(loadingEntityJoins || loadingColumns) && ( +
+ {loadingColumns ? "컬럼 로딩 중..." : "조인 컬럼 로딩 중..."} +
)}
diff --git a/frontend/lib/registry/components/card-display/types.ts b/frontend/lib/registry/components/card-display/types.ts index d4174453..368e43cc 100644 --- a/frontend/lib/registry/components/card-display/types.ts +++ b/frontend/lib/registry/components/card-display/types.ts @@ -45,6 +45,12 @@ export interface CardDisplayConfig extends ComponentConfig { // 컬럼 매핑 설정 columnMapping?: ColumnMappingConfig; + // 컴포넌트별 테이블 설정 + useCustomTable?: boolean; + customTableName?: string; + tableName?: string; + isReadOnly?: boolean; + // 테이블 데이터 설정 dataSource?: "static" | "table" | "api"; tableId?: string;