From 5093863e08142376f6ebec5819ac21acfd1ef00e Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Thu, 12 Mar 2026 06:00:48 +0900 Subject: [PATCH] [agent-pipeline] pipe-20260311204151-c4wy round-2 --- .../V2SelectedItemsDetailInputConfigPanel.tsx | 1585 +++++++++++++++++ .../selected-items-detail-input/index.ts | 6 +- 2 files changed, 1587 insertions(+), 4 deletions(-) create mode 100644 frontend/components/v2/config-panels/V2SelectedItemsDetailInputConfigPanel.tsx diff --git a/frontend/components/v2/config-panels/V2SelectedItemsDetailInputConfigPanel.tsx b/frontend/components/v2/config-panels/V2SelectedItemsDetailInputConfigPanel.tsx new file mode 100644 index 00000000..b8ecd32a --- /dev/null +++ b/frontend/components/v2/config-panels/V2SelectedItemsDetailInputConfigPanel.tsx @@ -0,0 +1,1585 @@ +"use client"; + +/** + * V2 SelectedItemsDetailInput 설정 패널 + * 토스식 단계별 UX: 테이블 설정 -> 컬럼/필드 관리 -> 고급 설정(접힘) + */ + +import React, { useState, useEffect, useMemo, useCallback, useRef } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { Button } from "@/components/ui/button"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { Separator } from "@/components/ui/separator"; +import { + Database, + Table2, + Columns3, + Plus, + Trash2, + Settings, + ChevronDown, + Check, + ChevronsUpDown, + Link2, + Layers, + LayoutGrid, + LayoutList, + FolderPlus, + Calculator, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import type { ComponentData } from "@/types/screen"; +import type { + SelectedItemsDetailInputConfig, + AdditionalFieldDefinition, + FieldGroup, + AutoDetectedFk, + ParentDataMapping, +} from "@/lib/registry/components/selected-items-detail-input/types"; + +// ─── 테이블 컬럼 타입 ─── +interface ColumnInfo { + columnName: string; + columnLabel?: string; + dataType?: string; + inputType?: string; + codeCategory?: string; + referenceTable?: string; + referenceColumn?: string; +} + +// ─── 레이아웃 카드 정의 ─── +const LAYOUT_CARDS = [ + { + value: "grid", + icon: LayoutGrid, + title: "테이블 형식", + description: "행 단위 데이터 표시", + }, + { + value: "card", + icon: LayoutList, + title: "카드 형식", + description: "각 항목을 카드로 표시", + }, +] as const; + +// ─── 입력 모드 카드 정의 ─── +const INPUT_MODE_CARDS = [ + { + value: "inline", + icon: Columns3, + title: "항상 표시", + description: "입력창을 항상 표시", + }, + { + value: "modal", + icon: Layers, + title: "추가 버튼", + description: "클릭 시 입력, 완료 후 카드", + }, +] as const; + +// ─── Props ─── +export interface V2SelectedItemsDetailInputConfigPanelProps { + config: Record; + onChange: (config: Record) => void; + allComponents?: ComponentData[]; + currentComponent?: ComponentData; + screenTableName?: string; + allTables?: Array<{ tableName: string; displayName?: string }>; + onUpdateProperty?: (id: string, key: string, value: any) => void; +} + +/** + * Combobox 서브컴포넌트 - 테이블 선택용 + */ +const TableCombobox: React.FC<{ + value: string; + tables: Array<{ tableName: string; displayName?: string }>; + placeholder: string; + onSelect: (tableName: string) => void; +}> = ({ value, tables, placeholder, onSelect }) => { + const [open, setOpen] = useState(false); + const [search, setSearch] = useState(""); + + const filtered = useMemo(() => { + if (!search) return tables; + const s = search.toLowerCase(); + return tables.filter( + (t) => + t.tableName.toLowerCase().includes(s) || + t.displayName?.toLowerCase().includes(s), + ); + }, [tables, search]); + + const selectedLabel = useMemo(() => { + if (!value) return placeholder; + const t = tables.find((t) => t.tableName === value); + return t ? t.displayName || t.tableName : value; + }, [value, tables, placeholder]); + + return ( + + + + + + + + + + 테이블을 찾을 수 없습니다. + + + {filtered.map((t) => ( + { + onSelect(v); + setOpen(false); + setSearch(""); + }} + className="text-xs sm:text-sm" + > + +
+ {t.displayName || t.tableName} + {t.displayName && ( + + {t.tableName} + + )} +
+
+ ))} +
+
+
+
+
+ ); +}; + +/** + * Combobox 서브컴포넌트 - 컬럼 선택용 + */ +const ColumnCombobox: React.FC<{ + value: string; + columns: ColumnInfo[]; + placeholder: string; + disabled?: boolean; + onSelect: (columnName: string, column: ColumnInfo) => void; +}> = ({ value, columns, placeholder, disabled, onSelect }) => { + const [open, setOpen] = useState(false); + + const selectedLabel = useMemo(() => { + if (!value) return placeholder; + const c = columns.find((c) => c.columnName === value); + return c ? c.columnLabel || c.columnName : value; + }, [value, columns, placeholder]); + + return ( + + + + + + + + + + 컬럼을 찾을 수 없습니다. + + + {columns.map((c) => ( + { + onSelect(c.columnName, c); + setOpen(false); + }} + className="text-xs" + > + +
+ + {c.columnLabel || c.columnName} + + + {c.columnName} + {c.dataType ? ` (${c.dataType})` : ""} + +
+
+ ))} +
+
+
+
+
+ ); +}; + +/** + * V2 SelectedItemsDetailInput 설정 패널 (토스식 UX) + */ +export const V2SelectedItemsDetailInputConfigPanel: React.FC< + V2SelectedItemsDetailInputConfigPanelProps +> = ({ + config: rawConfig, + onChange, + allComponents = [], + currentComponent, + screenTableName, + allTables = [], + onUpdateProperty, +}) => { + const config = rawConfig as SelectedItemsDetailInputConfig; + + // ─── 핸들러 ─── + const handleChange = useCallback( + (key: string, value: any) => { + onChange({ ...config, [key]: value }); + }, + [config, onChange], + ); + + // ─── 테이블 컬럼 로드 상태 ─── + const [sourceColumns, setSourceColumns] = useState([]); + const [targetColumns, setTargetColumns] = useState([]); + const [autoDetectedFks, setAutoDetectedFks] = useState([]); + const fkAutoAppliedRef = useRef(false); + + // ─── 테이블 컬럼 로드 ─── + const loadColumns = useCallback(async (tableName: string): Promise => { + if (!tableName) return []; + try { + const { tableManagementApi } = await import("@/lib/api/tableManagement"); + const response = await tableManagementApi.getColumnList(tableName); + if (response.success && response.data) { + return (response.data.columns || []).map((col: any) => ({ + columnName: col.columnName, + columnLabel: col.displayName || col.columnLabel || col.columnName, + dataType: col.dataType, + inputType: col.inputType, + codeCategory: col.codeCategory, + referenceTable: col.referenceTable, + referenceColumn: col.referenceColumn, + })); + } + } catch (error) { + console.error("컬럼 로드 오류:", tableName, error); + } + return []; + }, []); + + // 원본 테이블 컬럼 로드 + useEffect(() => { + if (!config.sourceTable) { + setSourceColumns([]); + return; + } + loadColumns(config.sourceTable).then(setSourceColumns); + }, [config.sourceTable, loadColumns]); + + // 대상 테이블 컬럼 로드 + useEffect(() => { + if (!config.targetTable) { + setTargetColumns([]); + setAutoDetectedFks([]); + return; + } + loadColumns(config.targetTable).then(setTargetColumns); + }, [config.targetTable, loadColumns]); + + // FK 자동 감지 + const detectedFks = useMemo(() => { + if (!config.targetTable || targetColumns.length === 0) return []; + const entityFkColumns = targetColumns.filter( + (col) => col.inputType === "entity" && col.referenceTable, + ); + if (entityFkColumns.length === 0) return []; + + return entityFkColumns.map((col) => { + let mappingType: "source" | "parent" | "unknown" = "unknown"; + if (config.sourceTable && col.referenceTable === config.sourceTable) { + mappingType = "source"; + } else if (config.sourceTable && col.referenceTable !== config.sourceTable) { + mappingType = "parent"; + } + return { + columnName: col.columnName, + columnLabel: col.columnLabel, + referenceTable: col.referenceTable!, + referenceColumn: col.referenceColumn || "id", + mappingType, + }; + }); + }, [config.targetTable, config.sourceTable, targetColumns]); + + useEffect(() => { + setAutoDetectedFks(detectedFks); + }, [detectedFks]); + + // targetTable 변경 시 FK 자동 적용 리셋 + useEffect(() => { + fkAutoAppliedRef.current = false; + }, [config.targetTable]); + + // FK 자동 매핑 적용 (최초 1회) + useEffect(() => { + if (fkAutoAppliedRef.current || detectedFks.length === 0) return; + + const sourceFk = detectedFks.find((fk) => fk.mappingType === "source"); + const parentFks = detectedFks.filter((fk) => fk.mappingType === "parent"); + let changed = false; + + if (sourceFk && !config.sourceKeyField) { + handleChange("sourceKeyField", sourceFk.columnName); + changed = true; + } + + if ( + parentFks.length > 0 && + (!config.parentDataMapping || config.parentDataMapping.length === 0) + ) { + const autoMappings: ParentDataMapping[] = parentFks.map((fk) => ({ + sourceTable: fk.referenceTable, + sourceField: "id", + targetField: fk.columnName, + })); + handleChange("parentDataMapping", autoMappings); + changed = true; + } + + if (changed) { + fkAutoAppliedRef.current = true; + } + }, [detectedFks, config.sourceKeyField, config.parentDataMapping, handleChange]); + + // ─── 필드 관련 로컬 상태 ─── + const localFields = useMemo( + () => config.additionalFields || [], + [config.additionalFields], + ); + + const displayColumns = useMemo>( + () => config.displayColumns || [], + [config.displayColumns], + ); + + const localFieldGroups = useMemo( + () => config.fieldGroups || [], + [config.fieldGroups], + ); + + // 사용 가능한 원본 컬럼 (표시용) + const availableSourceColumns = useMemo(() => { + const used = new Set(displayColumns.map((c) => c.name)); + return sourceColumns.filter((c) => !used.has(c.columnName)); + }, [sourceColumns, displayColumns]); + + // 사용 가능한 대상 컬럼 (입력 필드용) + const availableTargetColumns = useMemo(() => { + const used = new Set(localFields.map((f) => f.name)); + return targetColumns.filter((c) => !used.has(c.columnName)); + }, [targetColumns, localFields]); + + // ─── 표시 컬럼 관리 ─── + const addDisplayColumn = useCallback( + (columnName: string, columnLabel: string) => { + if (!displayColumns.some((c) => c.name === columnName)) { + handleChange("displayColumns", [ + ...displayColumns, + { name: columnName, label: columnLabel }, + ]); + } + }, + [displayColumns, handleChange], + ); + + const removeDisplayColumn = useCallback( + (columnName: string) => { + handleChange( + "displayColumns", + displayColumns.filter((c) => c.name !== columnName), + ); + }, + [displayColumns, handleChange], + ); + + // ─── 추가 입력 필드 관리 ─── + const addField = useCallback(() => { + const newField: AdditionalFieldDefinition = { + name: `field_${localFields.length + 1}`, + label: `필드 ${localFields.length + 1}`, + type: "text", + }; + handleChange("additionalFields", [...localFields, newField]); + }, [localFields, handleChange]); + + const updateField = useCallback( + (index: number, updates: Partial) => { + const newFields = [...localFields]; + newFields[index] = { ...newFields[index], ...updates }; + handleChange("additionalFields", newFields); + }, + [localFields, handleChange], + ); + + const removeField = useCallback( + (index: number) => { + handleChange( + "additionalFields", + localFields.filter((_, i) => i !== index), + ); + }, + [localFields, handleChange], + ); + + // ─── 필드 그룹 관리 ─── + const addFieldGroup = useCallback(() => { + const newGroup: FieldGroup = { + id: `group_${localFieldGroups.length + 1}`, + title: `그룹 ${localFieldGroups.length + 1}`, + order: localFieldGroups.length, + }; + handleChange("fieldGroups", [...localFieldGroups, newGroup]); + }, [localFieldGroups, handleChange]); + + const updateFieldGroup = useCallback( + (groupId: string, updates: Partial) => { + const newGroups = localFieldGroups.map((g) => + g.id === groupId ? { ...g, ...updates } : g, + ); + handleChange("fieldGroups", newGroups); + }, + [localFieldGroups, handleChange], + ); + + const removeFieldGroup = useCallback( + (groupId: string) => { + const updatedFields = localFields.map((f) => + f.groupId === groupId ? { ...f, groupId: undefined } : f, + ); + handleChange("additionalFields", updatedFields); + handleChange( + "fieldGroups", + localFieldGroups.filter((g) => g.id !== groupId), + ); + }, + [localFields, localFieldGroups, handleChange], + ); + + // ─── 부모 매핑 관리 ─── + const parentMappings = useMemo( + () => config.parentDataMapping || [], + [config.parentDataMapping], + ); + + // 부모 매핑 소스 컬럼 캐시 + const [mappingSourceColumns, setMappingSourceColumns] = useState< + Record + >({}); + + const addParentMapping = useCallback(() => { + handleChange("parentDataMapping", [ + ...parentMappings, + { sourceTable: "", sourceField: "", targetField: "" }, + ]); + }, [parentMappings, handleChange]); + + const updateParentMapping = useCallback( + (index: number, updates: Partial) => { + const updated = [...parentMappings]; + updated[index] = { ...updated[index], ...updates }; + handleChange("parentDataMapping", updated); + }, + [parentMappings, handleChange], + ); + + const removeParentMapping = useCallback( + (index: number) => { + handleChange( + "parentDataMapping", + parentMappings.filter((_, i) => i !== index), + ); + }, + [parentMappings, handleChange], + ); + + // 부모 매핑 소스 컬럼 로드 + const loadMappingColumns = useCallback( + async (tableName: string, index: number) => { + const cols = await loadColumns(tableName); + setMappingSourceColumns((prev) => ({ ...prev, [index]: cols })); + }, + [loadColumns], + ); + + // 기존 매핑의 소스 컬럼 초기 로드 + useEffect(() => { + parentMappings.forEach((mapping, index) => { + if (mapping.sourceTable && !mappingSourceColumns[index]) { + loadMappingColumns(mapping.sourceTable, index); + } + }); + }, [parentMappings, mappingSourceColumns, loadMappingColumns]); + + // ─── Collapsible 상태 ─── + const [openSections, setOpenSections] = useState>({}); + const toggleSection = useCallback((key: string) => { + setOpenSections((prev) => ({ ...prev, [key]: !prev[key] })); + }, []); + + // ─── screenTableName 자동 설정 ─── + useEffect(() => { + if (screenTableName && !config.targetTable) { + handleChange("targetTable", screenTableName); + } + }, [screenTableName]); + + // ─── 렌더링 ─── + return ( +
+ {/* ════════ 1단계: 테이블 설정 ════════ */} +
+
+ + 테이블 설정 +
+

+ 데이터의 원본과 저장 대상을 설정해요 +

+
+ + {/* 데이터 소스 ID */} +
+ + handleChange("dataSourceId", e.target.value)} + placeholder="비워두면 URL 파라미터에서 자동 설정" + className="h-8 text-xs" + /> +

