슬롯 컴포넌트 상세 설정 패널 추가: RepeatContainerConfigPanel에 슬롯 컴포넌트의 필드 바인딩, 라벨 설정, 크기 및 스타일 변경 기능을 포함한 상세 설정 패널을 구현하였습니다. 또한, 동적 컴포넌트 설정 패널을 통해 각 컴포넌트의 전용 설정을 관리할 수 있도록 개선하였습니다.

This commit is contained in:
kjs 2026-01-19 09:37:02 +09:00
parent b4bfb9964f
commit 6ea3aef396
2 changed files with 488 additions and 50 deletions

View File

@ -14,11 +14,12 @@ import {
} from "@/components/ui/select";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Database, Table2, ChevronsUpDown, Check, LayoutGrid, LayoutList, Rows3, Plus, X, GripVertical, Trash2, Type } from "lucide-react";
import { Database, Table2, ChevronsUpDown, Check, LayoutGrid, LayoutList, Rows3, Plus, X, GripVertical, Trash2, Type, Settings2, ChevronDown, ChevronUp } from "lucide-react";
import { cn } from "@/lib/utils";
import { RepeatContainerConfig, SlotComponentConfig } from "./types";
import { tableTypeApi } from "@/lib/api/screen";
import { tableManagementApi } from "@/lib/api/tableManagement";
import { DynamicComponentConfigPanel, hasComponentConfigPanel } from "@/lib/utils/getComponentConfigPanel";
interface RepeatContainerConfigPanelProps {
config: RepeatContainerConfig;
@ -263,6 +264,7 @@ export function RepeatContainerConfigPanel({
onChange={onChange}
availableColumns={availableColumns}
loadingColumns={loadingColumns}
screenTableName={screenTableName}
/>
{/* 레이아웃 설정 */}
@ -597,11 +599,89 @@ export function RepeatContainerConfigPanel({
// ============================================================
// 슬롯 자식 컴포넌트 관리 섹션
// ============================================================
// 슬롯 컴포넌트의 전체 설정 패널을 표시하는 컴포넌트
interface SlotComponentDetailPanelProps {
child: SlotComponentConfig;
screenTableName?: string;
availableColumns: Array<{ columnName: string; displayName?: string }>;
onConfigChange: (newConfig: Record<string, any>) => void;
onFieldNameChange: (fieldName: string) => void;
onLabelChange: (label: string) => void;
}
function SlotComponentDetailPanel({
child,
screenTableName,
availableColumns,
onConfigChange,
onFieldNameChange,
onLabelChange,
}: SlotComponentDetailPanelProps) {
return (
<div className="space-y-3">
{/* 데이터 필드 바인딩 - 모든 컴포넌트에서 사용 가능 */}
<div className="space-y-1 p-2 bg-blue-50 rounded-md border border-blue-200">
<Label className="text-[10px] text-blue-700 font-medium flex items-center gap-1">
<Database className="h-3 w-3" />
</Label>
<Select
value={child.fieldName || ""}
onValueChange={onFieldNameChange}
>
<SelectTrigger className="h-7 text-xs bg-white">
<SelectValue placeholder="표시할 필드 선택..." />
</SelectTrigger>
<SelectContent>
<SelectItem value=""> </SelectItem>
{availableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
{child.fieldName && (
<p className="text-[9px] text-blue-600">
"{child.fieldName}"
</p>
)}
</div>
{/* 라벨 설정 */}
<div className="space-y-1">
<Label className="text-[10px] text-slate-500"> </Label>
<Input
value={child.label || ""}
onChange={(e) => onLabelChange(e.target.value)}
placeholder="표시할 라벨"
className="h-7 text-xs"
/>
</div>
{/* 컴포넌트 전용 설정 */}
<div className="border-t pt-2">
<div className="text-[10px] font-medium text-slate-600 mb-2">
{child.componentType}
</div>
<DynamicComponentConfigPanel
componentId={child.componentType}
config={child.componentConfig || {}}
onChange={onConfigChange}
screenTableName={screenTableName}
/>
</div>
</div>
);
}
interface SlotChildrenSectionProps {
config: RepeatContainerConfig;
onChange: (config: Partial<RepeatContainerConfig>) => void;
availableColumns: Array<{ columnName: string; displayName?: string }>;
loadingColumns: boolean;
screenTableName?: string;
}
function SlotChildrenSection({
@ -609,12 +689,28 @@ function SlotChildrenSection({
onChange,
availableColumns,
loadingColumns,
screenTableName,
}: SlotChildrenSectionProps) {
const [selectedColumn, setSelectedColumn] = useState<string>("");
const [columnComboboxOpen, setColumnComboboxOpen] = useState(false);
// 각 컴포넌트별 상세 설정 열림 상태
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
const children = config.children || [];
// 상세 설정 열기/닫기 토글
const toggleExpanded = (id: string) => {
setExpandedIds((prev) => {
const newSet = new Set(prev);
if (newSet.has(id)) {
newSet.delete(id);
} else {
newSet.add(id);
}
return newSet;
});
};
// 컴포넌트 추가
const addComponent = (columnName: string, displayName: string) => {
const newChild: SlotComponentConfig = {
@ -625,6 +721,7 @@ function SlotChildrenSection({
position: { x: 0, y: children.length * 40 },
size: { width: 200, height: 32 },
componentConfig: {},
style: {},
};
onChange({
@ -639,6 +736,11 @@ function SlotChildrenSection({
onChange({
children: children.filter((c) => c.id !== id),
});
setExpandedIds((prev) => {
const newSet = new Set(prev);
newSet.delete(id);
return newSet;
});
};
// 컴포넌트 라벨 변경
@ -655,6 +757,46 @@ function SlotChildrenSection({
});
};
// 컴포넌트 필드 바인딩 변경
const updateComponentFieldName = (id: string, fieldName: string) => {
onChange({
children: children.map((c) => (c.id === id ? { ...c, fieldName } : c)),
});
};
// 컴포넌트 설정 변경 (componentConfig)
const updateComponentConfig = (id: string, key: string, value: any) => {
onChange({
children: children.map((c) =>
c.id === id
? { ...c, componentConfig: { ...c.componentConfig, [key]: value } }
: c
),
});
};
// 컴포넌트 스타일 변경
const updateComponentStyle = (id: string, key: string, value: any) => {
onChange({
children: children.map((c) =>
c.id === id
? { ...c, style: { ...c.style, [key]: value } }
: c
),
});
};
// 컴포넌트 크기 변경
const updateComponentSize = (id: string, width: number | undefined, height: number | undefined) => {
onChange({
children: children.map((c) =>
c.id === id
? { ...c, size: { width: width ?? c.size?.width ?? 200, height: height ?? c.size?.height ?? 32 } }
: c
),
});
};
return (
<div className="space-y-3">
<div>
@ -668,16 +810,18 @@ function SlotChildrenSection({
{/* 추가된 필드 목록 */}
{children.length > 0 ? (
<div className="space-y-2">
{children.map((child, index) => (
<div
key={child.id}
className="flex items-center gap-2 rounded-md border border-green-200 bg-green-50 p-2"
>
<div className="flex h-5 w-5 items-center justify-center rounded bg-green-200 text-xs font-medium text-green-700">
{index + 1}
</div>
<div className="flex-1 space-y-1">
<div className="flex items-center gap-2">
{children.map((child, index) => {
const isExpanded = expandedIds.has(child.id);
return (
<div
key={child.id}
className="rounded-md border border-green-200 bg-green-50 overflow-hidden"
>
{/* 기본 정보 헤더 */}
<div className="flex items-center gap-2 p-2">
<div className="flex h-5 w-5 items-center justify-center rounded bg-green-200 text-xs font-medium text-green-700">
{index + 1}
</div>
<div className="flex-1">
<div className="text-xs font-medium text-green-700">
{child.label || child.fieldName}
@ -700,18 +844,257 @@ function SlotChildrenSection({
<SelectItem value="date-display"></SelectItem>
</SelectContent>
</Select>
<Button
variant="ghost"
size="sm"
className="h-5 w-5 p-0 text-blue-500 hover:text-blue-700"
onClick={() => toggleExpanded(child.id)}
title="상세 설정"
>
{isExpanded ? (
<ChevronUp className="h-3 w-3" />
) : (
<Settings2 className="h-3 w-3" />
)}
</Button>
<Button
variant="ghost"
size="sm"
className="h-5 w-5 p-0 text-red-400 hover:text-red-600"
onClick={() => removeComponent(child.id)}
>
<X className="h-3 w-3" />
</Button>
</div>
{/* 상세 설정 패널 (펼침) */}
{isExpanded && (
<div className="border-t border-green-200 bg-white p-3 space-y-3">
{/* 전용 ConfigPanel이 있는 복잡한 컴포넌트인 경우 */}
{hasComponentConfigPanel(child.componentType) ? (
<SlotComponentDetailPanel
child={child}
screenTableName={screenTableName}
availableColumns={availableColumns}
onConfigChange={(newConfig) => {
onChange({
children: children.map((c) =>
c.id === child.id
? { ...c, componentConfig: { ...c.componentConfig, ...newConfig } }
: c
),
});
}}
onFieldNameChange={(fieldName) => updateComponentFieldName(child.id, fieldName)}
onLabelChange={(label) => updateComponentLabel(child.id, label)}
/>
) : (
<>
{/* 데이터 필드 바인딩 - 가장 중요! */}
<div className="space-y-1">
<Label className="text-[10px] text-slate-500 font-medium flex items-center gap-1">
<Database className="h-3 w-3 text-blue-500" />
</Label>
<Select
value={child.fieldName || ""}
onValueChange={(value) => updateComponentFieldName(child.id, value)}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="표시할 필드 선택..." />
</SelectTrigger>
<SelectContent>
<SelectItem value=""> </SelectItem>
{availableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
{child.fieldName && (
<p className="text-[9px] text-green-600">
"{child.fieldName}"
</p>
)}
</div>
{/* 라벨 설정 */}
<div className="space-y-1">
<Label className="text-[10px] text-slate-500"> </Label>
<Input
value={child.label || ""}
onChange={(e) => updateComponentLabel(child.id, e.target.value)}
placeholder="표시할 라벨"
className="h-7 text-xs"
/>
</div>
{/* 크기 설정 */}
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-[10px] text-slate-500"> (px)</Label>
<Input
type="number"
value={child.size?.width || 200}
onChange={(e) =>
updateComponentSize(child.id, parseInt(e.target.value) || 200, undefined)
}
className="h-7 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-[10px] text-slate-500"> (px)</Label>
<Input
type="number"
value={child.size?.height || 32}
onChange={(e) =>
updateComponentSize(child.id, undefined, parseInt(e.target.value) || 32)
}
className="h-7 text-xs"
/>
</div>
</div>
{/* 스타일 설정 */}
<div className="space-y-2">
<Label className="text-[10px] text-slate-500 font-medium"></Label>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-[10px] text-slate-400"> </Label>
<Select
value={child.style?.fontSize || "14px"}
onValueChange={(value) => updateComponentStyle(child.id, "fontSize", value)}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="10px">10px ( )</SelectItem>
<SelectItem value="12px">12px ()</SelectItem>
<SelectItem value="14px">14px ()</SelectItem>
<SelectItem value="16px">16px ()</SelectItem>
<SelectItem value="18px">18px ( )</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-[10px] text-slate-400"> </Label>
<Select
value={child.style?.fontWeight || "normal"}
onValueChange={(value) => updateComponentStyle(child.id, "fontWeight", value)}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="normal"></SelectItem>
<SelectItem value="500"></SelectItem>
<SelectItem value="600"> </SelectItem>
<SelectItem value="bold"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-[10px] text-slate-400"> </Label>
<Select
value={child.style?.textAlign || "left"}
onValueChange={(value) => updateComponentStyle(child.id, "textAlign", value)}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="left"></SelectItem>
<SelectItem value="center"></SelectItem>
<SelectItem value="right"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-[10px] text-slate-400"> </Label>
<div className="flex gap-1">
<Input
type="color"
value={child.style?.color || "#000000"}
onChange={(e) => updateComponentStyle(child.id, "color", e.target.value)}
className="h-7 w-10 p-0.5 cursor-pointer"
/>
<Input
value={child.style?.color || "#000000"}
onChange={(e) => updateComponentStyle(child.id, "color", e.target.value)}
className="h-7 flex-1 text-xs"
placeholder="#000000"
/>
</div>
</div>
</div>
</div>
{/* 컴포넌트 타입별 추가 설정 */}
{(child.componentType === "number-display" || child.componentType === "number-input") && (
<div className="space-y-2">
<Label className="text-[10px] text-slate-500 font-medium"> </Label>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-[10px] text-slate-400"> 릿</Label>
<Input
type="number"
min={0}
max={10}
value={child.componentConfig?.decimalPlaces ?? 0}
onChange={(e) =>
updateComponentConfig(child.id, "decimalPlaces", parseInt(e.target.value) || 0)
}
className="h-7 text-xs"
/>
</div>
<div className="flex items-center gap-2 pt-4">
<Checkbox
id={`thousandSep_${child.id}`}
checked={child.componentConfig?.thousandSeparator ?? true}
onCheckedChange={(checked) =>
updateComponentConfig(child.id, "thousandSeparator", checked)
}
/>
<Label htmlFor={`thousandSep_${child.id}`} className="text-[10px]">
</Label>
</div>
</div>
</div>
)}
{(child.componentType === "date-display" || child.componentType === "date-input") && (
<div className="space-y-2">
<Label className="text-[10px] text-slate-500 font-medium"> </Label>
<Select
value={child.componentConfig?.dateFormat || "YYYY-MM-DD"}
onValueChange={(value) => updateComponentConfig(child.id, "dateFormat", value)}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="YYYY-MM-DD">2024-01-15</SelectItem>
<SelectItem value="YYYY.MM.DD">2024.01.15</SelectItem>
<SelectItem value="YYYY/MM/DD">2024/01/15</SelectItem>
<SelectItem value="MM/DD/YYYY">01/15/2024</SelectItem>
<SelectItem value="DD-MM-YYYY">15-01-2024</SelectItem>
<SelectItem value="YYYY-MM-DD HH:mm">2024-01-15 14:30</SelectItem>
</SelectContent>
</Select>
</div>
)}
</>
)}
</div>
)}
</div>
<Button
variant="ghost"
size="sm"
className="h-5 w-5 p-0 text-red-400 hover:text-red-600"
onClick={() => removeComponent(child.id)}
>
<X className="h-3 w-3" />
</Button>
</div>
))}
);
})}
</div>
) : (
<div className="rounded-md border border-dashed border-slate-300 bg-slate-50 p-4 text-center">

View File

@ -5,7 +5,9 @@
import React from "react";
// 컴포넌트별 ConfigPanel 동적 import 맵
// 모든 ConfigPanel이 있는 컴포넌트를 여기에 등록해야 슬롯/중첩 컴포넌트에서 전용 설정 패널이 표시됨
const CONFIG_PANEL_MAP: Record<string, () => Promise<any>> = {
// ========== 기본 입력 컴포넌트 ==========
"text-input": () => import("@/lib/registry/components/text-input/TextInputConfigPanel"),
"number-input": () => import("@/lib/registry/components/number-input/NumberInputConfigPanel"),
"date-input": () => import("@/lib/registry/components/date-input/DateInputConfigPanel"),
@ -15,34 +17,60 @@ const CONFIG_PANEL_MAP: Record<string, () => Promise<any>> = {
"radio-basic": () => import("@/lib/registry/components/radio-basic/RadioBasicConfigPanel"),
"toggle-switch": () => import("@/lib/registry/components/toggle-switch/ToggleSwitchConfigPanel"),
"file-upload": () => import("@/lib/registry/components/file-upload/FileUploadConfigPanel"),
"button-primary": () => import("@/components/screen/config-panels/ButtonConfigPanel"),
"text-display": () => import("@/lib/registry/components/text-display/TextDisplayConfigPanel"),
"slider-basic": () => import("@/lib/registry/components/slider-basic/SliderBasicConfigPanel"),
"test-input": () => import("@/lib/registry/components/test-input/TestInputConfigPanel"),
// ========== 버튼 ==========
"button-primary": () => import("@/components/screen/config-panels/ButtonConfigPanel"),
// ========== 표시 컴포넌트 ==========
"text-display": () => import("@/lib/registry/components/text-display/TextDisplayConfigPanel"),
"image-display": () => import("@/lib/registry/components/image-display/ImageDisplayConfigPanel"),
"divider-line": () => import("@/lib/registry/components/divider-line/DividerLineConfigPanel"),
"image-widget": () => import("@/lib/registry/components/image-widget/ImageWidgetConfigPanel"),
// ========== 레이아웃/컨테이너 ==========
"accordion-basic": () => import("@/lib/registry/components/accordion-basic/AccordionBasicConfigPanel"),
"table-list": () => import("@/lib/registry/components/table-list/TableListConfigPanel"),
"card-display": () => import("@/lib/registry/components/card-display/CardDisplayConfigPanel"),
"split-panel-layout": () => import("@/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel"),
"split-panel-layout2": () => import("@/lib/registry/components/split-panel-layout2/SplitPanelLayout2ConfigPanel"),
"repeater-field-group": () => import("@/components/webtypes/config/RepeaterConfigPanel"),
"flow-widget": () => import("@/components/screen/config-panels/FlowWidgetConfigPanel"),
// 🆕 수주 등록 관련 컴포넌트들
"autocomplete-search-input": () =>
import("@/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel"),
"entity-search-input": () => import("@/lib/registry/components/entity-search-input/EntitySearchInputConfigPanel"),
"modal-repeater-table": () => import("@/lib/registry/components/modal-repeater-table/ModalRepeaterTableConfigPanel"),
// 🆕 조건부 컨테이너
"conditional-container": () =>
import("@/lib/registry/components/conditional-container/ConditionalContainerConfigPanel"),
// 🆕 선택 항목 상세입력
"selected-items-detail-input": () =>
import("@/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputConfigPanel"),
// 🆕 섹션 그룹화 레이아웃
"section-card": () => import("@/lib/registry/components/section-card/SectionCardConfigPanel"),
"section-paper": () => import("@/lib/registry/components/section-paper/SectionPaperConfigPanel"),
// 🆕 탭 컴포넌트
"split-panel-layout": () => import("@/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel"),
"split-panel-layout2": () => import("@/lib/registry/components/split-panel-layout2/SplitPanelLayout2ConfigPanel"),
"screen-split-panel": () => import("@/lib/registry/components/screen-split-panel/ScreenSplitPanelConfigPanel"),
"conditional-container": () => import("@/lib/registry/components/conditional-container/ConditionalContainerConfigPanel"),
// ========== 테이블/리스트 ==========
"table-list": () => import("@/lib/registry/components/table-list/TableListConfigPanel"),
"pivot-grid": () => import("@/lib/registry/components/pivot-grid/PivotGridConfigPanel"),
"table-search-widget": () => import("@/lib/registry/components/table-search-widget/TableSearchWidgetConfigPanel"),
"tax-invoice-list": () => import("@/lib/registry/components/tax-invoice-list/TaxInvoiceListConfigPanel"),
// ========== 리피터/반복 ==========
"repeat-container": () => import("@/lib/registry/components/repeat-container/RepeatContainerConfigPanel"),
"repeater-field-group": () => import("@/components/webtypes/config/RepeaterConfigPanel"),
"unified-repeater": () => import("@/components/unified/config-panels/UnifiedRepeaterConfigPanel"),
"simple-repeater-table": () => import("@/lib/registry/components/simple-repeater-table/SimpleRepeaterTableConfigPanel"),
"modal-repeater-table": () => import("@/lib/registry/components/modal-repeater-table/ModalRepeaterTableConfigPanel"),
"repeat-screen-modal": () => import("@/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel"),
"related-data-buttons": () => import("@/lib/registry/components/related-data-buttons/RelatedDataButtonsConfigPanel"),
// ========== 검색/선택 ==========
"autocomplete-search-input": () => import("@/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel"),
"entity-search-input": () => import("@/lib/registry/components/entity-search-input/EntitySearchInputConfigPanel"),
"selected-items-detail-input": () => import("@/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputConfigPanel"),
"customer-item-mapping": () => import("@/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel"),
"mail-recipient-selector": () => import("@/lib/registry/components/mail-recipient-selector/MailRecipientSelectorConfigPanel"),
"location-swap-selector": () => import("@/lib/registry/components/location-swap-selector/LocationSwapSelectorConfigPanel"),
// ========== 특수 컴포넌트 ==========
"flow-widget": () => import("@/components/screen/config-panels/FlowWidgetConfigPanel"),
"tabs-widget": () => import("@/components/screen/config-panels/TabsConfigPanel"),
"map": () => import("@/lib/registry/components/map/MapConfigPanel"),
"rack-structure": () => import("@/lib/registry/components/rack-structure/RackStructureConfigPanel"),
"aggregation-widget": () => import("@/lib/registry/components/aggregation-widget/AggregationWidgetConfigPanel"),
"numbering-rule": () => import("@/lib/registry/components/numbering-rule/NumberingRuleConfigPanel"),
"category-manager": () => import("@/lib/registry/components/category-manager/CategoryManagerConfigPanel"),
"universal-form-modal": () => import("@/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel"),
};
// ConfigPanel 컴포넌트 캐시
@ -68,16 +96,43 @@ export async function getComponentConfigPanel(componentId: string): Promise<Reac
const module = await importFn();
// 모듈에서 ConfigPanel 컴포넌트 추출
// 1차: PascalCase 변환된 이름으로 찾기 (예: text-input -> TextInputConfigPanel)
// 2차: 특수 export명들 fallback
// 3차: default export
const pascalCaseName = `${toPascalCase(componentId)}ConfigPanel`;
const ConfigPanelComponent =
module[`${toPascalCase(componentId)}ConfigPanel`] ||
module.RepeaterConfigPanel || // repeater-field-group의 export명
module.FlowWidgetConfigPanel || // flow-widget의 export명
module.CustomerItemMappingConfigPanel || // customer-item-mapping의 export명
module.SelectedItemsDetailInputConfigPanel || // selected-items-detail-input의 export명
module.ButtonConfigPanel || // button-primary의 export명
module.SectionCardConfigPanel || // section-card의 export명
module.SectionPaperConfigPanel || // section-paper의 export명
module.TabsConfigPanel || // tabs-widget의 export명
module[pascalCaseName] ||
// 특수 export명들
module.RepeaterConfigPanel ||
module.FlowWidgetConfigPanel ||
module.CustomerItemMappingConfigPanel ||
module.SelectedItemsDetailInputConfigPanel ||
module.ButtonConfigPanel ||
module.SectionCardConfigPanel ||
module.SectionPaperConfigPanel ||
module.TabsConfigPanel ||
module.UnifiedRepeaterConfigPanel ||
module.RepeatContainerConfigPanel ||
module.ScreenSplitPanelConfigPanel ||
module.SimpleRepeaterTableConfigPanel ||
module.ModalRepeaterTableConfigPanel ||
module.RepeatScreenModalConfigPanel ||
module.RelatedDataButtonsConfigPanel ||
module.AutocompleteSearchInputConfigPanel ||
module.EntitySearchInputConfigPanel ||
module.MailRecipientSelectorConfigPanel ||
module.LocationSwapSelectorConfigPanel ||
module.MapConfigPanel ||
module.RackStructureConfigPanel ||
module.AggregationWidgetConfigPanel ||
module.NumberingRuleConfigPanel ||
module.CategoryManagerConfigPanel ||
module.UniversalFormModalConfigPanel ||
module.PivotGridConfigPanel ||
module.TableSearchWidgetConfigPanel ||
module.TaxInvoiceListConfigPanel ||
module.ImageWidgetConfigPanel ||
module.TestInputConfigPanel ||
module.default;
if (!ConfigPanelComponent) {