웹 타입 설정패널 분리

This commit is contained in:
kjs
2025-09-09 15:42:04 +09:00
parent a17602c643
commit 49c8f9a2dd
12 changed files with 357 additions and 243 deletions

View File

@@ -5115,6 +5115,7 @@ model web_type_standards {
description String?
category String? @default("input") @db.VarChar(50)
component_name String? @default("TextWidget") @db.VarChar(100)
config_panel String? @db.VarChar(100)
default_config Json?
validation_rules Json?
default_style Json?

View File

@@ -2,6 +2,42 @@ import { Request, Response } from "express";
import { PrismaClient } from "@prisma/client";
import { AuthenticatedRequest } from "../types/auth";
// 임시 타입 확장 (Prisma Client 재생성 전까지)
interface WebTypeStandardCreateData {
web_type: string;
type_name: string;
type_name_eng?: string;
description?: string;
category?: string;
component_name?: string;
config_panel?: string;
default_config?: any;
validation_rules?: any;
default_style?: any;
input_properties?: any;
sort_order?: number;
is_active?: string;
created_by?: string;
updated_by?: string;
}
interface WebTypeStandardUpdateData {
type_name?: string;
type_name_eng?: string;
description?: string;
category?: string;
component_name?: string;
config_panel?: string;
default_config?: any;
validation_rules?: any;
default_style?: any;
input_properties?: any;
sort_order?: number;
is_active?: string;
updated_by?: string;
updated_date?: Date;
}
const prisma = new PrismaClient();
export class WebTypeStandardController {
@@ -91,6 +127,7 @@ export class WebTypeStandardController {
description,
category = "input",
component_name = "TextWidget",
config_panel,
default_config,
validation_rules,
default_style,
@@ -127,6 +164,7 @@ export class WebTypeStandardController {
description,
category,
component_name,
config_panel,
default_config,
validation_rules,
default_style,
@@ -135,7 +173,7 @@ export class WebTypeStandardController {
is_active,
created_by: req.user?.userId || "system",
updated_by: req.user?.userId || "system",
},
} as WebTypeStandardCreateData,
});
return res.status(201).json({
@@ -163,6 +201,7 @@ export class WebTypeStandardController {
description,
category,
component_name,
config_panel,
default_config,
validation_rules,
default_style,
@@ -191,6 +230,7 @@ export class WebTypeStandardController {
description,
category,
component_name,
config_panel,
default_config,
validation_rules,
default_style,
@@ -199,7 +239,7 @@ export class WebTypeStandardController {
is_active,
updated_by: req.user?.userId || "system",
updated_date: new Date(),
},
} as WebTypeStandardUpdateData,
});
return res.json({

View File

@@ -14,6 +14,7 @@ import { toast } from "sonner";
import { ArrowLeft, Save, RotateCcw, Eye } from "lucide-react";
import { useWebTypes, type WebTypeFormData } from "@/hooks/admin/useWebTypes";
import { AVAILABLE_COMPONENTS, getComponentInfo } from "@/lib/utils/availableComponents";
import { AVAILABLE_CONFIG_PANELS, getConfigPanelInfo } from "@/lib/utils/availableConfigPanels";
import Link from "next/link";
// 기본 카테고리 목록
@@ -57,6 +58,7 @@ export default function EditWebTypePage() {
description: found.description || "",
category: found.category,
component_name: found.component_name || "TextWidget",
config_panel: found.config_panel || "none",
default_config: found.default_config || {},
validation_rules: found.validation_rules || {},
default_style: found.default_style || {},
@@ -159,6 +161,7 @@ export default function EditWebTypePage() {
description: originalData.description || "",
category: originalData.category,
component_name: originalData.component_name || "TextWidget",
config_panel: originalData.config_panel || "none",
default_config: originalData.default_config || {},
validation_rules: originalData.validation_rules || {},
default_style: originalData.default_style || {},
@@ -311,6 +314,44 @@ export default function EditWebTypePage() {
)}
</div>
{/* 설정 패널 */}
<div className="space-y-2">
<Label htmlFor="config_panel"> </Label>
<Select
value={formData.config_panel || "none"}
onValueChange={(value) => handleInputChange("config_panel", value === "none" ? null : value)}
>
<SelectTrigger>
<SelectValue placeholder="설정 패널 선택" />
</SelectTrigger>
<SelectContent>
{AVAILABLE_CONFIG_PANELS.map((panel) => (
<SelectItem key={panel.value} value={panel.value}>
<div className="flex flex-col">
<span className="font-medium">{panel.label}</span>
<span className="text-muted-foreground text-xs">{panel.description}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
{formData.config_panel && (
<div className="rounded-lg border border-green-200 bg-green-50 p-3">
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-green-500"></div>
<span className="text-sm font-medium text-green-700">
: {getConfigPanelInfo(formData.config_panel)?.label || formData.config_panel}
</span>
</div>
{getConfigPanelInfo(formData.config_panel)?.description && (
<p className="mt-1 ml-4 text-xs text-green-600">
{getConfigPanelInfo(formData.config_panel)?.description}
</p>
)}
</div>
)}
</div>
{/* 설명 */}
<div className="space-y-2">
<Label htmlFor="description"></Label>

View File

@@ -14,6 +14,7 @@ import { toast } from "sonner";
import { ArrowLeft, Save, RotateCcw } from "lucide-react";
import { useWebTypes, type WebTypeFormData } from "@/hooks/admin/useWebTypes";
import { AVAILABLE_COMPONENTS } from "@/lib/utils/availableComponents";
import { AVAILABLE_CONFIG_PANELS, getConfigPanelInfo } from "@/lib/utils/availableConfigPanels";
import Link from "next/link";
// 기본 카테고리 목록
@@ -30,6 +31,7 @@ export default function NewWebTypePage() {
description: "",
category: "input",
component_name: "TextWidget",
config_panel: "none",
default_config: {},
validation_rules: {},
default_style: {},
@@ -139,6 +141,7 @@ export default function NewWebTypePage() {
description: "",
category: "input",
component_name: "TextWidget",
config_panel: "none",
default_config: {},
validation_rules: {},
default_style: {},
@@ -270,6 +273,44 @@ export default function NewWebTypePage() {
)}
</div>
{/* 설정 패널 */}
<div className="space-y-2">
<Label htmlFor="config_panel"> </Label>
<Select
value={formData.config_panel || "none"}
onValueChange={(value) => handleInputChange("config_panel", value === "none" ? null : value)}
>
<SelectTrigger>
<SelectValue placeholder="설정 패널 선택" />
</SelectTrigger>
<SelectContent>
{AVAILABLE_CONFIG_PANELS.map((panel) => (
<SelectItem key={panel.value} value={panel.value}>
<div className="flex flex-col">
<span className="font-medium">{panel.label}</span>
<span className="text-muted-foreground text-xs">{panel.description}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
{formData.config_panel && (
<div className="rounded-lg border border-green-200 bg-green-50 p-3">
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-green-500"></div>
<span className="text-sm font-medium text-green-700">
: {getConfigPanelInfo(formData.config_panel)?.label || formData.config_panel}
</span>
</div>
{getConfigPanelInfo(formData.config_panel)?.description && (
<p className="mt-1 ml-4 text-xs text-green-600">
{getConfigPanelInfo(formData.config_panel)?.description}
</p>
)}
</div>
)}
</div>
{/* 설명 */}
<div className="space-y-2">
<Label htmlFor="description"></Label>

View File

@@ -245,6 +245,13 @@ export default function WebTypesManagePage() {
(sortDirection === "asc" ? <SortAsc className="h-4 w-4" /> : <SortDesc className="h-4 w-4" />)}
</div>
</TableHead>
<TableHead className="hover:bg-muted/50 cursor-pointer" onClick={() => handleSort("config_panel")}>
<div className="flex items-center gap-2">
{sortField === "config_panel" &&
(sortDirection === "asc" ? <SortAsc className="h-4 w-4" /> : <SortDesc className="h-4 w-4" />)}
</div>
</TableHead>
<TableHead className="hover:bg-muted/50 cursor-pointer" onClick={() => handleSort("is_active")}>
<div className="flex items-center gap-2">
@@ -265,7 +272,7 @@ export default function WebTypesManagePage() {
<TableBody>
{filteredAndSortedWebTypes.length === 0 ? (
<TableRow>
<TableCell colSpan={9} className="py-8 text-center">
<TableCell colSpan={10} className="py-8 text-center">
.
</TableCell>
</TableRow>
@@ -289,6 +296,11 @@ export default function WebTypesManagePage() {
{webType.component_name || "TextWidget"}
</Badge>
</TableCell>
<TableCell>
<Badge variant="secondary" className="font-mono text-xs">
{webType.config_panel === "none" || !webType.config_panel ? "기본 설정" : webType.config_panel}
</Badge>
</TableCell>
<TableCell>
<Badge variant={webType.is_active === "Y" ? "default" : "secondary"}>
{webType.is_active === "Y" ? "활성화" : "비활성화"}

View File

@@ -3063,6 +3063,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
autoHeight={true}
>
<DetailSettingsPanel
key={`detail-settings-${selectedComponent?.id}-${selectedComponent?.type === "widget" ? (selectedComponent as any).widgetType : "non-widget"}`}
selectedComponent={selectedComponent || undefined}
onUpdateProperty={(componentId: string, path: string, value: any) => {
updateComponentProperty(componentId, path, value);

View File

@@ -4,35 +4,8 @@ import React from "react";
import { Settings } from "lucide-react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useWebTypes } from "@/hooks/admin/useWebTypes";
import {
ComponentData,
WidgetComponent,
FileComponent,
WebTypeConfig,
DateTypeConfig,
NumberTypeConfig,
SelectTypeConfig,
TextTypeConfig,
TextareaTypeConfig,
CheckboxTypeConfig,
RadioTypeConfig,
FileTypeConfig,
CodeTypeConfig,
EntityTypeConfig,
ButtonTypeConfig,
TableInfo,
} from "@/types/screen";
import { DateTypeConfigPanel } from "./webtype-configs/DateTypeConfigPanel";
import { NumberTypeConfigPanel } from "./webtype-configs/NumberTypeConfigPanel";
import { SelectTypeConfigPanel } from "./webtype-configs/SelectTypeConfigPanel";
import { TextTypeConfigPanel } from "./webtype-configs/TextTypeConfigPanel";
import { TextareaTypeConfigPanel } from "./webtype-configs/TextareaTypeConfigPanel";
import { CheckboxTypeConfigPanel } from "./webtype-configs/CheckboxTypeConfigPanel";
import { RadioTypeConfigPanel } from "./webtype-configs/RadioTypeConfigPanel";
import { FileTypeConfigPanel } from "./webtype-configs/FileTypeConfigPanel";
import { CodeTypeConfigPanel } from "./webtype-configs/CodeTypeConfigPanel";
import { RatingTypeConfigPanel, RatingTypeConfig } from "./webtype-configs/RatingTypeConfigPanel";
import { EntityTypeConfigPanel } from "./webtype-configs/EntityTypeConfigPanel";
import { getConfigPanelComponent } from "@/lib/utils/getConfigPanelComponent";
import { ComponentData, WidgetComponent, FileComponent, WebTypeConfig, TableInfo } from "@/types/screen";
import { ButtonConfigPanel } from "./ButtonConfigPanel";
import { FileComponentConfigPanel } from "./FileComponentConfigPanel";
@@ -51,166 +24,77 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
}) => {
// 데이터베이스에서 입력 가능한 웹타입들을 동적으로 가져오기
const { webTypes } = useWebTypes({ active: "Y" });
console.log(`🔍 DetailSettingsPanel webTypes 로드됨:`, webTypes?.length, "개");
console.log(`🔍 webTypes:`, webTypes);
console.log(`🔍 DetailSettingsPanel selectedComponent:`, selectedComponent);
console.log(`🔍 DetailSettingsPanel selectedComponent.widgetType:`, selectedComponent?.widgetType);
const inputableWebTypes = webTypes.map((wt) => wt.web_type);
// 웹타입별 상세 설정 렌더링 함수
const renderWebTypeConfig = React.useCallback(
(widget: WidgetComponent) => {
const currentConfig = widget.webTypeConfig || {};
// 웹타입별 상세 설정 렌더링 함수 - useCallback 제거하여 항상 최신 widget 사용
const renderWebTypeConfig = (widget: WidgetComponent) => {
const currentConfig = widget.webTypeConfig || {};
console.log("🎨 DetailSettingsPanel renderWebTypeConfig 호출:", {
componentId: widget.id,
console.log("🎨 DetailSettingsPanel renderWebTypeConfig 호출:", {
componentId: widget.id,
widgetType: widget.widgetType,
currentConfig,
configExists: !!currentConfig,
configKeys: Object.keys(currentConfig),
configStringified: JSON.stringify(currentConfig),
widgetWebTypeConfig: widget.webTypeConfig,
widgetWebTypeConfigExists: !!widget.webTypeConfig,
timestamp: new Date().toISOString(),
});
console.log("🎨 selectedComponent 전체:", selectedComponent);
const handleConfigChange = (newConfig: WebTypeConfig) => {
console.log("🔧 WebTypeConfig 업데이트:", {
widgetType: widget.widgetType,
currentConfig,
configExists: !!currentConfig,
configKeys: Object.keys(currentConfig),
configStringified: JSON.stringify(currentConfig),
widgetWebTypeConfig: widget.webTypeConfig,
widgetWebTypeConfigExists: !!widget.webTypeConfig,
timestamp: new Date().toISOString(),
oldConfig: currentConfig,
newConfig,
componentId: widget.id,
isEqual: JSON.stringify(currentConfig) === JSON.stringify(newConfig),
});
const handleConfigChange = (newConfig: WebTypeConfig) => {
console.log("🔧 WebTypeConfig 업데이트:", {
widgetType: widget.widgetType,
oldConfig: currentConfig,
newConfig,
componentId: widget.id,
isEqual: JSON.stringify(currentConfig) === JSON.stringify(newConfig),
});
// 강제 새 객체 생성으로 React 변경 감지 보장
const freshConfig = { ...newConfig };
onUpdateProperty(widget.id, "webTypeConfig", freshConfig);
};
// 강제 새 객체 생성으로 React 변경 감지 보장
const freshConfig = { ...newConfig };
onUpdateProperty(widget.id, "webTypeConfig", freshConfig);
};
// 1순위: DB에서 지정된 설정 패널 사용
const dbWebType = webTypes.find((wt) => wt.web_type === widget.widgetType);
console.log(`🎨 웹타입 "${widget.widgetType}" DB 조회 결과:`, dbWebType);
switch (widget.widgetType) {
case "date":
case "datetime":
return (
<DateTypeConfigPanel
key={`date-config-${widget.id}`}
config={currentConfig as DateTypeConfig}
onConfigChange={handleConfigChange}
/>
);
if (dbWebType?.config_panel) {
console.log(`🎨 웹타입 "${widget.widgetType}" → DB 지정 설정 패널 "${dbWebType.config_panel}" 사용`);
const ConfigPanelComponent = getConfigPanelComponent(dbWebType.config_panel);
console.log(`🎨 getConfigPanelComponent 결과:`, ConfigPanelComponent);
case "number":
case "decimal":
return (
<NumberTypeConfigPanel
key={`${widget.id}-number`}
config={currentConfig as NumberTypeConfig}
onConfigChange={handleConfigChange}
/>
);
case "select":
case "dropdown":
return (
<SelectTypeConfigPanel
key={`${widget.id}-select`}
config={currentConfig as SelectTypeConfig}
onConfigChange={handleConfigChange}
/>
);
case "text":
case "email":
case "tel":
return (
<TextTypeConfigPanel
key={`${widget.id}-text`}
config={currentConfig as TextTypeConfig}
onConfigChange={handleConfigChange}
/>
);
case "textarea":
return (
<TextareaTypeConfigPanel
key={`${widget.id}-textarea`}
config={currentConfig as TextareaTypeConfig}
onConfigChange={handleConfigChange}
/>
);
case "checkbox":
case "boolean":
return (
<CheckboxTypeConfigPanel
key={`${widget.id}-checkbox`}
config={currentConfig as CheckboxTypeConfig}
onConfigChange={handleConfigChange}
/>
);
case "radio":
return (
<RadioTypeConfigPanel
key={`${widget.id}-radio`}
config={currentConfig as RadioTypeConfig}
onConfigChange={handleConfigChange}
/>
);
case "file":
return (
<FileTypeConfigPanel
key={`${widget.id}-file`}
config={currentConfig as FileTypeConfig}
onConfigChange={handleConfigChange}
/>
);
case "code":
return (
<CodeTypeConfigPanel
key={`${widget.id}-code`}
config={currentConfig as CodeTypeConfig}
onConfigChange={handleConfigChange}
/>
);
case "rating":
case "star":
case "score":
return (
<RatingTypeConfigPanel
key={`${widget.id}-rating`}
config={currentConfig as RatingTypeConfig}
onConfigChange={handleConfigChange}
/>
);
case "entity":
return (
<EntityTypeConfigPanel
key={`${widget.id}-entity`}
config={currentConfig as EntityTypeConfig}
onConfigChange={handleConfigChange}
/>
);
case "button":
return (
<ButtonConfigPanel
key={`${widget.id}-button`}
component={widget}
onUpdateComponent={(updates) => {
Object.entries(updates).forEach(([key, value]) => {
onUpdateProperty(widget.id, key, value);
});
}}
/>
);
default:
return <div className="text-sm text-gray-500 italic"> .</div>;
if (ConfigPanelComponent) {
console.log(`🎨 ✅ ConfigPanelComponent 렌더링 시작`);
return <ConfigPanelComponent config={currentConfig} onConfigChange={handleConfigChange} />;
} else {
console.log(`🎨 ❌ ConfigPanelComponent가 null - 기본 설정 표시`);
return (
<div className="py-8 text-center text-gray-500">
<br />
"{widget.widgetType}" .
</div>
);
}
},
[onUpdateProperty],
);
} else {
console.log(`🎨 config_panel이 없음 - 기본 설정 표시`);
return (
<div className="py-8 text-center text-gray-500">
<br />
"{widget.widgetType}" .
</div>
);
}
};
if (!selectedComponent) {
return (

View File

@@ -1,6 +1,6 @@
"use client";
import React, { useState, useEffect, useRef, useMemo } from "react";
import React, { useState, useEffect, useRef } from "react";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
@@ -20,7 +20,6 @@ import {
TableInfo,
} from "@/types/screen";
import DataTableConfigPanel from "./DataTableConfigPanel";
import { DynamicConfigPanel } from "@/lib/registry";
import { useWebTypes } from "@/hooks/admin/useWebTypes";
// DataTableConfigPanel을 위한 안정화된 래퍼 컴포넌트
@@ -351,39 +350,6 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
</select>
</div>
{/* 동적 웹타입 설정 패널 */}
{selectedComponent.widgetType && (
<div className="space-y-3">
<Separator />
<div>
<Label className="text-sm font-medium"> </Label>
<div className="mt-2">
<DynamicConfigPanel
webType={selectedComponent.widgetType}
component={selectedComponent}
onUpdateComponent={(updatedComponent) => {
// 컴포넌트 전체 업데이트
Object.keys(updatedComponent).forEach((key) => {
if (key !== "id") {
onUpdateProperty(key, updatedComponent[key]);
}
});
}}
onUpdateProperty={(property, value) => {
// 웹타입 설정 업데이트
if (property === "webTypeConfig") {
onUpdateProperty("webTypeConfig", value);
} else {
onUpdateProperty(property, value);
}
}}
/>
</div>
</div>
<Separator />
</div>
)}
<div>
<Label htmlFor="placeholder" className="text-sm font-medium">

View File

@@ -12,6 +12,7 @@ export interface WebTypeStandard {
description?: string;
category: string;
component_name?: string;
config_panel?: string;
default_config?: any;
validation_rules?: any;
default_style?: any;
@@ -32,6 +33,7 @@ export interface WebTypeFormData {
description?: string;
category: string;
component_name?: string;
config_panel?: string;
default_config?: any;
validation_rules?: any;
default_style?: any;

View File

@@ -1,58 +1,101 @@
"use client";
import React, { useMemo } from "react";
import { WebTypeRegistry } from "./WebTypeRegistry";
import { WebTypeConfigPanelProps } from "./types";
import { useWebTypes } from "@/hooks/admin/useWebTypes";
import { getConfigPanelComponent } from "@/lib/utils/getConfigPanelComponent";
/**
* 동적 설정 패널 렌더러 컴포넌트
* 레지스트리에서 웹타입을 조회하여 해당 설정 패널을 동적으로 렌더링합니다.
* DB에서 웹타입 설정을 조회하여 해당 설정 패널을 동적으로 렌더링합니다.
*/
export const DynamicConfigPanel: React.FC<
WebTypeConfigPanelProps & {
webType: string;
}
> = ({ webType, component, onUpdateComponent, onUpdateProperty }) => {
// 레지스트리에서 웹타입 정의 조회
const webTypeDefinition = useMemo(() => {
return WebTypeRegistry.getWebType(webType);
}, [webType]);
// DB에서 웹타입 목록 조회
const { webTypes } = useWebTypes({ active: "Y" });
// 웹타입이 등록되지 않은 경우
if (!webTypeDefinition) {
console.warn(`웹타입 "${webType}"이 레지스트리에 등록되지 않았습니다.`);
console.log(`🔧 DynamicConfigPanel: 웹타입 "${webType}" 설정 패널 로드 시작`);
console.log(`🔧 DynamicConfigPanel: webTypes 로드됨`, webTypes?.length, "개");
// DB에서 웹타입 정보 조회
const dbWebType = useMemo(() => {
if (!webTypes) return null;
const found = webTypes.find((wt) => wt.web_type === webType);
console.log(`🔧 DynamicConfigPanel: 웹타입 "${webType}" DB 조회 결과:`, found);
return found;
}, [webTypes, webType]);
// 웹타입이 DB에 없는 경우
if (!webTypes) {
return (
<div className="rounded-md border border-dashed border-gray-300 bg-gray-50 p-4">
<div className="flex items-center gap-2 text-gray-600">
<span className="text-sm font-medium"> ...</span>
</div>
<p className="mt-1 text-xs text-gray-500"> .</p>
</div>
);
}
if (!dbWebType) {
console.warn(`웹타입 "${webType}"이 데이터베이스에 등록되지 않았습니다.`);
return (
<div className="rounded-md border border-dashed border-red-300 bg-red-50 p-4">
<div className="flex items-center gap-2 text-red-600">
<span className="text-sm font-medium"> </span>
<span className="text-sm font-medium"> </span>
</div>
<p className="mt-1 text-xs text-red-500"> "{webType}" .</p>
<p className="mt-1 text-xs text-red-500"> "{webType}" .</p>
</div>
);
}
// 설정 패널 컴포넌트가 없는 경우
if (!webTypeDefinition.configPanel) {
// DB에서 설정 패널 조회
const configPanelName = dbWebType.config_panel;
console.log(`🔧 DynamicConfigPanel: config_panel="${configPanelName}"`);
// 설정 패널이 지정되지 않은 경우
if (!configPanelName || configPanelName === "none") {
console.log(`🔧 DynamicConfigPanel: 설정 패널이 없음 - 기본 설정 표시`);
return (
<div className="rounded-md border border-dashed border-yellow-300 bg-yellow-50 p-4">
<div className="flex items-center gap-2 text-yellow-600">
<span className="text-sm font-medium"> </span>
<span className="text-sm font-medium"> </span>
</div>
<p className="mt-1 text-xs text-yellow-500"> "{webType}" .</p>
<p className="mt-1 text-xs text-yellow-500"> "{webType}" .</p>
</div>
);
}
const ConfigPanelComponent = webTypeDefinition.configPanel;
// 설정 패널 컴포넌트 조회
const ConfigPanelComponent = getConfigPanelComponent(configPanelName);
console.log(`🔧 DynamicConfigPanel: ConfigPanelComponent=`, ConfigPanelComponent);
if (!ConfigPanelComponent) {
console.warn(`설정 패널 "${configPanelName}"을 찾을 수 없습니다.`);
return (
<div className="rounded-md border border-dashed border-red-300 bg-red-50 p-4">
<div className="flex items-center gap-2 text-red-600">
<span className="text-sm font-medium"> </span>
</div>
<p className="mt-1 text-xs text-red-500"> "{configPanelName}" .</p>
</div>
);
}
// 설정 패널 props 구성
const configPanelProps: WebTypeConfigPanelProps = {
component,
onUpdateComponent,
onUpdateProperty,
const configPanelProps = {
config: component.webTypeConfig || {},
onConfigChange: (newConfig: any) => {
console.log(`🔧 DynamicConfigPanel: 설정 변경`, newConfig);
onUpdateProperty("webTypeConfig", newConfig);
},
};
try {
console.log(`🔧 DynamicConfigPanel: "${configPanelName}" 컴포넌트 렌더링 시작`);
return <ConfigPanelComponent {...configPanelProps} />;
} catch (error) {
console.error(`웹타입 "${webType}" 설정 패널 렌더링 중 오류 발생:`, error);
@@ -95,18 +138,18 @@ export function renderConfigPanel(
/**
* 웹타입이 설정 패널을 지원하는지 확인하는 헬퍼 함수
* @deprecated DB 기반 동적 시스템으로 대체됨. DynamicConfigPanel을 직접 사용하세요.
*/
export function hasConfigPanel(webType: string): boolean {
const webTypeDefinition = WebTypeRegistry.getWebType(webType);
return !!(webTypeDefinition && webTypeDefinition.configPanel);
console.warn("hasConfigPanel은 deprecated됨. DynamicConfigPanel을 직접 사용하세요.");
return false;
}
/**
* 웹타입의 기본 설정을 가져오는 헬퍼 함수
* @deprecated DB 기반 동적 시스템으로 대체됨. 각 설정 패널에서 직접 관리합니다.
*/
export function getDefaultConfig(webType: string): Record<string, any> | null {
const webTypeDefinition = WebTypeRegistry.getWebType(webType);
return webTypeDefinition ? webTypeDefinition.defaultConfig : null;
console.warn("getDefaultConfig는 deprecated됨. 각 설정 패널에서 직접 기본값을 관리하세요.");
return null;
}

View File

@@ -0,0 +1,22 @@
// 사용 가능한 설정 패널 목록
export const AVAILABLE_CONFIG_PANELS = [
{ value: "none", label: "기본 설정", description: "기본 설정 패널 (설정 없음)" },
{ value: "DateTypeConfigPanel", label: "날짜 설정", description: "날짜/시간 필드 설정" },
{ value: "NumberTypeConfigPanel", label: "숫자 설정", description: "숫자 입력 필드 설정" },
{ value: "SelectTypeConfigPanel", label: "선택 설정", description: "드롭다운/선택 필드 설정" },
{ value: "TextTypeConfigPanel", label: "텍스트 설정", description: "텍스트 입력 필드 설정" },
{ value: "TextareaTypeConfigPanel", label: "텍스트영역 설정", description: "여러 줄 텍스트 설정" },
{ value: "CheckboxTypeConfigPanel", label: "체크박스 설정", description: "체크박스 필드 설정" },
{ value: "RadioTypeConfigPanel", label: "라디오 설정", description: "라디오 버튼 설정" },
{ value: "FileTypeConfigPanel", label: "파일 설정", description: "파일 업로드 설정" },
{ value: "CodeTypeConfigPanel", label: "코드 설정", description: "공통코드 선택 설정" },
{ value: "EntityTypeConfigPanel", label: "엔티티 설정", description: "엔티티 참조 설정" },
{ value: "RatingTypeConfigPanel", label: "별점 설정", description: "별점 평가 설정" },
] as const;
export type ConfigPanelName = (typeof AVAILABLE_CONFIG_PANELS)[number]["value"];
// 설정 패널 정보 조회 함수
export const getConfigPanelInfo = (panelName: string) => {
return AVAILABLE_CONFIG_PANELS.find((panel) => panel.value === panelName);
};

View File

@@ -0,0 +1,61 @@
// 설정 패널 컴포넌트 동적 매핑
import React from "react";
import { DateTypeConfigPanel } from "@/components/screen/panels/webtype-configs/DateTypeConfigPanel";
import { NumberTypeConfigPanel } from "@/components/screen/panels/webtype-configs/NumberTypeConfigPanel";
import { SelectTypeConfigPanel } from "@/components/screen/panels/webtype-configs/SelectTypeConfigPanel";
import { TextTypeConfigPanel } from "@/components/screen/panels/webtype-configs/TextTypeConfigPanel";
import { TextareaTypeConfigPanel } from "@/components/screen/panels/webtype-configs/TextareaTypeConfigPanel";
import { CheckboxTypeConfigPanel } from "@/components/screen/panels/webtype-configs/CheckboxTypeConfigPanel";
import { RadioTypeConfigPanel } from "@/components/screen/panels/webtype-configs/RadioTypeConfigPanel";
import { FileTypeConfigPanel } from "@/components/screen/panels/webtype-configs/FileTypeConfigPanel";
import { CodeTypeConfigPanel } from "@/components/screen/panels/webtype-configs/CodeTypeConfigPanel";
import { EntityTypeConfigPanel } from "@/components/screen/panels/webtype-configs/EntityTypeConfigPanel";
import { RatingTypeConfigPanel } from "@/components/screen/panels/webtype-configs/RatingTypeConfigPanel";
// 설정 패널 컴포넌트 타입
export type ConfigPanelComponent = React.ComponentType<{
config: any;
onConfigChange: (config: any) => void;
}>;
// 설정 패널 이름으로 직접 매핑하는 함수 (DB의 config_panel 필드용)
export const getConfigPanelComponent = (panelName: string): ConfigPanelComponent | null => {
console.log(`🔧 getConfigPanelComponent 호출: panelName="${panelName}"`);
// "none"이나 빈 값은 null 반환 (기본 설정)
if (!panelName || panelName === "none") {
console.log(`🔧 빈 값 또는 "none" → null 반환`);
return null;
}
switch (panelName) {
case "DateTypeConfigPanel":
return DateTypeConfigPanel;
case "NumberTypeConfigPanel":
return NumberTypeConfigPanel;
case "SelectTypeConfigPanel":
return SelectTypeConfigPanel;
case "TextTypeConfigPanel":
return TextTypeConfigPanel;
case "TextareaTypeConfigPanel":
return TextareaTypeConfigPanel;
case "CheckboxTypeConfigPanel":
return CheckboxTypeConfigPanel;
case "RadioTypeConfigPanel":
return RadioTypeConfigPanel;
case "FileTypeConfigPanel":
return FileTypeConfigPanel;
case "CodeTypeConfigPanel":
return CodeTypeConfigPanel;
case "EntityTypeConfigPanel":
return EntityTypeConfigPanel;
case "RatingTypeConfigPanel":
console.log(`🔧 RatingTypeConfigPanel 컴포넌트 반환`);
console.log(`🔧 RatingTypeConfigPanel 타입:`, typeof RatingTypeConfigPanel);
console.log(`🔧 RatingTypeConfigPanel 내용:`, RatingTypeConfigPanel);
return RatingTypeConfigPanel;
default:
console.warn(`🔧 알 수 없는 설정 패널: ${panelName}, 기본 설정 사용`);
return null; // 기본 설정 (패널 없음)
}
};