+ 비워두면 Button에서 자동 전달 +

+
+ + {/* 원본 테이블 */} +
+ + handleChange("sourceTable", v)} + /> +

+ 이전 화면에서 전달받은 데이터의 원본 테이블 +

+
+ + {/* 저장 대상 테이블 */} +
+ + handleChange("targetTable", v)} + /> +

+ 최종 데이터를 저장할 테이블 +

+
+ + {/* FK 자동 감지 결과 */} + {autoDetectedFks.length > 0 && ( +
+

+ FK 자동 감지 ({autoDetectedFks.length}건) +

+
+ {autoDetectedFks.map((fk) => ( +
+ + {fk.mappingType === "source" + ? "원본" + : fk.mappingType === "parent" + ? "부모" + : "미분류"} + + + {fk.columnName} + + + {fk.referenceTable} +
+ ))} +
+

+ 엔티티 설정 기반 자동 매핑. sourceKeyField와 parentDataMapping이 + 자동 설정됩니다. +

+
+ )} + + + + {/* ════════ 2단계: 레이아웃 & 입력 모드 선택 ════════ */} +
+
+ + 레이아웃 +
+
+ + {/* 레이아웃 카드 선택 */} +
+ {LAYOUT_CARDS.map((card) => { + const isSelected = (config.layout || "grid") === card.value; + const Icon = card.icon; + return ( + + ); + })} +
+ + {/* 입력 모드 카드 선택 */} +
+ + 입력 모드 + +
+
+ {INPUT_MODE_CARDS.map((card) => { + const isSelected = (config.inputMode || "inline") === card.value; + const Icon = card.icon; + return ( + + ); + })} +
+ + + + {/* ════════ 3단계: 표시 컬럼 (원본 데이터) ════════ */} +
+
+ + 표시 컬럼 (원본 데이터) +
+

