diff --git a/backend-node/prisma/schema.prisma b/backend-node/prisma/schema.prisma
index 9944035f..421ecbcb 100644
--- a/backend-node/prisma/schema.prisma
+++ b/backend-node/prisma/schema.prisma
@@ -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?
diff --git a/backend-node/src/controllers/webTypeStandardController.ts b/backend-node/src/controllers/webTypeStandardController.ts
index de3cea09..a62e892e 100644
--- a/backend-node/src/controllers/webTypeStandardController.ts
+++ b/backend-node/src/controllers/webTypeStandardController.ts
@@ -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({
diff --git a/frontend/app/(main)/admin/standards/[webType]/edit/page.tsx b/frontend/app/(main)/admin/standards/[webType]/edit/page.tsx
index 9ab967cd..be7dd3f5 100644
--- a/frontend/app/(main)/admin/standards/[webType]/edit/page.tsx
+++ b/frontend/app/(main)/admin/standards/[webType]/edit/page.tsx
@@ -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() {
)}
+ {/* 설정 패널 */}
+
+
+
+ {formData.config_panel && (
+
+
+
+
+ 현재 선택: {getConfigPanelInfo(formData.config_panel)?.label || formData.config_panel}
+
+
+ {getConfigPanelInfo(formData.config_panel)?.description && (
+
+ {getConfigPanelInfo(formData.config_panel)?.description}
+
+ )}
+
+ )}
+
+
{/* 설명 */}
diff --git a/frontend/app/(main)/admin/standards/new/page.tsx b/frontend/app/(main)/admin/standards/new/page.tsx
index 95a069dd..77df8a74 100644
--- a/frontend/app/(main)/admin/standards/new/page.tsx
+++ b/frontend/app/(main)/admin/standards/new/page.tsx
@@ -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() {
)}
+ {/* 설정 패널 */}
+
+
+
+ {formData.config_panel && (
+
+
+
+
+ 현재 선택: {getConfigPanelInfo(formData.config_panel)?.label || formData.config_panel}
+
+
+ {getConfigPanelInfo(formData.config_panel)?.description && (
+
+ {getConfigPanelInfo(formData.config_panel)?.description}
+
+ )}
+
+ )}
+
+
{/* 설명 */}
diff --git a/frontend/app/(main)/admin/standards/page.tsx b/frontend/app/(main)/admin/standards/page.tsx
index 8f683ee0..c21266ab 100644
--- a/frontend/app/(main)/admin/standards/page.tsx
+++ b/frontend/app/(main)/admin/standards/page.tsx
@@ -245,6 +245,13 @@ export default function WebTypesManagePage() {
(sortDirection === "asc" ? : )}
+ handleSort("config_panel")}>
+
+ 설정 패널
+ {sortField === "config_panel" &&
+ (sortDirection === "asc" ? : )}
+
+
handleSort("is_active")}>
상태
@@ -265,7 +272,7 @@ export default function WebTypesManagePage() {
{filteredAndSortedWebTypes.length === 0 ? (
-
+
조건에 맞는 웹타입이 없습니다.
@@ -289,6 +296,11 @@ export default function WebTypesManagePage() {
{webType.component_name || "TextWidget"}
+
+
+ {webType.config_panel === "none" || !webType.config_panel ? "기본 설정" : webType.config_panel}
+
+
{webType.is_active === "Y" ? "활성화" : "비활성화"}
diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx
index 43436307..b7894863 100644
--- a/frontend/components/screen/ScreenDesigner.tsx
+++ b/frontend/components/screen/ScreenDesigner.tsx
@@ -3063,6 +3063,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
autoHeight={true}
>
{
updateComponentProperty(componentId, path, value);
diff --git a/frontend/components/screen/panels/DetailSettingsPanel.tsx b/frontend/components/screen/panels/DetailSettingsPanel.tsx
index 37f8c94e..0ae34682 100644
--- a/frontend/components/screen/panels/DetailSettingsPanel.tsx
+++ b/frontend/components/screen/panels/DetailSettingsPanel.tsx
@@ -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 = ({
}) => {
// 데이터베이스에서 입력 가능한 웹타입들을 동적으로 가져오기
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 (
-
- );
+ 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 (
-
- );
-
- case "select":
- case "dropdown":
- return (
-
- );
-
- case "text":
- case "email":
- case "tel":
- return (
-
- );
-
- case "textarea":
- return (
-
- );
-
- case "checkbox":
- case "boolean":
- return (
-
- );
-
- case "radio":
- return (
-
- );
-
- case "file":
- return (
-
- );
-
- case "code":
- return (
-
- );
-
- case "rating":
- case "star":
- case "score":
- return (
-
- );
-
- case "entity":
- return (
-
- );
-
- case "button":
- return (
- {
- Object.entries(updates).forEach(([key, value]) => {
- onUpdateProperty(widget.id, key, value);
- });
- }}
- />
- );
-
- default:
- return 해당 웹타입의 상세 설정이 지원되지 않습니다.
;
+ if (ConfigPanelComponent) {
+ console.log(`🎨 ✅ ConfigPanelComponent 렌더링 시작`);
+ return ;
+ } else {
+ console.log(`🎨 ❌ ConfigPanelComponent가 null - 기본 설정 표시`);
+ return (
+
+ ⚙️ 기본 설정
+
+ 웹타입 "{widget.widgetType}"은 추가 설정이 없습니다.
+
+ );
}
- },
- [onUpdateProperty],
- );
+ } else {
+ console.log(`🎨 config_panel이 없음 - 기본 설정 표시`);
+ return (
+
+ ⚙️ 기본 설정
+
+ 웹타입 "{widget.widgetType}"은 추가 설정이 없습니다.
+
+ );
+ }
+ };
if (!selectedComponent) {
return (
diff --git a/frontend/components/screen/panels/PropertiesPanel.tsx b/frontend/components/screen/panels/PropertiesPanel.tsx
index 8664f7bd..13c44181 100644
--- a/frontend/components/screen/panels/PropertiesPanel.tsx
+++ b/frontend/components/screen/panels/PropertiesPanel.tsx
@@ -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 = ({
- {/* 동적 웹타입 설정 패널 */}
- {selectedComponent.widgetType && (
-
-
-
-
-
- {
- // 컴포넌트 전체 업데이트
- 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);
- }
- }}
- />
-
-
-
-
- )}
-