diff --git a/frontend/components/unified/config-panels/UnifiedRepeaterConfigPanel.tsx b/frontend/components/unified/config-panels/UnifiedRepeaterConfigPanel.tsx
index 6bfdc27c..85015604 100644
--- a/frontend/components/unified/config-panels/UnifiedRepeaterConfigPanel.tsx
+++ b/frontend/components/unified/config-panels/UnifiedRepeaterConfigPanel.tsx
@@ -29,8 +29,24 @@ import {
Eye,
EyeOff,
Wand2,
+ Check,
+ ChevronsUpDown,
} from "lucide-react";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
import { tableTypeApi } from "@/lib/api/screen";
+import { tableManagementApi } from "@/lib/api/tableManagement";
import { getAvailableNumberingRules, getAvailableNumberingRulesForScreen } from "@/lib/api/numberingRule";
import { NumberingRuleConfig } from "@/types/numbering-rule";
import { cn } from "@/lib/utils";
@@ -42,6 +58,14 @@ import {
MODAL_SIZE_OPTIONS,
} from "@/types/unified-repeater";
+// 테이블 엔티티 관계 정보
+interface TableRelation {
+ tableName: string;
+ tableLabel: string;
+ foreignKeyColumn: string; // 저장 테이블의 FK 컬럼
+ referenceColumn: string; // 마스터 테이블의 PK 컬럼
+}
+
interface UnifiedRepeaterConfigPanelProps {
config: UnifiedRepeaterConfig;
onChange: (config: UnifiedRepeaterConfig) => void;
@@ -117,6 +141,13 @@ export const UnifiedRepeaterConfigPanel: React.FC
>([]);
+ const [loadingTables, setLoadingTables] = useState(false);
+ const [relatedTables, setRelatedTables] = useState([]); // 현재 테이블과 연관된 테이블 목록
+ const [loadingRelations, setLoadingRelations] = useState(false);
+ const [tableComboboxOpen, setTableComboboxOpen] = useState(false); // 테이블 Combobox 열림 상태
+
// 🆕 확장된 컬럼 (상세 설정 표시용)
const [expandedColumn, setExpandedColumn] = useState(null);
@@ -199,6 +230,60 @@ export const UnifiedRepeaterConfigPanel: React.FC {
+ const loadTables = 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.displayName || t.table_label || t.tableName || t.table_name,
+ })));
+ }
+ } catch (error) {
+ console.error("테이블 목록 로드 실패:", error);
+ } finally {
+ setLoadingTables(false);
+ }
+ };
+ loadTables();
+ }, []);
+
+ // 현재 테이블과 연관된 테이블 목록 로드 (엔티티 관계 기반)
+ useEffect(() => {
+ const loadRelatedTables = async () => {
+ if (!currentTableName) {
+ setRelatedTables([]);
+ return;
+ }
+
+ setLoadingRelations(true);
+ try {
+ // column_labels에서 현재 테이블을 reference_table로 참조하는 테이블 찾기
+ const { apiClient } = await import("@/lib/api/client");
+ const response = await apiClient.get(`/table-management/columns/${currentTableName}/referenced-by`);
+
+ if (response.data.success && response.data.data) {
+ const relations: TableRelation[] = response.data.data.map((rel: any) => ({
+ tableName: rel.tableName || rel.table_name,
+ tableLabel: rel.tableLabel || rel.table_label || rel.tableName || rel.table_name,
+ foreignKeyColumn: rel.columnName || rel.column_name, // FK 컬럼
+ referenceColumn: rel.referenceColumn || rel.reference_column || "id", // PK 컬럼
+ }));
+ setRelatedTables(relations);
+ }
+ } catch (error) {
+ console.error("연관 테이블 로드 실패:", error);
+ setRelatedTables([]);
+ } finally {
+ setLoadingRelations(false);
+ }
+ };
+ loadRelatedTables();
+ }, [currentTableName]);
+
// 설정 업데이트 헬퍼
const updateConfig = useCallback(
(updates: Partial) => {
@@ -234,10 +319,50 @@ export const UnifiedRepeaterConfigPanel: React.FC {
+ // 빈 값 선택 시 (현재 테이블로 복원)
+ if (!tableName || tableName === currentTableName) {
+ updateConfig({
+ useCustomTable: false,
+ mainTableName: undefined,
+ foreignKeyColumn: undefined,
+ foreignKeySourceColumn: undefined,
+ });
+ return;
+ }
+
+ // 연관 테이블에서 FK 관계 찾기
+ const relation = relatedTables.find(r => r.tableName === tableName);
+
+ if (relation) {
+ // 엔티티 관계가 있으면 자동으로 FK/PK 설정
+ updateConfig({
+ useCustomTable: true,
+ mainTableName: tableName,
+ foreignKeyColumn: relation.foreignKeyColumn,
+ foreignKeySourceColumn: relation.referenceColumn,
+ });
+ } else {
+ // 엔티티 관계가 없으면 직접 입력 필요
+ updateConfig({
+ useCustomTable: true,
+ mainTableName: tableName,
+ foreignKeyColumn: undefined,
+ foreignKeySourceColumn: "id",
+ });
+ }
+ }, [currentTableName, relatedTables, updateConfig]);
+
+ // 저장 테이블 컬럼 로드 (저장 테이블이 설정되면 해당 테이블, 아니면 현재 화면 테이블)
+ // 실제 저장할 테이블의 컬럼을 보여줘야 함
+ const targetTableForColumns = config.useCustomTable && config.mainTableName
+ ? config.mainTableName
+ : currentTableName;
+
useEffect(() => {
const loadCurrentTableColumns = async () => {
- if (!currentTableName) {
+ if (!targetTableForColumns) {
setCurrentTableColumns([]);
setEntityColumns([]);
return;
@@ -245,7 +370,7 @@ export const UnifiedRepeaterConfigPanel: React.FC {
@@ -529,97 +654,185 @@ export const UnifiedRepeaterConfigPanel: React.FC
- {/* 저장 대상 테이블 설정 */}
+ {/* 저장 대상 테이블 */}
-
-
- 화면 메인 테이블과 다른 테이블에 저장할 경우 설정
-
+
-
-
{
- if (!checked) {
- updateConfig({
- useCustomTable: false,
- mainTableName: undefined,
- foreignKeyColumn: undefined,
- foreignKeySourceColumn: undefined,
- });
- } else {
- updateConfig({ useCustomTable: true });
- }
- }}
- />
-
+ {/* 현재 선택된 테이블 표시 (기존 테이블 UI와 동일한 스타일) */}
+
+
+
+
+
+ {config.useCustomTable && config.mainTableName
+ ? (allTables.find(t => t.tableName === config.mainTableName)?.displayName || config.mainTableName)
+ : (currentTableName || "미설정")
+ }
+
+ {config.useCustomTable && config.mainTableName && config.foreignKeyColumn && (
+
+ FK: {config.foreignKeyColumn} → {currentTableName}.{config.foreignKeySourceColumn || "id"}
+
+ )}
+ {!config.useCustomTable && currentTableName && (
+
화면 메인 테이블
+ )}
+
+
- {config.useCustomTable && (
-
- {/* 저장 테이블 선택 */}
-
-
-
-
updateConfig({ mainTableName: e.target.value })}
- placeholder="테이블명 직접 입력 (예: receiving_detail)"
- className="h-7 text-xs"
- />
-
-
- {/* FK 컬럼 설정 */}
+ {/* 테이블 변경 Combobox */}
+
+
+
+
+
+
+
+
+
+ 테이블을 찾을 수 없습니다.
+
+
+ {/* 현재 테이블 (기본) */}
+ {currentTableName && (
+
+ {
+ handleSaveTableSelect(currentTableName);
+ setTableComboboxOpen(false);
+ }}
+ className="text-xs"
+ >
+
+
+ {currentTableName}
+ (기본)
+
+
+ )}
+
+ {/* 연관 테이블 (엔티티 관계) */}
+ {relatedTables.length > 0 && (
+
+ {relatedTables.map((rel) => (
+ {
+ handleSaveTableSelect(rel.tableName);
+ setTableComboboxOpen(false);
+ }}
+ className="text-xs"
+ >
+
+
+ {rel.tableLabel}
+
+ ({rel.foreignKeyColumn})
+
+
+ ))}
+
+ )}
+
+ {/* 전체 테이블 목록 */}
+
+ {allTables
+ .filter(t => t.tableName !== currentTableName && !relatedTables.some(r => r.tableName === t.tableName))
+ .map((table) => (
+ {
+ handleSaveTableSelect(table.tableName);
+ setTableComboboxOpen(false);
+ }}
+ className="text-xs"
+ >
+
+
+ {table.displayName}
+
+ ))
+ }
+
+
+
+
+
+
+ {/* FK 직접 입력 (연관 테이블이 아닌 경우만) */}
+ {config.useCustomTable && config.mainTableName &&
+ !relatedTables.some(r => r.tableName === config.mainTableName) && (
+
+
+ 엔티티 관계가 설정되지 않은 테이블입니다. FK 컬럼을 직접 입력하세요.
+
-
- {config.mainTableName && config.foreignKeyColumn && (
-
- 저장 흐름: {config.mainTableName}.{config.foreignKeyColumn} →
- {currentTableName}.{config.foreignKeySourceColumn || "id"} (자동 연결)
-
- )}
-
- )}
-
- {!config.useCustomTable && (
-
- 현재: 화면 메인 테이블 ({currentTableName || "미설정"})에 저장
)}
@@ -809,10 +1022,10 @@ export const UnifiedRepeaterConfigPanel: React.FC
)}
- {/* 현재 테이블 컬럼 (입력용) */}
+ {/* 저장 테이블 컬럼 (입력용) */}
- 현재 테이블 ({currentTableName || "미선택"}) - 입력용
+ 저장 테이블 ({targetTableForColumns || "미선택"}) - 입력용
{loadingColumns ? (
로딩 중...