+ 전달받은 원본 데이터 중 화면에 표시할 컬럼 +

+
+ + {displayColumns.length > 0 && ( +
+ {displayColumns.map((col) => ( +
+
+ {col.label} + + {col.name} + +
+ +
+ ))} +
+ )} + + {sourceColumns.length > 0 ? ( + + + + + + + + + + 사용 가능한 컬럼이 없습니다. + + + {availableSourceColumns.map((c) => ( + + addDisplayColumn( + c.columnName, + c.columnLabel || c.columnName, + ) + } + className="text-xs" + > +
+
+ {c.columnLabel || c.columnName} +
+ {c.dataType && ( +
+ {c.dataType} +
+ )} +
+
+ ))} +
+
+
+
+
+ ) : ( +

+ {config.sourceTable + ? "컬럼 로딩 중..." + : "원본 테이블을 먼저 선택하세요"} +

+ )} + + + + {/* ════════ 4단계: 추가 입력 필드 ════════ */} +
+
+ + 추가 입력 필드 +
+

+ 저장 대상 테이블의 컬럼을 입력 필드로 추가 +

+
+ + {localFields.length > 0 && ( +
+ {localFields.map((field, index) => ( +
+
+ + 필드 {index + 1}: {field.label || field.name} + + +
+ +
+ {/* 필드명 (컬럼 선택) */} +
+ + + updateField(index, { + name, + label: col.columnLabel || name, + inputType: col.inputType || "text", + codeCategory: col.codeCategory, + }) + } + /> +
+ + {/* 라벨 */} +
+ + + updateField(index, { label: e.target.value }) + } + placeholder="필드 라벨" + className="h-7 text-xs" + /> +
+
+ +
+ {/* 타입 (자동) */} +
+ + +
+ + {/* Placeholder */} +
+ + + updateField(index, { placeholder: e.target.value }) + } + placeholder="입력 안내" + className="h-7 text-xs" + /> +
+
+ + {/* 필드 그룹 선택 */} + {localFieldGroups.length > 0 && ( +
+ + +
+ )} + + {/* 필수 / 자동 채우기 */} +
+
+ + updateField(index, { required: checked }) + } + /> + +
+ {field.autoFillFrom && ( + + 자동 채우기: {field.autoFillFrom} + + )} +
+
+ ))} +
+ )} + + + + + + {/* ════════ 접히는 섹션들 ════════ */} + + {/* ─── 필드 그룹 관리 (Collapsible) ─── */} + toggleSection("fieldGroups")} + > + +
+ + 필드 그룹 관리 + {localFieldGroups.length > 0 && ( + + {localFieldGroups.length} + + )} +
+ +
+ +

