From a8ded6455de86c7d46662d0629d7720a5f35e5db Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Tue, 28 Apr 2026 17:57:57 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20ConfigPanel=20Inv=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=ED=86=B5=ED=95=A9=20+=20legacy=20?= =?UTF-8?q?=ED=8C=A8=EB=84=90=20=EB=B6=84=EB=A6=AC=20+=20input=20cp=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 11 패널 일괄 Inv* prefix 통일: - 통합 (lib/registry/components/X/): button / container / divider / search / stats / table / title / input → Inv*ConfigPanel - frontend/components/v2/config-panels/V2FieldConfigPanel → InvFieldConfigPanel - 옛 v2-* hidden 호환 → InvLegacy{Divider,Text,Button}ConfigPanel input 통합 컴포넌트 cp 톤 신규 작성 (InvInputConfigPanel): - 277줄 옛 디자인 → CPVisualGrid 10칸 type 카드 + 타입별 옵션 + FeatureChipGrid getComponentConfigPanel.tsx 버그 수정 (Codex 검토): - "stats" key 중복 제거 (옛 StatsCardConfigPanel 이 통합 stats 덮던 silent bug) - ALIAS 에서 v2-button-primary/v2-divider-line/v2-text-display 제외 (옵션 B 일관성 — 옛 hidden 컴포넌트는 InvLegacy 패널 사용) - MAP 의 해당 키를 InvLegacy* 로 직접 매핑 호출처 일괄 갱신: - 각 통합 컴포넌트의 index.ts 7개 (import / config_panel / re-export) - v2-input/v2-select/v2-divider-line/v2-text-display/v2-button-primary 의 index.ts (config_panel 매핑) - V2PropertiesPanel.tsx 의 require pattern (v2-input/v2-select) 검증: tsc 우리 영역 0건 / V2FieldConfigPanel 잔재 0건 / 기존 path 잔재 0건 다음 세션: useDbTables hook 추출 + 잔여 V2* cp 마이그 + dead code 정리 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../screen/panels/V2PropertiesPanel.tsx | 4 +- ...onfigPanel.tsx => InvFieldConfigPanel.tsx} | 9 +- ...nel.tsx => InvLegacyButtonConfigPanel.tsx} | 10 +- ...el.tsx => InvLegacyDividerConfigPanel.tsx} | 10 +- ...Panel.tsx => InvLegacyTextConfigPanel.tsx} | 10 +- ...nfigPanel.tsx => InvButtonConfigPanel.tsx} | 12 +- .../lib/registry/components/button/index.ts | 6 +- ...gPanel.tsx => InvContainerConfigPanel.tsx} | 10 +- .../registry/components/container/index.ts | 6 +- ...figPanel.tsx => InvDividerConfigPanel.tsx} | 10 +- .../lib/registry/components/divider/index.ts | 6 +- .../components/input/InputConfigPanel.tsx | 277 ------------- .../components/input/InvInputConfigPanel.tsx | 376 ++++++++++++++++++ .../lib/registry/components/input/index.ts | 6 +- ...nfigPanel.tsx => InvSearchConfigPanel.tsx} | 10 +- .../lib/registry/components/search/index.ts | 6 +- ...onfigPanel.tsx => InvStatsConfigPanel.tsx} | 10 +- .../lib/registry/components/stats/index.ts | 6 +- ...onfigPanel.tsx => InvTableConfigPanel.tsx} | 10 +- .../lib/registry/components/table/index.ts | 6 +- ...onfigPanel.tsx => InvTitleConfigPanel.tsx} | 10 +- .../lib/registry/components/title/index.ts | 6 +- .../components/v2-button-primary/index.ts | 4 +- .../components/v2-divider-line/index.ts | 4 +- .../lib/registry/components/v2-input/index.ts | 4 +- .../registry/components/v2-select/index.ts | 4 +- .../components/v2-text-display/index.ts | 4 +- .../lib/utils/getComponentConfigPanel.tsx | 40 +- 28 files changed, 491 insertions(+), 385 deletions(-) rename frontend/components/v2/config-panels/{V2FieldConfigPanel.tsx => InvFieldConfigPanel.tsx} (99%) rename frontend/components/v2/config-panels/{InvButtonConfigPanel.tsx => InvLegacyButtonConfigPanel.tsx} (99%) rename frontend/components/v2/config-panels/{InvDividerConfigPanel.tsx => InvLegacyDividerConfigPanel.tsx} (94%) rename frontend/components/v2/config-panels/{InvTextConfigPanel.tsx => InvLegacyTextConfigPanel.tsx} (95%) rename frontend/lib/registry/components/button/{ButtonConfigPanel.tsx => InvButtonConfigPanel.tsx} (97%) rename frontend/lib/registry/components/container/{ContainerConfigPanel.tsx => InvContainerConfigPanel.tsx} (97%) rename frontend/lib/registry/components/divider/{DividerConfigPanel.tsx => InvDividerConfigPanel.tsx} (95%) delete mode 100644 frontend/lib/registry/components/input/InputConfigPanel.tsx create mode 100644 frontend/lib/registry/components/input/InvInputConfigPanel.tsx rename frontend/lib/registry/components/search/{SearchConfigPanel.tsx => InvSearchConfigPanel.tsx} (97%) rename frontend/lib/registry/components/stats/{StatsConfigPanel.tsx => InvStatsConfigPanel.tsx} (98%) rename frontend/lib/registry/components/table/{TableConfigPanel.tsx => InvTableConfigPanel.tsx} (98%) rename frontend/lib/registry/components/title/{TitleConfigPanel.tsx => InvTitleConfigPanel.tsx} (95%) diff --git a/frontend/components/screen/panels/V2PropertiesPanel.tsx b/frontend/components/screen/panels/V2PropertiesPanel.tsx index 26e44658..41fa40c0 100644 --- a/frontend/components/screen/panels/V2PropertiesPanel.tsx +++ b/frontend/components/screen/panels/V2PropertiesPanel.tsx @@ -213,8 +213,8 @@ export const V2PropertiesPanel: React.FC = ({ // 🆕 V2 컴포넌트 직접 감지 및 설정 패널 렌더링 if (componentId?.startsWith("v2-")) { const v2ConfigPanels: Record void }>> = { - "v2-input": require("@/components/v2/config-panels/V2FieldConfigPanel").V2FieldConfigPanel, - "v2-select": require("@/components/v2/config-panels/V2FieldConfigPanel").V2FieldConfigPanel, + "v2-input": require("@/components/v2/config-panels/InvFieldConfigPanel").InvFieldConfigPanel, + "v2-select": require("@/components/v2/config-panels/InvFieldConfigPanel").InvFieldConfigPanel, "v2-date": require("@/components/v2/config-panels/V2DateConfigPanel").V2DateConfigPanel, // v2-list / v2-repeater / v2-table-list 는 InvDataConfigPanel 통합 — 하드코딩 매핑 제거 → ComponentRegistry fallback 사용 "v2-layout": require("@/components/v2/config-panels/V2LayoutConfigPanel").V2LayoutConfigPanel, diff --git a/frontend/components/v2/config-panels/V2FieldConfigPanel.tsx b/frontend/components/v2/config-panels/InvFieldConfigPanel.tsx similarity index 99% rename from frontend/components/v2/config-panels/V2FieldConfigPanel.tsx rename to frontend/components/v2/config-panels/InvFieldConfigPanel.tsx index c54742b3..1754ab5b 100644 --- a/frontend/components/v2/config-panels/V2FieldConfigPanel.tsx +++ b/frontend/components/v2/config-panels/InvFieldConfigPanel.tsx @@ -44,6 +44,7 @@ import { CPSwitch, CPNumber, CPSegment, + CPColor, CPIconBtn, CPCrumb, CPFormatTrigger, @@ -407,7 +408,7 @@ function applyTriple( } // ───────────────────────────────────────────────────────── -interface V2FieldConfigPanelProps { +interface InvFieldConfigPanelProps { config: Record; onChange: (config: Record) => void; tableName?: string; @@ -419,7 +420,7 @@ interface V2FieldConfigPanelProps { componentType?: string; } -export const V2FieldConfigPanel: React.FC = ({ +export const InvFieldConfigPanel: React.FC = ({ config, onChange, tableName, @@ -786,7 +787,7 @@ export const V2FieldConfigPanel: React.FC = ({ ); }; -V2FieldConfigPanel.displayName = "V2FieldConfigPanel"; +InvFieldConfigPanel.displayName = "InvFieldConfigPanel"; // ─────────────────────────────────────────────────────── // 유틸 컴포넌트 @@ -2188,4 +2189,4 @@ const FilterConditionsSection: React.FC<{ ); }; -export default V2FieldConfigPanel; +export default InvFieldConfigPanel; diff --git a/frontend/components/v2/config-panels/InvButtonConfigPanel.tsx b/frontend/components/v2/config-panels/InvLegacyButtonConfigPanel.tsx similarity index 99% rename from frontend/components/v2/config-panels/InvButtonConfigPanel.tsx rename to frontend/components/v2/config-panels/InvLegacyButtonConfigPanel.tsx index 3002070f..3eba901e 100644 --- a/frontend/components/v2/config-panels/InvButtonConfigPanel.tsx +++ b/frontend/components/v2/config-panels/InvLegacyButtonConfigPanel.tsx @@ -1,7 +1,7 @@ "use client"; /** - * InvButtonConfigPanel — 버튼 (v2-button-primary) cp 톤 설정 패널 + * InvLegacyButtonConfigPanel — 버튼 (v2-button-primary) cp 톤 설정 패널 * * 흐름: * ① 액션 유형 (CPVisualGrid 14종 시각 카드) @@ -131,7 +131,7 @@ interface ScreenOption { description?: string; } -interface InvButtonConfigPanelProps { +interface InvLegacyButtonConfigPanelProps { config: Record; onChange: (config: Record) => void; component?: ComponentData; @@ -147,7 +147,7 @@ interface InvButtonConfigPanelProps { // ─────────────────────────────────────────────────────── // Main Component // ─────────────────────────────────────────────────────── -export const InvButtonConfigPanel: React.FC = ({ +export const InvLegacyButtonConfigPanel: React.FC = ({ config, onChange, component, @@ -582,7 +582,7 @@ export const InvButtonConfigPanel: React.FC = ({ ); }; -InvButtonConfigPanel.displayName = "InvButtonConfigPanel"; +InvLegacyButtonConfigPanel.displayName = "InvLegacyButtonConfigPanel"; // ─────────────────────────────────────────────────────── // describeAction — ④ 섹션 desc 동적 텍스트 @@ -1712,4 +1712,4 @@ function TransferDataMappingBody(p: TransferDataMappingBodyProps) { ); } -export default InvButtonConfigPanel; +export default InvLegacyButtonConfigPanel; diff --git a/frontend/components/v2/config-panels/InvDividerConfigPanel.tsx b/frontend/components/v2/config-panels/InvLegacyDividerConfigPanel.tsx similarity index 94% rename from frontend/components/v2/config-panels/InvDividerConfigPanel.tsx rename to frontend/components/v2/config-panels/InvLegacyDividerConfigPanel.tsx index 29fe70bd..da0b6e4f 100644 --- a/frontend/components/v2/config-panels/InvDividerConfigPanel.tsx +++ b/frontend/components/v2/config-panels/InvLegacyDividerConfigPanel.tsx @@ -1,7 +1,7 @@ "use client"; /** - * InvDividerConfigPanel — 구분선 (v2-divider-line) cp 톤 설정 패널 + * InvLegacyDividerConfigPanel — 구분선 (v2-divider-line) cp 톤 설정 패널 * * 흐름: * ① 선 모양 — 두께 + 색상 (CPVisualGrid 시각 카드) @@ -24,7 +24,7 @@ import { Hint, } from "./_shared/cp"; -interface InvDividerConfigPanelProps { +interface InvLegacyDividerConfigPanelProps { config: Record; onChange: (config: Record) => void; } @@ -32,7 +32,7 @@ interface InvDividerConfigPanelProps { const DEFAULT_LINE_COLOR = "#d1d5db"; const DEFAULT_TEXT_COLOR = "#6b7280"; -export const InvDividerConfigPanel: React.FC = ({ +export const InvLegacyDividerConfigPanel: React.FC = ({ config, onChange, }) => { @@ -162,7 +162,7 @@ export const InvDividerConfigPanel: React.FC = ({ ); }; -InvDividerConfigPanel.displayName = "InvDividerConfigPanel"; +InvLegacyDividerConfigPanel.displayName = "InvLegacyDividerConfigPanel"; // 작은 헬퍼 — 색 점 (visual preview) function ColorDot({ color }: { color: string }) { @@ -180,4 +180,4 @@ function ColorDot({ color }: { color: string }) { ); } -export default InvDividerConfigPanel; +export default InvLegacyDividerConfigPanel; diff --git a/frontend/components/v2/config-panels/InvTextConfigPanel.tsx b/frontend/components/v2/config-panels/InvLegacyTextConfigPanel.tsx similarity index 95% rename from frontend/components/v2/config-panels/InvTextConfigPanel.tsx rename to frontend/components/v2/config-panels/InvLegacyTextConfigPanel.tsx index 32e48b92..c7bd7382 100644 --- a/frontend/components/v2/config-panels/InvTextConfigPanel.tsx +++ b/frontend/components/v2/config-panels/InvLegacyTextConfigPanel.tsx @@ -1,7 +1,7 @@ "use client"; /** - * InvTextConfigPanel — 제목/텍스트 (v2-text-display) cp 톤 설정 패널 + * InvLegacyTextConfigPanel — 제목/텍스트 (v2-text-display) cp 톤 설정 패널 * * 흐름: * ① 표시 텍스트 — CPText @@ -26,12 +26,12 @@ import { } from "./_shared/cp"; import { TextDisplayConfig } from "@/lib/registry/components/v2-text-display/types"; -interface InvTextConfigPanelProps { +interface InvLegacyTextConfigPanelProps { config: TextDisplayConfig; onChange: (config: Partial) => void; } -export const InvTextConfigPanel: React.FC = ({ config, onChange }) => { +export const InvLegacyTextConfigPanel: React.FC = ({ config, onChange }) => { const update = (field: keyof TextDisplayConfig, value: any) => { const next = { ...config, [field]: value }; onChange({ [field]: value }); @@ -200,6 +200,6 @@ export const InvTextConfigPanel: React.FC = ({ config, ); }; -InvTextConfigPanel.displayName = "InvTextConfigPanel"; +InvLegacyTextConfigPanel.displayName = "InvLegacyTextConfigPanel"; -export default InvTextConfigPanel; +export default InvLegacyTextConfigPanel; diff --git a/frontend/lib/registry/components/button/ButtonConfigPanel.tsx b/frontend/lib/registry/components/button/InvButtonConfigPanel.tsx similarity index 97% rename from frontend/lib/registry/components/button/ButtonConfigPanel.tsx rename to frontend/lib/registry/components/button/InvButtonConfigPanel.tsx index d6d36a31..1d630a1b 100644 --- a/frontend/lib/registry/components/button/ButtonConfigPanel.tsx +++ b/frontend/lib/registry/components/button/InvButtonConfigPanel.tsx @@ -1,7 +1,7 @@ "use client"; /** - * ButtonConfigPanel — 통합 "버튼" (id: button) cp 톤 설정 패널 + * InvButtonConfigPanel — 통합 "버튼" (id: button) cp 톤 설정 패널 * * 흐름: * ① 텍스트 @@ -10,7 +10,7 @@ * ④ 아이콘 — IconPicker + 아이콘 위치 * ▾ 고급 — 비활성화 (FeatureChipGrid) * - * v2-button-primary 의 InvButtonConfigPanel 와 별개 (그쪽은 옛 컴포넌트의 풍부한 액션 옵션). + * v2-button-primary 의 InvInvButtonConfigPanel 와 별개 (그쪽은 옛 컴포넌트의 풍부한 액션 옵션). * 이 파일은 통합 "button" 컴포넌트의 단순한 패널. * * Reference: notes/gbpark/2026-04-28-cp-panel-standard.md @@ -46,7 +46,7 @@ import { import { IconPicker } from "../common/IconPicker"; import type { ButtonConfig } from "./types"; -export interface ButtonConfigPanelProps { +export interface InvButtonConfigPanelProps { config?: ButtonConfig; onChange?: (config: ButtonConfig) => void; onUpdateProperty?: (componentId: string, path: string, value: unknown) => void; @@ -142,7 +142,7 @@ const ACTION_GROUPS: Array<{ id: string; name: string; items: string[] }> = [ // ─────────────────────────────────────────────────────── // Main // ─────────────────────────────────────────────────────── -export const ButtonConfigPanel: React.FC = ({ +export const InvButtonConfigPanel: React.FC = ({ config, onChange, selectedComponent, @@ -273,7 +273,7 @@ export const ButtonConfigPanel: React.FC = ({ ); }; -ButtonConfigPanel.displayName = "ButtonConfigPanel"; +InvButtonConfigPanel.displayName = "InvButtonConfigPanel"; // ─────────────────────────────────────────────────────── // ActionCardGrid — 2단계 선택 (그룹 segment + 그 안 chip) @@ -426,4 +426,4 @@ function ActionChip({ ); } -export default ButtonConfigPanel; +export default InvButtonConfigPanel; diff --git a/frontend/lib/registry/components/button/index.ts b/frontend/lib/registry/components/button/index.ts index 00f0ece7..13fecfba 100644 --- a/frontend/lib/registry/components/button/index.ts +++ b/frontend/lib/registry/components/button/index.ts @@ -3,7 +3,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; import { ButtonWrapper } from "./ButtonComponent"; -import { ButtonConfigPanel } from "./ButtonConfigPanel"; +import { InvButtonConfigPanel } from "./InvButtonConfigPanel"; import type { ButtonConfig } from "./types"; /** @@ -42,7 +42,7 @@ export const ButtonDefinition = createComponentDefinition({ component: ButtonWrapper, default_config: DEFAULT_CONFIG as Record, default_size: { width: 120, height: 36 }, - config_panel: ButtonConfigPanel, + config_panel: InvButtonConfigPanel, icon: "MousePointer", tags: ["버튼", "button", "action", "click"], version: "2.0.0", @@ -61,4 +61,4 @@ export const ButtonDefinition = createComponentDefinition({ export type { ButtonConfig } from "./types"; export { ButtonComponent, ButtonWrapper } from "./ButtonComponent"; -export { ButtonConfigPanel } from "./ButtonConfigPanel"; +export { InvButtonConfigPanel } from "./InvButtonConfigPanel"; diff --git a/frontend/lib/registry/components/container/ContainerConfigPanel.tsx b/frontend/lib/registry/components/container/InvContainerConfigPanel.tsx similarity index 97% rename from frontend/lib/registry/components/container/ContainerConfigPanel.tsx rename to frontend/lib/registry/components/container/InvContainerConfigPanel.tsx index 01f4f7f6..2c208ed2 100644 --- a/frontend/lib/registry/components/container/ContainerConfigPanel.tsx +++ b/frontend/lib/registry/components/container/InvContainerConfigPanel.tsx @@ -1,7 +1,7 @@ "use client"; /** - * ContainerConfigPanel — "영역" (container) cp 톤 설정 패널 + * InvContainerConfigPanel — "영역" (container) cp 톤 설정 패널 * * containerType 5종 분기: * - section (카드 / 페이퍼 / 평범) @@ -45,13 +45,13 @@ import { } from "@/components/v2/config-panels/_shared/cp"; import type { ContainerConfig, ContainerTab } from "./types"; -export interface ContainerConfigPanelProps { +export interface InvContainerConfigPanelProps { config?: ContainerConfig; onChange?: (config: ContainerConfig) => void; selectedComponent?: { id: string; config?: ContainerConfig; [k: string]: any }; } -export const ContainerConfigPanel: React.FC = ({ +export const InvContainerConfigPanel: React.FC = ({ config, onChange, selectedComponent, @@ -377,6 +377,6 @@ export const ContainerConfigPanel: React.FC = ({ ); }; -ContainerConfigPanel.displayName = "ContainerConfigPanel"; +InvContainerConfigPanel.displayName = "InvContainerConfigPanel"; -export default ContainerConfigPanel; +export default InvContainerConfigPanel; diff --git a/frontend/lib/registry/components/container/index.ts b/frontend/lib/registry/components/container/index.ts index 38494e0b..afb32db0 100644 --- a/frontend/lib/registry/components/container/index.ts +++ b/frontend/lib/registry/components/container/index.ts @@ -3,7 +3,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; import { ContainerWrapper } from "./ContainerComponent"; -import { ContainerConfigPanel } from "./ContainerConfigPanel"; +import { InvContainerConfigPanel } from "./InvContainerConfigPanel"; const DEFAULT_CONFIG = { containerType: "section", @@ -21,7 +21,7 @@ export const ContainerDefinition = createComponentDefinition({ component: ContainerWrapper, default_config: DEFAULT_CONFIG as Record, default_size: { width: 600, height: 300 }, - config_panel: ContainerConfigPanel, + config_panel: InvContainerConfigPanel, icon: "LayoutGrid", tags: ["컨테이너", "container", "탭", "섹션", "아코디언", "반복", "layout"], version: "2.0.0", @@ -31,4 +31,4 @@ export const ContainerDefinition = createComponentDefinition({ export type { ContainerConfig } from "./types"; export { ContainerComponent, ContainerWrapper } from "./ContainerComponent"; -export { ContainerConfigPanel } from "./ContainerConfigPanel"; +export { InvContainerConfigPanel } from "./InvContainerConfigPanel"; diff --git a/frontend/lib/registry/components/divider/DividerConfigPanel.tsx b/frontend/lib/registry/components/divider/InvDividerConfigPanel.tsx similarity index 95% rename from frontend/lib/registry/components/divider/DividerConfigPanel.tsx rename to frontend/lib/registry/components/divider/InvDividerConfigPanel.tsx index 152740ee..bb9c753b 100644 --- a/frontend/lib/registry/components/divider/DividerConfigPanel.tsx +++ b/frontend/lib/registry/components/divider/InvDividerConfigPanel.tsx @@ -1,7 +1,7 @@ "use client"; /** - * DividerConfigPanel — 통합 "구분선" (id: divider) cp 톤 설정 패널 + * InvDividerConfigPanel — 통합 "구분선" (id: divider) cp 톤 설정 패널 * * 흐름: * ① 모양 — 방향 (CPSegment) + 두께 (CPVisualGrid 시각 미리보기) + 선 스타일 (CPVisualGrid 시각 미리보기) @@ -26,7 +26,7 @@ import { } from "@/components/v2/config-panels/_shared/cp"; import type { DividerConfig } from "./types"; -export interface DividerConfigPanelProps { +export interface InvDividerConfigPanelProps { config?: DividerConfig; onChange?: (config: DividerConfig) => void; onUpdateProperty?: (componentId: string, path: string, value: unknown) => void; @@ -35,7 +35,7 @@ export interface DividerConfigPanelProps { const DEFAULT_COLOR = "#d1d5db"; -export const DividerConfigPanel: React.FC = ({ +export const InvDividerConfigPanel: React.FC = ({ config, onChange, onUpdateProperty, @@ -189,6 +189,6 @@ export const DividerConfigPanel: React.FC = ({ ); }; -DividerConfigPanel.displayName = "DividerConfigPanel"; +InvDividerConfigPanel.displayName = "InvDividerConfigPanel"; -export default DividerConfigPanel; +export default InvDividerConfigPanel; diff --git a/frontend/lib/registry/components/divider/index.ts b/frontend/lib/registry/components/divider/index.ts index f78b8e5a..d36e41a0 100644 --- a/frontend/lib/registry/components/divider/index.ts +++ b/frontend/lib/registry/components/divider/index.ts @@ -3,7 +3,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; import { DividerWrapper } from "./DividerComponent"; -import { DividerConfigPanel } from "./DividerConfigPanel"; +import { InvDividerConfigPanel } from "./InvDividerConfigPanel"; import type { DividerConfig } from "./types"; /** @@ -43,7 +43,7 @@ export const DividerDefinition = createComponentDefinition({ component: DividerWrapper, default_config: DEFAULT_CONFIG as Record, default_size: { width: 400, height: 2 }, - config_panel: DividerConfigPanel, + config_panel: InvDividerConfigPanel, icon: "SeparatorHorizontal", tags: ["구분선", "divider", "line", "separator", "v2-divider-line"], version: "2.0.0", @@ -54,4 +54,4 @@ export const DividerDefinition = createComponentDefinition({ export type { DividerConfig } from "./types"; export { DividerComponent, DividerWrapper } from "./DividerComponent"; -export { DividerConfigPanel } from "./DividerConfigPanel"; +export { InvDividerConfigPanel } from "./InvDividerConfigPanel"; diff --git a/frontend/lib/registry/components/input/InputConfigPanel.tsx b/frontend/lib/registry/components/input/InputConfigPanel.tsx deleted file mode 100644 index 276c776c..00000000 --- a/frontend/lib/registry/components/input/InputConfigPanel.tsx +++ /dev/null @@ -1,277 +0,0 @@ -"use client"; - -import React from "react"; -import type { InputConfig, InputFieldType } from "./types"; - -/** - * Input ConfigPanel — 통합 입력 컴포넌트 설정 편집. - * - * 가장 중요한 건 `type` 필드. 선택한 type 에 따라 하위 옵션이 달라진다. - * Phase B-1 의 최소 구현; Phase F 에서 entity ref picker / options builder - * 등 고급 UI 추가 예정. - */ - -export interface InputConfigPanelProps { - config?: InputConfig; - onChange?: (config: InputConfig) => void; - selectedComponent?: { id: string; config?: InputConfig; [k: string]: any }; -} - -export const InputConfigPanel: React.FC = ({ - config, - onChange, - selectedComponent, -}) => { - const current: InputConfig = - (config as InputConfig) || (selectedComponent?.config as InputConfig) || {}; - - const patch = (p: Partial) => { - onChange?.({ ...current, ...p }); - }; - - const type: InputFieldType = (current.type as InputFieldType) || "text"; - - return ( -
- {/* ─── 타입 (최상위) ─── */} -
- - -
- - {/* ─── 라벨 / placeholder ─── */} -
- - patch({ label: e.target.value })} - placeholder="필드 라벨" - className="border-border bg-background w-full rounded border px-2 py-1 text-xs" - /> -
- -
- - patch({ placeholder: e.target.value })} - placeholder="입력 안내" - className="border-border bg-background w-full rounded border px-2 py-1 text-xs" - /> -
- -
- - patch({ helperText: e.target.value || undefined })} - placeholder="하단 도움말" - className="border-border bg-background w-full rounded border px-2 py-1 text-xs" - /> -
- - {/* ─── 기본 토글 ─── */} - - - - - - - {/* ─── type 별 하위 옵션 ─── */} - {(type === "number") && ( - <> -
-
- number 옵션 -
-
-
-
- - patch({ min: e.target.value === "" ? undefined : Number(e.target.value) })} - className="border-border bg-background w-full rounded border px-1 py-0.5 text-[0.65rem]" - /> -
-
- - patch({ max: e.target.value === "" ? undefined : Number(e.target.value) })} - className="border-border bg-background w-full rounded border px-1 py-0.5 text-[0.65rem]" - /> -
-
- - patch({ step: e.target.value === "" ? undefined : Number(e.target.value) })} - className="border-border bg-background w-full rounded border px-1 py-0.5 text-[0.65rem]" - /> -
-
- - )} - - {(type === "text" || type === "textarea") && ( - <> -
-
- 문자열 길이 -
-
-
-
- - patch({ minLength: e.target.value === "" ? undefined : Number(e.target.value) })} - className="border-border bg-background w-full rounded border px-1 py-0.5 text-[0.65rem]" - /> -
-
- - patch({ maxLength: e.target.value === "" ? undefined : Number(e.target.value) })} - className="border-border bg-background w-full rounded border px-1 py-0.5 text-[0.65rem]" - /> -
-
- - )} - - {type === "textarea" && ( -
- - patch({ rows: Number(e.target.value) })} - min={1} - max={20} - className="border-border bg-background w-full rounded border px-2 py-1 text-xs" - /> -
- )} - - {type === "select" && ( -
- - (typeof o === "string" ? o : o.label)).join(", ") : ""} - onChange={(e) => { - const options = e.target.value - .split(",") - .map((s) => s.trim()) - .filter(Boolean); - patch({ options }); - }} - placeholder="예: 확정, 취소, 대기" - className="border-border bg-background w-full rounded border px-2 py-1 text-xs" - /> -
- )} - - {type === "file" && ( - <> -
- - patch({ accept: e.target.value || undefined })} - placeholder="예: image/*, .pdf" - className="border-border bg-background w-full rounded border px-2 py-1 text-xs" - /> -
- - - )} - - {(type === "number" || type === "date" || type === "datetime") && ( -
- - patch({ format: e.target.value || undefined })} - placeholder={type === "number" ? "#,##0" : "YYYY-MM-DD"} - className="border-border bg-background w-full rounded border px-2 py-1 text-xs" - /> -
- )} -
- ); -}; - -export default InputConfigPanel; diff --git a/frontend/lib/registry/components/input/InvInputConfigPanel.tsx b/frontend/lib/registry/components/input/InvInputConfigPanel.tsx new file mode 100644 index 00000000..40883330 --- /dev/null +++ b/frontend/lib/registry/components/input/InvInputConfigPanel.tsx @@ -0,0 +1,376 @@ +"use client"; + +/** + * InvInputConfigPanel — 통합 "입력 필드" (id: input) cp 톤 설정 패널 + * + * 흐름: + * ① 필드 타입 — CPVisualGrid 10칸 (text/number/date/datetime/select/entity/checkbox/textarea/file/code) + * ② 라벨 / 안내 — 라벨 + placeholder + 도움말 + * ③ 타입별 옵션 — type 에 따라 조건부 (number min/max/step, text minLength/maxLength, textarea rows, select options, file accept/multiple, format) + * ▾ 옵션 — required / editable / disabled (FeatureChipGrid) + * + * Reference: notes/gbpark/2026-04-28-cp-panel-standard.md + */ + +import React from "react"; +import { + Type, + Hash, + Calendar, + CalendarClock, + ChevronDown, + Link2, + CheckSquare, + AlignLeft, + Paperclip, + Code2, + Plus, + X, +} from "lucide-react"; +import { + CPSection, + CPRow, + CPGroup, + CPText, + CPNumber, + CPVisualGrid, + FeatureChipGrid, + Hint, +} from "@/components/v2/config-panels/_shared/cp"; +import type { InputConfig, InputFieldType } from "./types"; + +export interface InvInputConfigPanelProps { + config?: InputConfig; + onChange?: (config: InputConfig) => void; + selectedComponent?: { id: string; config?: InputConfig; [k: string]: any }; +} + +const TYPE_OPTIONS: Array<{ + value: InputFieldType; + label: string; + icon: React.ReactNode; + desc: string; +}> = [ + { value: "text", label: "문자열", icon: , desc: "일반 텍스트 한 줄" }, + { value: "number", label: "숫자", icon: , desc: "정수 / 소수 입력" }, + { value: "date", label: "날짜", icon: , desc: "YYYY-MM-DD" }, + { value: "datetime", label: "날짜+시간", icon: , desc: "분/초까지" }, + { value: "select", label: "드롭다운", icon: , desc: "선택지 list" }, + { value: "entity", label: "엔티티", icon: , desc: "FK 참조 (팝업)" }, + { value: "checkbox", label: "체크박스", icon: , desc: "참/거짓" }, + { value: "textarea", label: "장문", icon: , desc: "여러 줄 텍스트" }, + { value: "file", label: "파일", icon: , desc: "업로드" }, + { value: "code", label: "자동채번", icon: , desc: "readonly · 자동 생성" }, +]; + +export const InvInputConfigPanel: React.FC = ({ + config, + onChange, + selectedComponent, +}) => { + const current: InputConfig = + (config as InputConfig) || (selectedComponent?.config as InputConfig) || {}; + + const patch = (p: Partial) => onChange?.({ ...current, ...p }); + + const type: InputFieldType = (current.type as InputFieldType) || "text"; + + const optionsArr: string[] = Array.isArray(current.options) + ? current.options.map((o: any) => (typeof o === "string" ? o : o.label || o.value || "")) + : []; + + const addOption = () => patch({ options: [...optionsArr, ""] as any }); + const updateOption = (i: number, v: string) => + patch({ options: optionsArr.map((o, idx) => (idx === i ? v : o)) as any }); + const removeOption = (i: number) => + patch({ options: optionsArr.filter((_, idx) => idx !== i) as any }); + + return ( +
+ {/* ── ① 필드 타입 ─────────────────────────── */} + + patch({ type: v as InputFieldType })} + options={TYPE_OPTIONS.map((o) => ({ + value: o.value, + label: o.label, + preview: o.icon, + desc: o.desc, + }))} + /> + + + {/* ── ② 라벨 / 안내 ─────────────────────────── */} + + + patch({ label: v })} + placeholder="필드 라벨" + /> + + + patch({ placeholder: v })} + placeholder="입력하세요" + /> + + + patch({ helperText: v || undefined })} + placeholder="하단 도움말" + /> + + + + {/* ── ③ 타입별 옵션 (조건부) ─────────────────────────── */} + {type === "number" && ( + + + patch({ min: v })} + placeholder="제한 없음" + /> + + + patch({ max: v })} + placeholder="제한 없음" + /> + + + patch({ step: v })} + placeholder="1" + /> + + + patch({ format: v || undefined })} + placeholder="#,##0" + /> + + + )} + + {(type === "text" || type === "textarea") && ( + + + patch({ minLength: v })} + min={0} + placeholder="제한 없음" + /> + + + patch({ maxLength: v })} + min={0} + placeholder="제한 없음" + /> + + {type === "textarea" && ( + + patch({ rows: v ?? 3 })} + min={1} + max={20} + /> + + )} + + )} + + {(type === "date" || type === "datetime") && ( + + + patch({ format: v || undefined })} + placeholder={type === "date" ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm"} + /> + + + )} + + {type === "select" && ( + +
+ +
+ {optionsArr.length === 0 ? ( + 선택지가 없습니다. [+ 추가] 로 만드세요. + ) : ( +
+ {optionsArr.map((opt, i) => ( +
+ + {i + 1} + + updateOption(i, e.target.value)} + placeholder={`옵션 ${i + 1}`} + style={{ + height: 22, + padding: "0 6px", + fontSize: 11, + background: "var(--cp-surface)", + border: "1px solid var(--cp-border)", + borderRadius: 3, + color: "var(--cp-text)", + outline: "none", + fontFamily: "var(--v5-font-sans)", + }} + /> + +
+ ))} +
+ )} +
+ )} + + {type === "file" && ( + + + patch({ accept: v || undefined })} + placeholder="image/*" + /> + + patch({ [k]: v } as Partial)} + /> + + )} + + {type === "entity" && ( + + + FK 참조 picker UI 는 추후 추가 예정. 현재는 type 만 저장 (백엔드 키 보존). + + + )} + + {type === "code" && ( + + 채번 규칙은 별도 설정에서 관리합니다. + + )} + + {type === "checkbox" && ( + + 체크박스는 추가 옵션이 없습니다 (참/거짓). + + )} + + {/* ── ▾ 옵션 ─────────────────────────── */} + + patch({ [k]: v } as Partial)} + /> + +
+ ); +}; + +InvInputConfigPanel.displayName = "InvInputConfigPanel"; + +export default InvInputConfigPanel; diff --git a/frontend/lib/registry/components/input/index.ts b/frontend/lib/registry/components/input/index.ts index 3f0f6d25..e9bdf43a 100644 --- a/frontend/lib/registry/components/input/index.ts +++ b/frontend/lib/registry/components/input/index.ts @@ -3,7 +3,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; import { InputWrapper } from "./InputComponent"; -import { InputConfigPanel } from "./InputConfigPanel"; +import { InvInputConfigPanel } from "./InvInputConfigPanel"; import type { InputConfig } from "./types"; /** @@ -42,7 +42,7 @@ export const InputDefinition = createComponentDefinition({ component: InputWrapper, default_config: DEFAULT_CONFIG as Record, default_size: { width: 240, height: 48 }, - config_panel: InputConfigPanel, + config_panel: InvInputConfigPanel, icon: "Edit", tags: ["입력", "input", "field", "text", "number", "date", "select"], version: "2.0.0", @@ -61,4 +61,4 @@ export const InputDefinition = createComponentDefinition({ export type { InputConfig } from "./types"; export { InputComponent, InputWrapper } from "./InputComponent"; -export { InputConfigPanel } from "./InputConfigPanel"; +export { InvInputConfigPanel } from "./InvInputConfigPanel"; diff --git a/frontend/lib/registry/components/search/SearchConfigPanel.tsx b/frontend/lib/registry/components/search/InvSearchConfigPanel.tsx similarity index 97% rename from frontend/lib/registry/components/search/SearchConfigPanel.tsx rename to frontend/lib/registry/components/search/InvSearchConfigPanel.tsx index 724dade8..5e1a044a 100644 --- a/frontend/lib/registry/components/search/SearchConfigPanel.tsx +++ b/frontend/lib/registry/components/search/InvSearchConfigPanel.tsx @@ -1,7 +1,7 @@ "use client"; /** - * SearchConfigPanel — 통합 "검색" (id: search) cp 톤 설정 패널 + * InvSearchConfigPanel — 통합 "검색" (id: search) cp 톤 설정 패널 * * 흐름: * ① 테이블 연결 — 테이블 선택 + 검색 필드 자동 로드 버튼 @@ -28,7 +28,7 @@ import { } from "@/components/v2/config-panels/_shared/cp"; import type { SearchConfig } from "./types"; -export interface SearchConfigPanelProps { +export interface InvSearchConfigPanelProps { config?: SearchConfig; onChange?: (config: SearchConfig) => void; selectedComponent?: { id: string; config?: SearchConfig; [k: string]: any }; @@ -58,7 +58,7 @@ const FIELD_TYPE_LABEL: Record = { select: "선택", }; -export const SearchConfigPanel: React.FC = ({ +export const InvSearchConfigPanel: React.FC = ({ config, onChange, selectedComponent, @@ -287,7 +287,7 @@ export const SearchConfigPanel: React.FC = ({ ); }; -SearchConfigPanel.displayName = "SearchConfigPanel"; +InvSearchConfigPanel.displayName = "InvSearchConfigPanel"; // ─────────────────────────────────────────────────────── // SearchFieldRow — Dense list 한 줄 (높이 ~24px, 카드 박스 폐기) @@ -424,4 +424,4 @@ function SearchFieldRow({ ); } -export default SearchConfigPanel; +export default InvSearchConfigPanel; diff --git a/frontend/lib/registry/components/search/index.ts b/frontend/lib/registry/components/search/index.ts index 46cae7a1..c171526b 100644 --- a/frontend/lib/registry/components/search/index.ts +++ b/frontend/lib/registry/components/search/index.ts @@ -3,7 +3,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; import { SearchWrapper } from "./SearchComponent"; -import { SearchConfigPanel } from "./SearchConfigPanel"; +import { InvSearchConfigPanel } from "./InvSearchConfigPanel"; import type { SearchConfig } from "./types"; /** @@ -40,7 +40,7 @@ export const SearchDefinition = createComponentDefinition({ component: SearchWrapper, default_config: DEFAULT_CONFIG as Record, default_size: { width: 480, height: 48 }, - config_panel: SearchConfigPanel, + config_panel: InvSearchConfigPanel, icon: "Search", tags: ["검색", "search", "filter", "widget"], version: "2.0.0", @@ -55,4 +55,4 @@ export const SearchDefinition = createComponentDefinition({ export type { SearchConfig } from "./types"; export { SearchComponent, SearchWrapper } from "./SearchComponent"; -export { SearchConfigPanel } from "./SearchConfigPanel"; +export { InvSearchConfigPanel } from "./InvSearchConfigPanel"; diff --git a/frontend/lib/registry/components/stats/StatsConfigPanel.tsx b/frontend/lib/registry/components/stats/InvStatsConfigPanel.tsx similarity index 98% rename from frontend/lib/registry/components/stats/StatsConfigPanel.tsx rename to frontend/lib/registry/components/stats/InvStatsConfigPanel.tsx index faa88ff1..622cc4fb 100644 --- a/frontend/lib/registry/components/stats/StatsConfigPanel.tsx +++ b/frontend/lib/registry/components/stats/InvStatsConfigPanel.tsx @@ -1,7 +1,7 @@ "use client"; /** - * StatsConfigPanel — 통합 "통계 카드" (id: stats) cp 톤 설정 패널 + * InvStatsConfigPanel — 통합 "통계 카드" (id: stats) cp 톤 설정 패널 * * 흐름: * ① 기본 — 제목 @@ -37,7 +37,7 @@ const COLOR_PRESETS = [ { id: "dark", name: "다크", colors: ["#1e3a5f", "#1e3f28", "#5c3c0a", "#5c1a1a"] }, ]; -export interface StatsConfigPanelProps { +export interface InvStatsConfigPanelProps { config?: StatsConfig; onChange?: (config: StatsConfig) => void; selectedComponent?: { id: string; config?: StatsConfig; [k: string]: any }; @@ -47,7 +47,7 @@ export interface StatsConfigPanelProps { onTableChange?: (tableName: string) => void; } -export const StatsConfigPanel: React.FC = ({ +export const InvStatsConfigPanel: React.FC = ({ config, onChange, selectedComponent, @@ -346,7 +346,7 @@ export const StatsConfigPanel: React.FC = ({ ); }; -StatsConfigPanel.displayName = "StatsConfigPanel"; +InvStatsConfigPanel.displayName = "InvStatsConfigPanel"; // ─────────────────────────────────────────────────────── // CardPreview / ChipPreview / BigNumberPreview — 표시 스타일 미리보기 @@ -625,4 +625,4 @@ function inputStyle({ mono = false }: { mono?: boolean } = {}): React.CSSPropert }; } -export default StatsConfigPanel; +export default InvStatsConfigPanel; diff --git a/frontend/lib/registry/components/stats/index.ts b/frontend/lib/registry/components/stats/index.ts index bef3d43c..fab7f413 100644 --- a/frontend/lib/registry/components/stats/index.ts +++ b/frontend/lib/registry/components/stats/index.ts @@ -3,7 +3,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; import { StatsWrapper } from "./StatsComponent"; -import { StatsConfigPanel } from "./StatsConfigPanel"; +import { InvStatsConfigPanel } from "./InvStatsConfigPanel"; import type { StatsConfig } from "./types"; /** @@ -46,7 +46,7 @@ export const StatsDefinition = createComponentDefinition({ component: StatsWrapper, default_config: DEFAULT_CONFIG as Record, default_size: { width: 480, height: 120 }, - config_panel: StatsConfigPanel, + config_panel: InvStatsConfigPanel, icon: "BarChart", tags: ["통계", "kpi", "stats", "aggregation", "card"], version: "2.0.0", @@ -61,4 +61,4 @@ export const StatsDefinition = createComponentDefinition({ export type { StatsConfig } from "./types"; export { StatsComponent, StatsWrapper } from "./StatsComponent"; -export { StatsConfigPanel } from "./StatsConfigPanel"; +export { InvStatsConfigPanel } from "./InvStatsConfigPanel"; diff --git a/frontend/lib/registry/components/table/TableConfigPanel.tsx b/frontend/lib/registry/components/table/InvTableConfigPanel.tsx similarity index 98% rename from frontend/lib/registry/components/table/TableConfigPanel.tsx rename to frontend/lib/registry/components/table/InvTableConfigPanel.tsx index beb388b3..1621d715 100644 --- a/frontend/lib/registry/components/table/TableConfigPanel.tsx +++ b/frontend/lib/registry/components/table/InvTableConfigPanel.tsx @@ -1,7 +1,7 @@ "use client"; /** - * TableConfigPanel — 통합 "테이블" (id: table) cp 톤 설정 패널 + * InvTableConfigPanel — 통합 "테이블" (id: table) cp 톤 설정 패널 * * 흐름: * ① 테이블 연결 — 테이블 선택 + DB 컬럼 자동 로드 @@ -43,7 +43,7 @@ import { } from "@/components/v2/config-panels/_shared/cp"; import type { TableConfig, TableColumn } from "./types"; -export interface TableConfigPanelProps { +export interface InvTableConfigPanelProps { config?: TableConfig; onChange?: (config: TableConfig) => void; selectedComponent?: { id: string; config?: TableConfig; [k: string]: any }; @@ -53,7 +53,7 @@ export interface TableConfigPanelProps { onTableChange?: (tableName: string) => void; } -export const TableConfigPanel: React.FC = ({ +export const InvTableConfigPanel: React.FC = ({ config, onChange, selectedComponent, @@ -470,7 +470,7 @@ export const TableConfigPanel: React.FC = ({ ); }; -TableConfigPanel.displayName = "TableConfigPanel"; +InvTableConfigPanel.displayName = "InvTableConfigPanel"; // ─────────────────────────────────────────────────────── // ColumnEditRow — 컬럼 한 줄 편집 (dense) @@ -652,4 +652,4 @@ function inputStyle({ mono = false }: { mono?: boolean } = {}): React.CSSPropert }; } -export default TableConfigPanel; +export default InvTableConfigPanel; diff --git a/frontend/lib/registry/components/table/index.ts b/frontend/lib/registry/components/table/index.ts index e4453134..ca3c6bb1 100644 --- a/frontend/lib/registry/components/table/index.ts +++ b/frontend/lib/registry/components/table/index.ts @@ -3,7 +3,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; import { TableWrapper } from "./TableComponent"; -import { TableConfigPanel } from "./TableConfigPanel"; +import { InvTableConfigPanel } from "./InvTableConfigPanel"; import type { TableConfig } from "./types"; /** @@ -49,7 +49,7 @@ export const TableDefinition = createComponentDefinition({ component: TableWrapper, default_config: DEFAULT_CONFIG as Record, default_size: { width: 800, height: 400 }, - config_panel: TableConfigPanel, + config_panel: InvTableConfigPanel, icon: "Table", tags: ["테이블", "table", "grid", "list", "data", "split", "pivot"], version: "2.0.0", @@ -71,4 +71,4 @@ export const TableDefinition = createComponentDefinition({ export type { TableConfig, TableColumn } from "./types"; export { TableComponent, TableWrapper } from "./TableComponent"; -export { TableConfigPanel } from "./TableConfigPanel"; +export { InvTableConfigPanel } from "./InvTableConfigPanel"; diff --git a/frontend/lib/registry/components/title/TitleConfigPanel.tsx b/frontend/lib/registry/components/title/InvTitleConfigPanel.tsx similarity index 95% rename from frontend/lib/registry/components/title/TitleConfigPanel.tsx rename to frontend/lib/registry/components/title/InvTitleConfigPanel.tsx index 511126fa..ed5e793a 100644 --- a/frontend/lib/registry/components/title/TitleConfigPanel.tsx +++ b/frontend/lib/registry/components/title/InvTitleConfigPanel.tsx @@ -1,7 +1,7 @@ "use client"; /** - * TitleConfigPanel — 통합 "제목/텍스트" (id: title) cp 톤 설정 패널 + * InvTitleConfigPanel — 통합 "제목/텍스트" (id: title) cp 톤 설정 패널 * * 흐름: * ① 텍스트 @@ -28,14 +28,14 @@ import { } from "@/components/v2/config-panels/_shared/cp"; import type { TitleConfig } from "./types"; -export interface TitleConfigPanelProps { +export interface InvTitleConfigPanelProps { config?: TitleConfig; onChange?: (config: TitleConfig) => void; onUpdateProperty?: (componentId: string, path: string, value: unknown) => void; selectedComponent?: { id: string; config?: TitleConfig; [k: string]: any }; } -export const TitleConfigPanel: React.FC = ({ +export const InvTitleConfigPanel: React.FC = ({ config, onChange, onUpdateProperty, @@ -199,6 +199,6 @@ export const TitleConfigPanel: React.FC = ({ ); }; -TitleConfigPanel.displayName = "TitleConfigPanel"; +InvTitleConfigPanel.displayName = "InvTitleConfigPanel"; -export default TitleConfigPanel; +export default InvTitleConfigPanel; diff --git a/frontend/lib/registry/components/title/index.ts b/frontend/lib/registry/components/title/index.ts index 4ff04dd8..7388dc8c 100644 --- a/frontend/lib/registry/components/title/index.ts +++ b/frontend/lib/registry/components/title/index.ts @@ -3,7 +3,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; import { TitleWrapper } from "./TitleComponent"; -import { TitleConfigPanel } from "./TitleConfigPanel"; +import { InvTitleConfigPanel } from "./InvTitleConfigPanel"; import type { TitleConfig } from "./types"; /** @@ -41,7 +41,7 @@ export const TitleDefinition = createComponentDefinition({ component: TitleWrapper, default_config: DEFAULT_CONFIG as Record, default_size: { width: 200, height: 28 }, - config_panel: TitleConfigPanel, + config_panel: InvTitleConfigPanel, icon: "Type", tags: ["제목", "텍스트", "title", "text", "label", "heading"], version: "2.0.0", @@ -52,4 +52,4 @@ export const TitleDefinition = createComponentDefinition({ export type { TitleConfig } from "./types"; export { TitleComponent, TitleWrapper } from "./TitleComponent"; -export { TitleConfigPanel } from "./TitleConfigPanel"; +export { InvTitleConfigPanel } from "./InvTitleConfigPanel"; diff --git a/frontend/lib/registry/components/v2-button-primary/index.ts b/frontend/lib/registry/components/v2-button-primary/index.ts index d0d78615..e0e40bbb 100644 --- a/frontend/lib/registry/components/v2-button-primary/index.ts +++ b/frontend/lib/registry/components/v2-button-primary/index.ts @@ -3,7 +3,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; import { ButtonPrimaryWrapper } from "./ButtonPrimaryComponent"; -import { InvButtonConfigPanel } from "@/components/v2/config-panels/InvButtonConfigPanel"; +import { InvLegacyButtonConfigPanel } from "@/components/v2/config-panels/InvLegacyButtonConfigPanel"; import { withContainerQuery } from "../../hoc/withContainerQuery"; /** @@ -45,7 +45,7 @@ export const V2ButtonPrimaryDefinition = createComponentDefinition({ }, }, default_size: { width: 100, height: 40 }, - config_panel: InvButtonConfigPanel, + config_panel: InvLegacyButtonConfigPanel, icon: "MousePointer", tags: ["버튼", "액션", "클릭"], version: "1.0.0", diff --git a/frontend/lib/registry/components/v2-divider-line/index.ts b/frontend/lib/registry/components/v2-divider-line/index.ts index 142694b7..054855d7 100644 --- a/frontend/lib/registry/components/v2-divider-line/index.ts +++ b/frontend/lib/registry/components/v2-divider-line/index.ts @@ -5,7 +5,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition import { ComponentCategory } from "@/types/component"; import type { WebType } from "@/types/screen"; import { DividerLineWrapper } from "./DividerLineComponent"; -import { InvDividerConfigPanel } from "@/components/v2/config-panels/InvDividerConfigPanel"; +import { InvLegacyDividerConfigPanel } from "@/components/v2/config-panels/InvLegacyDividerConfigPanel"; import { DividerLineConfig } from "./types"; /** @@ -25,7 +25,7 @@ export const V2DividerLineDefinition = createComponentDefinition({ maxLength: 255, }, default_size: { width: 400, height: 2 }, - config_panel: InvDividerConfigPanel, + config_panel: InvLegacyDividerConfigPanel, icon: "Layout", tags: [], version: "1.0.0", diff --git a/frontend/lib/registry/components/v2-input/index.ts b/frontend/lib/registry/components/v2-input/index.ts index ea390335..36a6a2b8 100644 --- a/frontend/lib/registry/components/v2-input/index.ts +++ b/frontend/lib/registry/components/v2-input/index.ts @@ -6,7 +6,7 @@ import { ComponentCategory } from "@/types/component"; import { createComponentDefinition } from "../../utils/createComponentDefinition"; -import { V2FieldConfigPanel } from "@/components/v2/config-panels/V2FieldConfigPanel"; +import { InvFieldConfigPanel } from "@/components/v2/config-panels/InvFieldConfigPanel"; import { V2Input } from "@/components/v2/V2Input"; import { withContainerQuery } from "../../hoc/withContainerQuery"; @@ -29,7 +29,7 @@ export const V2InputDefinition = createComponentDefinition({ tags: ["input", "text", "number", "v2"], // 설정 패널 - config_panel: V2FieldConfigPanel, + config_panel: InvFieldConfigPanel, // ─── INVYONE DataPort 선언 ─── dataPorts: { diff --git a/frontend/lib/registry/components/v2-select/index.ts b/frontend/lib/registry/components/v2-select/index.ts index 9fafe651..330f156f 100644 --- a/frontend/lib/registry/components/v2-select/index.ts +++ b/frontend/lib/registry/components/v2-select/index.ts @@ -6,7 +6,7 @@ import { ComponentCategory } from "@/types/component"; import { createComponentDefinition } from "../../utils/createComponentDefinition"; -import { V2FieldConfigPanel } from "@/components/v2/config-panels/V2FieldConfigPanel"; +import { InvFieldConfigPanel } from "@/components/v2/config-panels/InvFieldConfigPanel"; import { V2Select } from "@/components/v2/V2Select"; import { withContainerQuery } from "../../hoc/withContainerQuery"; @@ -38,7 +38,7 @@ export const V2SelectDefinition = createComponentDefinition({ tags: ["select", "dropdown", "combobox", "v2"], // 설정 패널 - config_panel: V2FieldConfigPanel, + config_panel: InvFieldConfigPanel, }); export default V2SelectDefinition; diff --git a/frontend/lib/registry/components/v2-text-display/index.ts b/frontend/lib/registry/components/v2-text-display/index.ts index 7eb6a4ba..b7855f3d 100644 --- a/frontend/lib/registry/components/v2-text-display/index.ts +++ b/frontend/lib/registry/components/v2-text-display/index.ts @@ -5,7 +5,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition import { ComponentCategory } from "@/types/component"; import type { WebType } from "@/types/screen"; import { TextDisplayWrapper } from "./TextDisplayComponent"; -import { InvTextConfigPanel } from "@/components/v2/config-panels/InvTextConfigPanel"; +import { InvLegacyTextConfigPanel } from "@/components/v2/config-panels/InvLegacyTextConfigPanel"; import { TextDisplayConfig } from "./types"; import { withContainerQuery } from "../../hoc/withContainerQuery"; @@ -29,7 +29,7 @@ export const V2TextDisplayDefinition = createComponentDefinition({ textAlign: "left", }, default_size: { width: 150, height: 24 }, - config_panel: InvTextConfigPanel, + config_panel: InvLegacyTextConfigPanel, icon: "Type", tags: ["텍스트", "표시", "라벨"], version: "1.0.0", diff --git a/frontend/lib/utils/getComponentConfigPanel.tsx b/frontend/lib/utils/getComponentConfigPanel.tsx index 723e8ac8..54b1ae7c 100644 --- a/frontend/lib/utils/getComponentConfigPanel.tsx +++ b/frontend/lib/utils/getComponentConfigPanel.tsx @@ -13,18 +13,18 @@ import type { ConfigPanelContext } from "@/lib/registry/components/common/Config // 관련 문서: notes/gbpark/2026-04-11-component-unification-plan.md §10.5 const CONFIG_PANEL_MAP: Record Promise> = { // ========== INVYONE 통합 컴포넌트 (2026-04-11, Phase A~) ========== - "divider": () => import("@/lib/registry/components/divider/DividerConfigPanel"), - "title": () => import("@/lib/registry/components/title/TitleConfigPanel"), - "button": () => import("@/lib/registry/components/button/ButtonConfigPanel"), - "search": () => import("@/lib/registry/components/search/SearchConfigPanel"), - "input": () => import("@/lib/registry/components/input/InputConfigPanel"), - "stats": () => import("@/lib/registry/components/stats/StatsConfigPanel"), - "table": () => import("@/lib/registry/components/table/TableConfigPanel"), - "container": () => import("@/lib/registry/components/container/ContainerConfigPanel"), + "divider": () => import("@/lib/registry/components/divider/InvDividerConfigPanel"), + "title": () => import("@/lib/registry/components/title/InvTitleConfigPanel"), + "button": () => import("@/lib/registry/components/button/InvButtonConfigPanel"), + "search": () => import("@/lib/registry/components/search/InvSearchConfigPanel"), + "input": () => import("@/lib/registry/components/input/InvInputConfigPanel"), + "stats": () => import("@/lib/registry/components/stats/InvStatsConfigPanel"), + "table": () => import("@/lib/registry/components/table/InvTableConfigPanel"), + "container": () => import("@/lib/registry/components/container/InvContainerConfigPanel"), // ========== V2 컴포넌트 ========== - "v2-input": () => import("@/components/v2/config-panels/V2FieldConfigPanel"), - "v2-select": () => import("@/components/v2/config-panels/V2FieldConfigPanel"), + "v2-input": () => import("@/components/v2/config-panels/InvFieldConfigPanel"), + "v2-select": () => import("@/components/v2/config-panels/InvFieldConfigPanel"), "v2-date": () => import("@/components/v2/config-panels/V2DateConfigPanel"), "v2-list": () => import("@/components/v2/config-panels/InvDataConfigPanel"), "v2-media": () => import("@/components/v2/config-panels/V2MediaConfigPanel"), @@ -49,14 +49,17 @@ const CONFIG_PANEL_MAP: Record Promise> = { // ========== 버튼 ========== "button-primary": () => import("@/components/screen/config-panels/ButtonConfigPanel"), - "v2-button-primary": () => import("@/components/screen/config-panels/ButtonConfigPanel"), + // v2-button-primary: hidden 호환 — InvLegacy 패널 사용 (옛 화면 config 스키마 보존) + "v2-button-primary": () => import("@/components/v2/config-panels/InvLegacyButtonConfigPanel"), // ========== 표시 컴포넌트 ========== "text-display": () => import("@/lib/registry/components/text-display/TextDisplayConfigPanel"), - "v2-text-display": () => import("@/lib/registry/components/v2-text-display/TextDisplayConfigPanel"), + // v2-text-display: hidden 호환 — InvLegacy 패널 사용 + "v2-text-display": () => import("@/components/v2/config-panels/InvLegacyTextConfigPanel"), "image-display": () => import("@/lib/registry/components/image-display/ImageDisplayConfigPanel"), "divider-line": () => import("@/lib/registry/components/divider-line/DividerLineConfigPanel"), - "v2-divider-line": () => import("@/lib/registry/components/v2-divider-line/DividerLineConfigPanel"), + // v2-divider-line: hidden 호환 — InvLegacy 패널 사용 + "v2-divider-line": () => import("@/components/v2/config-panels/InvLegacyDividerConfigPanel"), "image-widget": () => import("@/lib/registry/components/image-widget/ImageWidgetConfigPanel"), // ========== 레이아웃/컨테이너 ========== @@ -123,9 +126,9 @@ const CONFIG_PANEL_MAP: Record Promise> = { "v2-bom-tree": () => import("@/components/v2/config-panels/V2BomTreeConfigPanel"), // ========== 레거시 위젯 (component/onUpdateProperty props 사용) ========== + // ★ "stats" key 는 위 INVYONE 통합 섹션에서 정의됨 — 여기 중복 정의 금지 (덮어씀 → 통합 패널 안 보임) "card": () => import("@/components/screen/config-panels/CardConfigPanel"), "dashboard": () => import("@/components/screen/config-panels/DashboardConfigPanel"), - "stats": () => import("@/components/screen/config-panels/StatsCardConfigPanel"), "stats-card": () => import("@/components/screen/config-panels/StatsCardConfigPanel"), "progress": () => import("@/components/screen/config-panels/ProgressBarConfigPanel"), "progress-bar": () => import("@/components/screen/config-panels/ProgressBarConfigPanel"), @@ -143,10 +146,13 @@ const configPanelCache = new Map>(); * 컴포넌트 ID로 ConfigPanel 컴포넌트를 동적으로 로드 */ // ── Phase E: v2-* → 통합 컴포넌트 ConfigPanel alias ── +// ★ v2-button-primary / v2-divider-line / v2-text-display 는 alias 제외: +// hidden 호환 컴포넌트의 옛 화면 config 스키마가 통합 컴포넌트와 다르므로 +// InvLegacy 패널 (위 CONFIG_PANEL_MAP) 로 직접 매핑되어야 함. const CONFIG_PANEL_ALIAS: Record = { - "v2-divider-line": "divider", "divider-line": "divider", "v2-split-line": "divider", - "v2-text-display": "title", "text-display": "title", - "v2-button-primary": "button", "button-primary": "button", + "divider-line": "divider", "v2-split-line": "divider", + "text-display": "title", + "button-primary": "button", "v2-table-search-widget": "search", "table-search-widget": "search", "v2-input": "input", "v2-select": "input", "v2-date": "input", "text-input": "input", "number-input": "input", "date-input": "input",