diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 76253fa0..1b22a78c 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -4072,21 +4072,14 @@ export default function ScreenDesigner({ // 웹타입별 기본 비율 매핑 (12컬럼 기준 비율) const gridColumnsRatioMap: Record = { // 입력 컴포넌트 (INPUT 카테고리) - "text-input": 4 / 12, // 텍스트 입력 (33%) - "number-input": 2 / 12, // 숫자 입력 (16.67%) + // 텍스트/숫자/날짜/textarea/select/checkbox 는 canonical input 으로 흡수됨 (Phase E). + // radio-basic / toggle-switch 는 Phase F.1 에서 canonical input 으로 흡수됨. "email-input": 4 / 12, // 이메일 입력 (33%) "tel-input": 3 / 12, // 전화번호 입력 (25%) - "date-input": 3 / 12, // 날짜 입력 (25%) "datetime-input": 4 / 12, // 날짜시간 입력 (33%) "time-input": 2 / 12, // 시간 입력 (16.67%) - "textarea-basic": 6 / 12, // 텍스트 영역 (50%) - "select-basic": 3 / 12, // 셀렉트 (25%) - "checkbox-basic": 2 / 12, // 체크박스 (16.67%) - "radio-basic": 3 / 12, // 라디오 (25%) "file-basic": 4 / 12, // 파일 (33%) "file-upload": 4 / 12, // 파일 업로드 (33%) - "slider-basic": 3 / 12, // 슬라이더 (25%) - "toggle-switch": 2 / 12, // 토글 스위치 (16.67%) "repeater-field-group": 6 / 12, // 반복 필드 그룹 (50%) // 표시 컴포넌트 (DISPLAY 카테고리) diff --git a/frontend/components/screen/config-panels/button-config/ActionTab.tsx b/frontend/components/screen/config-panels/button-config/ActionTab.tsx index 878b9082..03708d07 100644 --- a/frontend/components/screen/config-panels/button-config/ActionTab.tsx +++ b/frontend/components/screen/config-panels/button-config/ActionTab.tsx @@ -3181,7 +3181,7 @@ export const ActionTab: React.FC = ({ .filter((comp: any) => { const type = comp.componentType || comp.type || ""; // 소스/타겟과 다른 컴포넌트 중 값을 제공할 수 있는 타입 - return ["conditional-container", "select-basic", "select", "combobox"].some((t) => + return ["conditional-container", "select", "combobox"].some((t) => type.includes(t), ); }) diff --git a/frontend/components/screen/config-panels/button/DataTab.tsx b/frontend/components/screen/config-panels/button/DataTab.tsx index 43cba580..44ab13a5 100644 --- a/frontend/components/screen/config-panels/button/DataTab.tsx +++ b/frontend/components/screen/config-panels/button/DataTab.tsx @@ -387,7 +387,7 @@ export const DataTab: React.FC = ({ {allComponents .filter((comp: any) => { const type = comp.componentType || comp.type || ""; - return ["conditional-container", "select-basic", "select", "combobox"].some((t) => + return ["conditional-container", "select", "combobox"].some((t) => type.includes(t), ); }) diff --git a/frontend/components/screen/modals/MultilangSettingsModal.tsx b/frontend/components/screen/modals/MultilangSettingsModal.tsx index a1f62b82..c6c2f916 100644 --- a/frontend/components/screen/modals/MultilangSettingsModal.tsx +++ b/frontend/components/screen/modals/MultilangSettingsModal.tsx @@ -73,10 +73,8 @@ interface LangText { } // 입력 가능한 폼 컴포넌트 타입 목록 +// text/number/date 등 6종 component id 는 Phase E 에서 canonical "input" 으로 흡수. const INPUT_COMPONENT_TYPES = new Set([ - "text-input", - "number-input", - "date-input", "datetime-input", "select-input", "textarea-input", diff --git a/frontend/components/screen/panels/ComponentsPanel.tsx b/frontend/components/screen/panels/ComponentsPanel.tsx index 00715508..36e954d6 100644 --- a/frontend/components/screen/panels/ComponentsPanel.tsx +++ b/frontend/components/screen/panels/ComponentsPanel.tsx @@ -96,11 +96,8 @@ export function ComponentsPanel({ const componentsByCategory = useMemo(() => { // 숨길 컴포넌트 ID 목록 const hiddenComponents = [ - // 기본 입력 컴포넌트 (테이블 컬럼 드래그 시 자동 생성) - "text-input", - "number-input", - "date-input", - "textarea-basic", + // 기본 입력 6종 (text-input/number-input/date-input/select-basic/ + // checkbox-basic/textarea-basic) — Phase E 에서 canonical input 으로 흡수, 등록/폴더 모두 삭제. // canonical input 으로 대체됨 (Phase D.4) "image-widget", // → canonical input (type='file', format='image') "file-upload", // → canonical input (type='file', format='file') @@ -127,8 +124,7 @@ export function ComponentsPanel({ "accordion-basic", // 아코디언 컴포넌트 "conditional-container", // 조건부 컨테이너 "universal-form-modal", // 범용 폼 모달 - // 옛 v2-media — Phase D.4 에서 canonical input 으로 흡수. hidden 유지 (생성 차단) - "v2-media", + // v2-media — Phase D.4 에서 canonical input 으로 흡수, 폴더/렌더러 삭제. // 플로우 위젯 숨김 처리 "flow-widget", // 선택 항목 상세입력 - 거래처 품목 추가 등에서 사용 @@ -158,19 +154,13 @@ export function ComponentsPanel({ // ★ 2026-04-11 통합 컴포넌트(Phase B-1): 필드 입력 20+종 → `input` // (V2 입력/선택은 Phase D.2 에서 완전 폐기 — 등록/생성 경로 자체 삭제, hidden 목록에 둘 필요 없음) "v2-category-manager", // → input (type='select', 추후 category 특화) - "v2-file-upload", // → canonical input (type='file', Phase D.4) - // v2-media 는 이미 위에서 hidden 처리됨 - // v2-numbering-rule: 폐기 (2026-05-11). admin 페이지 /admin/systemMng/numberingRuleList 로 대체 + // v2-file-upload / v2-media / v2-numbering-rule 는 Phase D.4 / D.5 / 2026-05-11 + // 폐기. 폴더/렌더러 삭제 — hidden 목록에 둘 필요 없음. "v2-location-swap-selector", // → input (type='entity') - // 아래 legacy 들은 이미 상단 "기본 입력 컴포넌트" 섹션에서 hidden: - // text-input, number-input, date-input, textarea-basic, image-widget, - // entity-search-input, autocomplete-search-input, file-upload (일부) - // 이미 리스트에 없는 것만 추가: - "select-basic", // → input (type='select') - "checkbox-basic", // → input (type='checkbox') - "radio-basic", // → input (type='select', radio 렌더) - "toggle-switch", // → input (type='checkbox', toggle 렌더) - "slider-basic", // → input (type='number', slider 렌더) + // 아래 legacy 들은 이미 상단 섹션에서 hidden / 또는 Phase E·F.1 에서 폴더 삭제: + // text-input, number-input, date-input, textarea-basic, select-basic, checkbox-basic + // radio-basic, toggle-switch (Phase F.1) + // image-widget, entity-search-input, autocomplete-search-input, file-upload (일부) // ★ 2026-04-11 통합 컴포넌트(Phase B-2): 통계/KPI → `stats` "v2-aggregation-widget", // → stats "v2-status-count", // → stats diff --git a/frontend/components/screen/panels/V2PropertiesPanel.tsx b/frontend/components/screen/panels/V2PropertiesPanel.tsx index cc64ab15..0abb33a8 100644 --- a/frontend/components/screen/panels/V2PropertiesPanel.tsx +++ b/frontend/components/screen/panels/V2PropertiesPanel.tsx @@ -394,15 +394,11 @@ export const V2PropertiesPanel: React.FC = ({ "boolean", "file", "autocomplete", - "text-input", - "number-input", - "date-input", - "textarea-basic", - "select-basic", - "checkbox-basic", - "radio-basic", "entity-search-input", "autocomplete-search-input", + // 입력 6종(text/number/date/select/checkbox/textarea) 은 Phase E, + // radio-basic/toggle-switch 는 Phase F.1 에서 canonical input 으로 흡수, + // 목록에서 제거됨. // 새로운 통합 입력 컴포넌트 (옛 V2 입력/선택은 input canonical 로 흡수되어 목록에서 제거됨) "input", "v2-entity-select", diff --git a/frontend/components/screen/panels/WebTypeConfigPanel.tsx b/frontend/components/screen/panels/WebTypeConfigPanel.tsx index 4351d5db..7e684a67 100644 --- a/frontend/components/screen/panels/WebTypeConfigPanel.tsx +++ b/frontend/components/screen/panels/WebTypeConfigPanel.tsx @@ -23,7 +23,7 @@ export const WebTypeConfigPanel: React.FC = ({ web_type // webType을 소문자로 변환하고 기본 타입 추출 const normalizedWebType = String(webType || "").toLowerCase(); - // 기본 타입 추출 (예: "radio-horizontal" -> "radio", "checkbox-basic" -> "checkbox") + // 기본 타입 추출 (예: "radio-horizontal" -> "radio", "select-multi" -> "select") const getBaseType = (type: string): string => { if (type.startsWith("radio")) return "radio"; if (type.startsWith("checkbox")) return "checkbox"; diff --git a/frontend/components/v2/config-panels/InvFieldConfigPanel.tsx b/frontend/components/v2/config-panels/InvFieldConfigPanel.tsx index 7c96316e..1f76ed61 100644 --- a/frontend/components/v2/config-panels/InvFieldConfigPanel.tsx +++ b/frontend/components/v2/config-panels/InvFieldConfigPanel.tsx @@ -98,12 +98,14 @@ const FORMATS_BY_TYPE: Record = { { id: "address", name: "주소", desc: "우편번호·지도", icon: "📍" }, { id: "card", name: "카드번호", desc: "PCI 마스킹", icon: "💳" }, { id: "url", name: "URL", desc: "링크", icon: "🔗" }, + { id: "color", name: "색상", desc: "#RRGGBB", icon: "🎨" }, { id: "mask", name: "커스텀 마스킹", desc: "###-####", icon: "⌨" }, ], number: [ - { id: "int", name: "정수", desc: "0, 1, 2…", icon: "#" }, - { id: "decimal", name: "소수", desc: "1.23", icon: "0.0" }, - { id: "percent", name: "퍼센트", desc: "0~100%", icon: "%" }, + { id: "int", name: "정수", desc: "0, 1, 2…", icon: "#" }, + { id: "decimal", name: "소수", desc: "1.23", icon: "0.0" }, + { id: "percent", name: "퍼센트", desc: "0~100%", icon: "%" }, + { id: "slider", name: "슬라이더", desc: "min~max", icon: "⇄" }, ], money: [ { id: "krw", name: "원", desc: "KRW", icon: "₩" }, @@ -253,16 +255,8 @@ function resolveTriple( return { kind: "attach", type: "file", format: "any" }; } - // 2.5 옛 레거시 컴포넌트 ID → triple default - // config.kind/type 가 없을 때만 도달. 명시된 config 가 있으면 step 0 에서 이미 return. - switch (componentType) { - case "text-input": return { kind: "input", type: "text", format: "free" }; - case "number-input": return { kind: "input", type: "number", format: "int" }; - case "date-input": return { kind: "input", type: "date", format: "date" }; - case "select-basic": return { kind: "choice", type: "single", format: "list" }; - case "checkbox-basic": return { kind: "choice", type: "single", format: "boolean" }; - case "textarea-basic": return { kind: "input", type: "text", format: "free" }; - } + // 2.5 옛 입력 6종 (text-input/number-input/date-input/select-basic/checkbox-basic/ + // textarea-basic) 은 Phase E 에서 canonical input 으로 흡수 — 레거시 매핑 제거. // 3. 선택 (source / multiple) const isMulti = !!config.multiple; diff --git a/frontend/lib/registry/ComponentRegistry.ts b/frontend/lib/registry/ComponentRegistry.ts index 43a42bb0..9c4851db 100644 --- a/frontend/lib/registry/ComponentRegistry.ts +++ b/frontend/lib/registry/ComponentRegistry.ts @@ -386,7 +386,7 @@ Hot Reload 제어 (비동기): 💡 사용 예시: __COMPONENT_REGISTRY__.search("input") __COMPONENT_REGISTRY__.byCategory("input") - __COMPONENT_REGISTRY__.get("text-input") + __COMPONENT_REGISTRY__.get("input") `); }, }; diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index a54e086b..3bc61e1e 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -372,10 +372,8 @@ export const DynamicComponentRenderer: React.FC = "v2-button-primary": "button", "button-primary": "button", // search "v2-table-search-widget": "search", "table-search-widget": "search", - // input (V2 입력/선택은 Phase D.2 에서 완전 폐기 — alias 제거) - "text-input": "input", "number-input": "input", "date-input": "input", - "select-basic": "input", "checkbox-basic": "input", "textarea-basic": "input", - "slider-basic": "input", "radio-basic": "input", "toggle-switch": "input", + // input (V2 입력/선택 Phase D.2, 입력 6종 Phase E, radio-basic/toggle-switch + // Phase F.1 폐기 — alias 모두 제거) // stats "v2-aggregation-widget": "stats", "aggregation-widget": "stats", "v2-status-count": "stats", diff --git a/frontend/lib/registry/DynamicWebTypeRenderer.tsx b/frontend/lib/registry/DynamicWebTypeRenderer.tsx index dc912b8b..c816bd13 100644 --- a/frontend/lib/registry/DynamicWebTypeRenderer.tsx +++ b/frontend/lib/registry/DynamicWebTypeRenderer.tsx @@ -167,25 +167,31 @@ export const DynamicWebTypeRenderer: React.FC = ({ // 파일 웹타입 — 위 통합 분기에서 처리됨. fallback 도 canonical input 으로 일원화. - // 텍스트 입력 웹타입들 - if (["text", "email", "password", "tel"].includes(webType)) { - const { TextInputComponent } = require("@/lib/registry/components/text-input/TextInputComponent"); - // console.log(`✅ 폴백: ${webType} 웹타입 → TextInputComponent 사용`); - return ; - } - - // 숫자 입력 웹타입들 - if (["number", "decimal"].includes(webType)) { - const { NumberInputComponent } = require("@/lib/registry/components/number-input/NumberInputComponent"); - // console.log(`✅ 폴백: ${webType} 웹타입 → NumberInputComponent 사용`); - return ; - } - - // 날짜 입력 웹타입들 - if (["date", "datetime", "time"].includes(webType)) { - const { DateInputComponent } = require("@/lib/registry/components/date-input/DateInputComponent"); - // console.log(`✅ 폴백: ${webType} 웹타입 → DateInputComponent 사용`); - return ; + // 텍스트 / 숫자 / 날짜 / 시간 입력 — Phase E (2026-05-12) 부터 canonical input 으로 라우팅. + // 옛 text-input / number-input / date-input 폴더는 흡수 후 삭제되었다. + const TEXT_LIKE = ["text", "email", "password", "tel", "url"]; + const NUMBER_LIKE = ["number", "decimal"]; + const DATE_LIKE = ["date", "datetime", "time", "daterange"]; + if ( + TEXT_LIKE.includes(webType) || + NUMBER_LIKE.includes(webType) || + DATE_LIKE.includes(webType) + ) { + const { InputComponent } = require("@/lib/registry/components/input/InputComponent"); + const inputCfg: Record = NUMBER_LIKE.includes(webType) + ? { type: "number", format: webType === "decimal" ? "decimal" : "int" } + : DATE_LIKE.includes(webType) + ? { type: webType === "daterange" ? "daterange" : (webType as "date" | "datetime" | "time") } + : { + type: "text", + format: + webType === "email" ? "email" : + webType === "password" ? "password" : + webType === "tel" ? "phone" : + webType === "url" ? "url" : + "free", + }; + return ; } // 카테고리 셀렉트 웹타입 @@ -197,7 +203,6 @@ export const DynamicWebTypeRenderer: React.FC = ({ // 기본 폴백: Input 컴포넌트 사용 const { Input } = require("@/components/ui/input"); - const { filterDOMProps } = require("@/lib/utils/domPropsFilter"); // console.log(`✅ 폴백: ${webType} 웹타입 → 기본 Input 사용`); const safeFallbackProps = filterDOMProps(props); return ; diff --git a/frontend/lib/registry/components/category-manager.tsx b/frontend/lib/registry/components/category-manager.tsx deleted file mode 100644 index 1c8c01aa..00000000 --- a/frontend/lib/registry/components/category-manager.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { ComponentDefinition, ComponentCategory } from "@/types/component"; -import { FolderTree } from "lucide-react"; -import { CategoryWidget } from "@/components/screen/widgets/CategoryWidget"; - -/** - * 카테고리 관리 컴포넌트 정의 - * - 메뉴 스코프 기반 카테고리 값 관리 - * - 좌우 분할 UI (컬럼 목록 + 값 관리) - */ -export const categoryManagerDefinition = { - // 기본 정보 - id: "category-manager", - name: "카테고리 관리", - name_eng: "Category Manager", - description: "메뉴 스코프 기반 카테고리 값 관리 (좌우 분할 UI)", - category: ComponentCategory.DISPLAY, - web_type: "category" as any, - - // 컴포넌트 - component: CategoryWidget, - - // 아이콘 - icon: FolderTree, - - // 기본 설정 - default_config: {}, - - // 기본 크기 - default_size: { - width: 1000, - height: 600, - }, - - // 태그 - tags: ["category", "reference", "manager", "scope"], - - // 작성자 - author: "system", - - // 속성 - properties: { - menuId: { - type: "number", - label: "메뉴 ID", - description: "현재 화면의 메뉴 ID (자동 설정)", - required: true, - }, - tableName: { - type: "string", - label: "테이블명", - description: "현재 화면의 테이블명 (자동 설정)", - required: true, - }, - }, - - // 특징 - features: [ - "메뉴 스코프 기반 카테고리 관리", - "좌우 분할 UI (컬럼 목록 + 값 관리)", - "실시간 검색 및 필터링", - "CRUD 기능 (추가, 수정, 삭제)", - "색상 및 아이콘 설정", - "계층 구조 지원 (부모-자식)", - ], - - // 제약사항 - constraints: { - minSize: { width: 800, height: 400 }, - maxSize: { width: 1400, height: 1000 }, - }, -} as ComponentDefinition; - diff --git a/frontend/lib/registry/components/checkbox-basic/CheckboxBasicComponent.tsx b/frontend/lib/registry/components/checkbox-basic/CheckboxBasicComponent.tsx deleted file mode 100644 index 7f90a15c..00000000 --- a/frontend/lib/registry/components/checkbox-basic/CheckboxBasicComponent.tsx +++ /dev/null @@ -1,186 +0,0 @@ -"use client"; - -import React, { useState } from "react"; -import { ComponentRendererProps } from "@/types/component"; -import { CheckboxBasicConfig } from "./types"; -import { cn } from "@/lib/registry/components/common/inputStyles"; -import { filterDOMProps } from "@/lib/utils/domPropsFilter"; - -export interface CheckboxBasicComponentProps extends ComponentRendererProps { - config?: CheckboxBasicConfig; -} - -/** - * CheckboxBasic 컴포넌트 - * checkbox-basic 컴포넌트입니다 - */ -export const CheckboxBasicComponent: React.FC = ({ - component, - isDesignMode = false, - isSelected = false, - isInteractive = false, - onClick, - onDragStart, - onDragEnd, - config, - className, - style, - formData, - onFormDataChange, - ...props -}) => { - // 컴포넌트 설정 - const componentConfig = { - ...config, - ...component.config, - } as CheckboxBasicConfig; - - // webType에 따른 세부 타입 결정 (TextInputComponent와 동일한 방식) - const webType = component.componentConfig?.webType || "checkbox"; - - // 상태 관리 - const [isChecked, setIsChecked] = useState(component.value === true || component.value === "true"); - const [checkedValues, setCheckedValues] = useState([]); - - // 스타일 계산 (위치는 RealtimePreviewDynamic에서 처리하므로 제외) - const componentStyle: React.CSSProperties = { - height: "100%", - ...component.style, - ...style, - // width는 항상 100%로 고정 (부모 컨테이너가 gridColumns로 크기 제어) - width: "100%", - }; - - // 디자인 모드 스타일 - if (isDesignMode) { - componentStyle.border = "1px dashed #cbd5e1"; - componentStyle.borderColor = isSelected ? "#3b82f6" : "#cbd5e1"; - } - - // 이벤트 핸들러 - const handleClick = (e: React.MouseEvent) => { - e.stopPropagation(); - onClick?.(); - }; - - const handleCheckboxChange = (checked: boolean) => { - setIsChecked(checked); - if (component.onChange) { - component.onChange(checked); - } - if (isInteractive && onFormDataChange && component.column_name) { - onFormDataChange(component.column_name, checked); - } - }; - - const handleGroupChange = (value: string, checked: boolean) => { - const newValues = checked ? [...checkedValues, value] : checkedValues.filter((v) => v !== value); - setCheckedValues(newValues); - if (isInteractive && onFormDataChange && component.column_name) { - onFormDataChange(component.column_name, newValues.join(",")); - } - }; - - // DOM 안전한 props만 필터링 - const safeDomProps = filterDOMProps(props); - - // 세부 타입별 렌더링 - const renderCheckboxByWebType = () => { - // boolean: On/Off 스위치 - if (webType === "boolean") { - return ( -