+ 추가 입력 필드를 여러 카드로 나눠서 표시 (예: 거래처 정보, 단가 + 정보) +

+ + {localFieldGroups.map((group, index) => ( +
+
+ + 그룹 {index + 1}: {group.title} + + +
+ +
+
+ + + updateFieldGroup(group.id, { id: e.target.value }) + } + className="h-7 text-xs" + /> +
+
+ + + updateFieldGroup(group.id, { title: e.target.value }) + } + className="h-7 text-xs" + /> +
+
+ +
+
+ + + updateFieldGroup(group.id, { + description: e.target.value, + }) + } + placeholder="그룹 설명" + className="h-7 text-xs" + /> +
+
+ + + updateFieldGroup(group.id, { + order: parseInt(e.target.value) || 0, + }) + } + className="h-7 text-xs" + min="0" + /> +
+
+ + {/* 소스 테이블 (그룹별) */} +
+ + + updateFieldGroup(group.id, { sourceTable: v }) + } + /> +
+ + {/* 최대 항목 수 */} +
+ + + updateFieldGroup(group.id, { + maxEntries: parseInt(e.target.value) || undefined, + }) + } + placeholder="무제한" + className="h-7 w-20 text-xs" + min="1" + /> +
+
+ ))} + + +
+
+ + {/* ─── 부모 데이터 매핑 (Collapsible) ─── */} + toggleSection("parentMapping")} + > + +
+ + 부모 데이터 매핑 + {parentMappings.length > 0 && ( + + {parentMappings.length} + + )} +
+ +
+ +

+ 이전 화면(거래처 선택 등)에서 넘어온 데이터를 자동으로 매핑 +

+ + {parentMappings.map((mapping, index) => { + const isAutoDetected = autoDetectedFks.some( + (fk) => + fk.mappingType === "parent" && + fk.columnName === mapping.targetField, + ); + return ( +
+ {isAutoDetected && ( + + FK 자동 감지 + + )} + + {/* 소스 테이블 */} +
+ + { + updateParentMapping(index, { + sourceTable: v, + sourceField: "", + }); + loadMappingColumns(v, index); + }} + /> +
+ + {/* 원본 필드 */} +
+ + + updateParentMapping(index, { sourceField: name }) + } + /> +
+ + {/* 저장 필드 */} +
+ + + updateParentMapping(index, { targetField: name }) + } + /> +
+ + {/* 기본값 + 삭제 */} +
+ + updateParentMapping(index, { + defaultValue: e.target.value || undefined, + }) + } + placeholder="기본값 (선택)" + className="h-7 flex-1 text-xs" + /> + +
+
+ ); + })} + + +
+
+ + {/* ─── 자동 계산 설정 (Collapsible) ─── */} + toggleSection("autoCalc")} + > + +
+ + 자동 계산 + {config.autoCalculation && ( + + 활성 + + )} +
+ +
+ +
+ + { + if (checked) { + handleChange("autoCalculation", { + targetField: "", + mode: "template", + inputFields: { + basePrice: "", + discountType: "", + discountValue: "", + roundingType: "", + roundingUnit: "", + }, + calculationType: "price", + valueMapping: {}, + calculationSteps: [], + }); + } else { + handleChange("autoCalculation", undefined); + } + }} + /> +
+ + {config.autoCalculation && ( +
+ {/* 계산 모드 */} +
+ + +
+ + {/* 계산 결과 필드 */} +
+ + +
+ + {/* 템플릿 모드 필드 매핑 */} + {config.autoCalculation.mode === "template" && ( +
+ + 필드 매핑 + + {( + [ + ["basePrice", "기준 단가"], + ["discountType", "할인 방식"], + ["discountValue", "할인값"], + ["roundingType", "반올림 방식"], + ["roundingUnit", "반올림 단위"], + ] as const + ).map(([key, label]) => ( +
+ + {label} + + +
+ ))} +
+ )} +
+ )} +
+
+ + {/* ─── 고급 설정 (Collapsible) ─── */} + toggleSection("advanced")} + > + +
+ + 고급 설정 +
+ +
+ + {/* sourceKeyField */} +
+ + handleChange("sourceKeyField", name)} + /> +

+ 대상 테이블에서 원본을 참조하는 FK 컬럼 +

+
+ + {/* 항목 번호 표시 */} +
+ + handleChange("showIndex", v)} + /> +
+ + {/* 항목 삭제 허용 */} +
+ + handleChange("allowRemove", v)} + /> +
+ + {/* 비활성화 */} +
+ + handleChange("disabled", v)} + /> +
+ + {/* 읽기 전용 */} +
+ + handleChange("readonly", v)} + /> +
+ + {/* 빈 상태 메시지 */} +
+ + + handleChange("emptyMessage", e.target.value) + } + placeholder="전달받은 데이터가 없습니다." + className="h-8 text-xs" + /> +
+
+
+
+ ); +}; + +V2SelectedItemsDetailInputConfigPanel.displayName = + "V2SelectedItemsDetailInputConfigPanel"; + +export default V2SelectedItemsDetailInputConfigPanel; diff --git a/frontend/lib/registry/components/selected-items-detail-input/index.ts b/frontend/lib/registry/components/selected-items-detail-input/index.ts index e4bca645..cced07d7 100644 --- a/frontend/lib/registry/components/selected-items-detail-input/index.ts +++ b/frontend/lib/registry/components/selected-items-detail-input/index.ts @@ -1,11 +1,9 @@ "use client"; -import React from "react"; import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; -import type { WebType } from "@/types/screen"; import { SelectedItemsDetailInputWrapper } from "./SelectedItemsDetailInputComponent"; -import { SelectedItemsDetailInputConfigPanel } from "./SelectedItemsDetailInputConfigPanel"; +import { V2SelectedItemsDetailInputConfigPanel } from "@/components/v2/config-panels/V2SelectedItemsDetailInputConfigPanel"; import { SelectedItemsDetailInputConfig } from "./types"; /** @@ -33,7 +31,7 @@ export const SelectedItemsDetailInputDefinition = createComponentDefinition({ readonly: false, } as SelectedItemsDetailInputConfig, defaultSize: { width: 800, height: 400 }, - configPanel: SelectedItemsDetailInputConfigPanel, + configPanel: V2SelectedItemsDetailInputConfigPanel, icon: "Table", tags: ["선택", "상세입력", "반복", "테이블", "데이터전달"], version: "1.0.0",