refactor: ConfigPanel Inv 네이밍 통합 + legacy 패널 분리 + input cp 마이그
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) <noreply@anthropic.com>
This commit is contained in:
@@ -213,8 +213,8 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
|
||||
// 🆕 V2 컴포넌트 직접 감지 및 설정 패널 렌더링
|
||||
if (componentId?.startsWith("v2-")) {
|
||||
const v2ConfigPanels: Record<string, React.FC<{ config: any; onChange: (config: any) => 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,
|
||||
|
||||
+5
-4
@@ -44,6 +44,7 @@ import {
|
||||
CPSwitch,
|
||||
CPNumber,
|
||||
CPSegment,
|
||||
CPColor,
|
||||
CPIconBtn,
|
||||
CPCrumb,
|
||||
CPFormatTrigger,
|
||||
@@ -407,7 +408,7 @@ function applyTriple(
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
interface V2FieldConfigPanelProps {
|
||||
interface InvFieldConfigPanelProps {
|
||||
config: Record<string, any>;
|
||||
onChange: (config: Record<string, any>) => void;
|
||||
tableName?: string;
|
||||
@@ -419,7 +420,7 @@ interface V2FieldConfigPanelProps {
|
||||
componentType?: string;
|
||||
}
|
||||
|
||||
export const V2FieldConfigPanel: React.FC<V2FieldConfigPanelProps> = ({
|
||||
export const InvFieldConfigPanel: React.FC<InvFieldConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
tableName,
|
||||
@@ -786,7 +787,7 @@ export const V2FieldConfigPanel: React.FC<V2FieldConfigPanelProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
V2FieldConfigPanel.displayName = "V2FieldConfigPanel";
|
||||
InvFieldConfigPanel.displayName = "InvFieldConfigPanel";
|
||||
|
||||
// ───────────────────────────────────────────────────────
|
||||
// 유틸 컴포넌트
|
||||
@@ -2188,4 +2189,4 @@ const FilterConditionsSection: React.FC<{
|
||||
);
|
||||
};
|
||||
|
||||
export default V2FieldConfigPanel;
|
||||
export default InvFieldConfigPanel;
|
||||
+5
-5
@@ -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<string, any>;
|
||||
onChange: (config: Record<string, any>) => void;
|
||||
component?: ComponentData;
|
||||
@@ -147,7 +147,7 @@ interface InvButtonConfigPanelProps {
|
||||
// ───────────────────────────────────────────────────────
|
||||
// Main Component
|
||||
// ───────────────────────────────────────────────────────
|
||||
export const InvButtonConfigPanel: React.FC<InvButtonConfigPanelProps> = ({
|
||||
export const InvLegacyButtonConfigPanel: React.FC<InvLegacyButtonConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
component,
|
||||
@@ -582,7 +582,7 @@ export const InvButtonConfigPanel: React.FC<InvButtonConfigPanelProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
InvButtonConfigPanel.displayName = "InvButtonConfigPanel";
|
||||
InvLegacyButtonConfigPanel.displayName = "InvLegacyButtonConfigPanel";
|
||||
|
||||
// ───────────────────────────────────────────────────────
|
||||
// describeAction — ④ 섹션 desc 동적 텍스트
|
||||
@@ -1712,4 +1712,4 @@ function TransferDataMappingBody(p: TransferDataMappingBodyProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export default InvButtonConfigPanel;
|
||||
export default InvLegacyButtonConfigPanel;
|
||||
+5
-5
@@ -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<string, any>;
|
||||
onChange: (config: Record<string, any>) => void;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ interface InvDividerConfigPanelProps {
|
||||
const DEFAULT_LINE_COLOR = "#d1d5db";
|
||||
const DEFAULT_TEXT_COLOR = "#6b7280";
|
||||
|
||||
export const InvDividerConfigPanel: React.FC<InvDividerConfigPanelProps> = ({
|
||||
export const InvLegacyDividerConfigPanel: React.FC<InvLegacyDividerConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
}) => {
|
||||
@@ -162,7 +162,7 @@ export const InvDividerConfigPanel: React.FC<InvDividerConfigPanelProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
+5
-5
@@ -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<TextDisplayConfig>) => void;
|
||||
}
|
||||
|
||||
export const InvTextConfigPanel: React.FC<InvTextConfigPanelProps> = ({ config, onChange }) => {
|
||||
export const InvLegacyTextConfigPanel: React.FC<InvLegacyTextConfigPanelProps> = ({ 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<InvTextConfigPanelProps> = ({ config,
|
||||
);
|
||||
};
|
||||
|
||||
InvTextConfigPanel.displayName = "InvTextConfigPanel";
|
||||
InvLegacyTextConfigPanel.displayName = "InvLegacyTextConfigPanel";
|
||||
|
||||
export default InvTextConfigPanel;
|
||||
export default InvLegacyTextConfigPanel;
|
||||
+6
-6
@@ -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<ButtonConfigPanelProps> = ({
|
||||
export const InvButtonConfigPanel: React.FC<InvButtonConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
selectedComponent,
|
||||
@@ -273,7 +273,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
ButtonConfigPanel.displayName = "ButtonConfigPanel";
|
||||
InvButtonConfigPanel.displayName = "InvButtonConfigPanel";
|
||||
|
||||
// ───────────────────────────────────────────────────────
|
||||
// ActionCardGrid — 2단계 선택 (그룹 segment + 그 안 chip)
|
||||
@@ -426,4 +426,4 @@ function ActionChip({
|
||||
);
|
||||
}
|
||||
|
||||
export default ButtonConfigPanel;
|
||||
export default InvButtonConfigPanel;
|
||||
@@ -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<string, any>,
|
||||
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";
|
||||
|
||||
+5
-5
@@ -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<ContainerConfigPanelProps> = ({
|
||||
export const InvContainerConfigPanel: React.FC<InvContainerConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
selectedComponent,
|
||||
@@ -377,6 +377,6 @@ export const ContainerConfigPanel: React.FC<ContainerConfigPanelProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
ContainerConfigPanel.displayName = "ContainerConfigPanel";
|
||||
InvContainerConfigPanel.displayName = "InvContainerConfigPanel";
|
||||
|
||||
export default ContainerConfigPanel;
|
||||
export default InvContainerConfigPanel;
|
||||
@@ -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<string, any>,
|
||||
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";
|
||||
|
||||
+5
-5
@@ -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<DividerConfigPanelProps> = ({
|
||||
export const InvDividerConfigPanel: React.FC<InvDividerConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
onUpdateProperty,
|
||||
@@ -189,6 +189,6 @@ export const DividerConfigPanel: React.FC<DividerConfigPanelProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
DividerConfigPanel.displayName = "DividerConfigPanel";
|
||||
InvDividerConfigPanel.displayName = "InvDividerConfigPanel";
|
||||
|
||||
export default DividerConfigPanel;
|
||||
export default InvDividerConfigPanel;
|
||||
@@ -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<string, any>,
|
||||
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";
|
||||
|
||||
@@ -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<InputConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
selectedComponent,
|
||||
}) => {
|
||||
const current: InputConfig =
|
||||
(config as InputConfig) || (selectedComponent?.config as InputConfig) || {};
|
||||
|
||||
const patch = (p: Partial<InputConfig>) => {
|
||||
onChange?.({ ...current, ...p });
|
||||
};
|
||||
|
||||
const type: InputFieldType = (current.type as InputFieldType) || "text";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 p-3 text-xs">
|
||||
{/* ─── 타입 (최상위) ─── */}
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.62rem] font-semibold tracking-wider uppercase">
|
||||
필드 타입 ⭐
|
||||
</label>
|
||||
<select
|
||||
value={type}
|
||||
onChange={(e) => patch({ type: e.target.value as InputFieldType })}
|
||||
className="border-border bg-background w-full rounded border px-2 py-1 text-xs"
|
||||
>
|
||||
<option value="text">text — 일반 문자열</option>
|
||||
<option value="number">number — 숫자</option>
|
||||
<option value="date">date — 날짜</option>
|
||||
<option value="datetime">datetime — 날짜+시간</option>
|
||||
<option value="select">select — 드롭다운</option>
|
||||
<option value="entity">entity — FK 참조 (팝업 검색)</option>
|
||||
<option value="checkbox">checkbox — 체크박스</option>
|
||||
<option value="textarea">textarea — 장문</option>
|
||||
<option value="file">file — 파일 첨부</option>
|
||||
<option value="code">code — 자동채번 (readonly)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* ─── 라벨 / placeholder ─── */}
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.62rem] font-semibold tracking-wider uppercase">
|
||||
라벨
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={current.label || ""}
|
||||
onChange={(e) => patch({ label: e.target.value })}
|
||||
placeholder="필드 라벨"
|
||||
className="border-border bg-background w-full rounded border px-2 py-1 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.62rem] font-semibold tracking-wider uppercase">
|
||||
placeholder
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={current.placeholder || ""}
|
||||
onChange={(e) => patch({ placeholder: e.target.value })}
|
||||
placeholder="입력 안내"
|
||||
className="border-border bg-background w-full rounded border px-2 py-1 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.62rem] font-semibold tracking-wider uppercase">
|
||||
도움말 텍스트
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={current.helperText || ""}
|
||||
onChange={(e) => patch({ helperText: e.target.value || undefined })}
|
||||
placeholder="하단 도움말"
|
||||
className="border-border bg-background w-full rounded border px-2 py-1 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ─── 기본 토글 ─── */}
|
||||
<label className="flex items-center gap-2 text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!current.required}
|
||||
onChange={(e) => patch({ required: e.target.checked })}
|
||||
/>
|
||||
<span>필수 입력</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2 text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={current.editable !== false}
|
||||
onChange={(e) => patch({ editable: e.target.checked })}
|
||||
/>
|
||||
<span>편집 가능</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2 text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!current.disabled}
|
||||
onChange={(e) => patch({ disabled: e.target.checked })}
|
||||
/>
|
||||
<span>비활성화</span>
|
||||
</label>
|
||||
|
||||
{/* ─── type 별 하위 옵션 ─── */}
|
||||
{(type === "number") && (
|
||||
<>
|
||||
<div className="border-border mt-2 border-t pt-2">
|
||||
<div className="text-muted-foreground mb-2 text-[0.6rem] font-semibold tracking-wider uppercase">
|
||||
number 옵션
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.55rem]">min</label>
|
||||
<input
|
||||
type="number"
|
||||
value={current.min ?? ""}
|
||||
onChange={(e) => 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]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.55rem]">max</label>
|
||||
<input
|
||||
type="number"
|
||||
value={current.max ?? ""}
|
||||
onChange={(e) => 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]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.55rem]">step</label>
|
||||
<input
|
||||
type="number"
|
||||
value={current.step ?? ""}
|
||||
onChange={(e) => 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]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{(type === "text" || type === "textarea") && (
|
||||
<>
|
||||
<div className="border-border mt-2 border-t pt-2">
|
||||
<div className="text-muted-foreground mb-2 text-[0.6rem] font-semibold tracking-wider uppercase">
|
||||
문자열 길이
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.55rem]">minLength</label>
|
||||
<input
|
||||
type="number"
|
||||
value={current.minLength ?? ""}
|
||||
onChange={(e) => 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]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.55rem]">maxLength</label>
|
||||
<input
|
||||
type="number"
|
||||
value={current.maxLength ?? ""}
|
||||
onChange={(e) => 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]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === "textarea" && (
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.62rem] font-semibold tracking-wider uppercase">
|
||||
textarea 줄 수
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={current.rows ?? 3}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{type === "select" && (
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.62rem] font-semibold tracking-wider uppercase">
|
||||
select 선택지 (쉼표 구분)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={Array.isArray(current.options) ? current.options.map((o: any) => (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"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{type === "file" && (
|
||||
<>
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.62rem] font-semibold tracking-wider uppercase">
|
||||
허용 확장자
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={current.accept || ""}
|
||||
onChange={(e) => patch({ accept: e.target.value || undefined })}
|
||||
placeholder="예: image/*, .pdf"
|
||||
className="border-border bg-background w-full rounded border px-2 py-1 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!current.multiple}
|
||||
onChange={(e) => patch({ multiple: e.target.checked })}
|
||||
/>
|
||||
<span>다중 선택</span>
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
|
||||
{(type === "number" || type === "date" || type === "datetime") && (
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-[0.62rem] font-semibold tracking-wider uppercase">
|
||||
포맷 (format)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={current.format || ""}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputConfigPanel;
|
||||
@@ -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: <Type size={16} />, desc: "일반 텍스트 한 줄" },
|
||||
{ value: "number", label: "숫자", icon: <Hash size={16} />, desc: "정수 / 소수 입력" },
|
||||
{ value: "date", label: "날짜", icon: <Calendar size={16} />, desc: "YYYY-MM-DD" },
|
||||
{ value: "datetime", label: "날짜+시간", icon: <CalendarClock size={16} />, desc: "분/초까지" },
|
||||
{ value: "select", label: "드롭다운", icon: <ChevronDown size={16} />, desc: "선택지 list" },
|
||||
{ value: "entity", label: "엔티티", icon: <Link2 size={16} />, desc: "FK 참조 (팝업)" },
|
||||
{ value: "checkbox", label: "체크박스", icon: <CheckSquare size={16} />, desc: "참/거짓" },
|
||||
{ value: "textarea", label: "장문", icon: <AlignLeft size={16} />, desc: "여러 줄 텍스트" },
|
||||
{ value: "file", label: "파일", icon: <Paperclip size={16} />, desc: "업로드" },
|
||||
{ value: "code", label: "자동채번", icon: <Code2 size={16} />, desc: "readonly · 자동 생성" },
|
||||
];
|
||||
|
||||
export const InvInputConfigPanel: React.FC<InvInputConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
selectedComponent,
|
||||
}) => {
|
||||
const current: InputConfig =
|
||||
(config as InputConfig) || (selectedComponent?.config as InputConfig) || {};
|
||||
|
||||
const patch = (p: Partial<InputConfig>) => 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 (
|
||||
<div style={{ fontFamily: "var(--v5-font-sans)", color: "var(--cp-text)", padding: "0 12px" }}>
|
||||
{/* ── ① 필드 타입 ─────────────────────────── */}
|
||||
<CPSection title="① 필드 타입" desc="입력 모드 (변형 결정)">
|
||||
<CPVisualGrid
|
||||
cols={5}
|
||||
cardHeight={62}
|
||||
value={type}
|
||||
onChange={(v) => patch({ type: v as InputFieldType })}
|
||||
options={TYPE_OPTIONS.map((o) => ({
|
||||
value: o.value,
|
||||
label: o.label,
|
||||
preview: o.icon,
|
||||
desc: o.desc,
|
||||
}))}
|
||||
/>
|
||||
</CPSection>
|
||||
|
||||
{/* ── ② 라벨 / 안내 ─────────────────────────── */}
|
||||
<CPSection title="② 라벨 / 안내">
|
||||
<CPRow label="라벨" help="필드 위에 표시될 이름">
|
||||
<CPText
|
||||
value={current.label || ""}
|
||||
onChange={(v) => patch({ label: v })}
|
||||
placeholder="필드 라벨"
|
||||
/>
|
||||
</CPRow>
|
||||
<CPRow label="placeholder" help="입력 전 안내 텍스트">
|
||||
<CPText
|
||||
value={current.placeholder || ""}
|
||||
onChange={(v) => patch({ placeholder: v })}
|
||||
placeholder="입력하세요"
|
||||
/>
|
||||
</CPRow>
|
||||
<CPRow label="도움말" help="필드 하단 보조 안내">
|
||||
<CPText
|
||||
value={current.helperText || ""}
|
||||
onChange={(v) => patch({ helperText: v || undefined })}
|
||||
placeholder="하단 도움말"
|
||||
/>
|
||||
</CPRow>
|
||||
</CPSection>
|
||||
|
||||
{/* ── ③ 타입별 옵션 (조건부) ─────────────────────────── */}
|
||||
{type === "number" && (
|
||||
<CPSection title="③ 숫자 옵션" desc="범위 / 증감">
|
||||
<CPRow label="최소 (min)">
|
||||
<CPNumber
|
||||
value={current.min ?? undefined}
|
||||
onChange={(v) => patch({ min: v })}
|
||||
placeholder="제한 없음"
|
||||
/>
|
||||
</CPRow>
|
||||
<CPRow label="최대 (max)">
|
||||
<CPNumber
|
||||
value={current.max ?? undefined}
|
||||
onChange={(v) => patch({ max: v })}
|
||||
placeholder="제한 없음"
|
||||
/>
|
||||
</CPRow>
|
||||
<CPRow label="증감 단위 (step)" help="화살표/스피너 한 칸">
|
||||
<CPNumber
|
||||
value={current.step ?? undefined}
|
||||
onChange={(v) => patch({ step: v })}
|
||||
placeholder="1"
|
||||
/>
|
||||
</CPRow>
|
||||
<CPRow label="포맷" help="예: #,##0 / 0.00">
|
||||
<CPText
|
||||
value={current.format || ""}
|
||||
onChange={(v) => patch({ format: v || undefined })}
|
||||
placeholder="#,##0"
|
||||
/>
|
||||
</CPRow>
|
||||
</CPSection>
|
||||
)}
|
||||
|
||||
{(type === "text" || type === "textarea") && (
|
||||
<CPSection title="③ 문자열 옵션">
|
||||
<CPRow label="최소 길이 (minLength)">
|
||||
<CPNumber
|
||||
value={current.minLength ?? undefined}
|
||||
onChange={(v) => patch({ minLength: v })}
|
||||
min={0}
|
||||
placeholder="제한 없음"
|
||||
/>
|
||||
</CPRow>
|
||||
<CPRow label="최대 길이 (maxLength)">
|
||||
<CPNumber
|
||||
value={current.maxLength ?? undefined}
|
||||
onChange={(v) => patch({ maxLength: v })}
|
||||
min={0}
|
||||
placeholder="제한 없음"
|
||||
/>
|
||||
</CPRow>
|
||||
{type === "textarea" && (
|
||||
<CPRow label="줄 수 (rows)">
|
||||
<CPNumber
|
||||
value={current.rows ?? 3}
|
||||
onChange={(v) => patch({ rows: v ?? 3 })}
|
||||
min={1}
|
||||
max={20}
|
||||
/>
|
||||
</CPRow>
|
||||
)}
|
||||
</CPSection>
|
||||
)}
|
||||
|
||||
{(type === "date" || type === "datetime") && (
|
||||
<CPSection title="③ 날짜 옵션">
|
||||
<CPRow label="포맷" help={type === "date" ? "예: YYYY-MM-DD" : "예: YYYY-MM-DD HH:mm"}>
|
||||
<CPText
|
||||
value={current.format || ""}
|
||||
onChange={(v) => patch({ format: v || undefined })}
|
||||
placeholder={type === "date" ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm"}
|
||||
/>
|
||||
</CPRow>
|
||||
</CPSection>
|
||||
)}
|
||||
|
||||
{type === "select" && (
|
||||
<CPSection title="③ 선택지" desc={`${optionsArr.length}개`}>
|
||||
<div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 5 }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={addOption}
|
||||
style={{
|
||||
padding: "4px 10px",
|
||||
fontSize: 10.5,
|
||||
background: "var(--cp-bg-subtle)",
|
||||
border: "1px solid var(--cp-border)",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer",
|
||||
color: "var(--cp-text)",
|
||||
fontFamily: "var(--v5-font-sans)",
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<Plus size={10} /> 추가
|
||||
</button>
|
||||
</div>
|
||||
{optionsArr.length === 0 ? (
|
||||
<Hint>선택지가 없습니다. [+ 추가] 로 만드세요.</Hint>
|
||||
) : (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
{optionsArr.map((opt, i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "16px 1fr 22px",
|
||||
alignItems: "center",
|
||||
columnGap: 6,
|
||||
padding: "3px 6px",
|
||||
background: "var(--cp-bg-subtle)",
|
||||
border: "1px solid var(--cp-border-subtle)",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 9,
|
||||
color: "var(--cp-text-muted)",
|
||||
fontFamily: "var(--v5-font-mono)",
|
||||
textAlign: "right",
|
||||
}}
|
||||
>
|
||||
{i + 1}
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
value={opt}
|
||||
onChange={(e) => 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)",
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeOption(i)}
|
||||
style={{
|
||||
width: 22,
|
||||
height: 22,
|
||||
padding: 0,
|
||||
background: "transparent",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
color: "var(--v5-red, #ef4444)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<X size={10} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CPSection>
|
||||
)}
|
||||
|
||||
{type === "file" && (
|
||||
<CPSection title="③ 파일 옵션">
|
||||
<CPRow label="허용 확장자" help="예: image/* 또는 .pdf,.docx">
|
||||
<CPText
|
||||
value={current.accept || ""}
|
||||
onChange={(v) => patch({ accept: v || undefined })}
|
||||
placeholder="image/*"
|
||||
/>
|
||||
</CPRow>
|
||||
<FeatureChipGrid
|
||||
items={[
|
||||
{
|
||||
key: "multiple",
|
||||
label: "다중 선택",
|
||||
desc: "한 번에 여러 파일을 선택할 수 있어요.\nOFF 일 때는 한 파일씩 교체.",
|
||||
},
|
||||
]}
|
||||
source={current as any}
|
||||
onToggle={(k, v) => patch({ [k]: v } as Partial<InputConfig>)}
|
||||
/>
|
||||
</CPSection>
|
||||
)}
|
||||
|
||||
{type === "entity" && (
|
||||
<CPSection title="③ 엔티티 참조" desc="FK 참조 — 팝업에서 행 선택">
|
||||
<Hint tone="warn">
|
||||
FK 참조 picker UI 는 추후 추가 예정. 현재는 type 만 저장 (백엔드 키 보존).
|
||||
</Hint>
|
||||
</CPSection>
|
||||
)}
|
||||
|
||||
{type === "code" && (
|
||||
<CPSection title="③ 자동채번" desc="readonly · 저장 시 자동 생성">
|
||||
<Hint>채번 규칙은 별도 설정에서 관리합니다.</Hint>
|
||||
</CPSection>
|
||||
)}
|
||||
|
||||
{type === "checkbox" && (
|
||||
<CPSection title="③ 체크박스">
|
||||
<Hint>체크박스는 추가 옵션이 없습니다 (참/거짓).</Hint>
|
||||
</CPSection>
|
||||
)}
|
||||
|
||||
{/* ── ▾ 옵션 ─────────────────────────── */}
|
||||
<CPGroup title="옵션" defaultOpen>
|
||||
<FeatureChipGrid
|
||||
items={[
|
||||
{
|
||||
key: "required",
|
||||
label: "필수",
|
||||
desc: "비어 있으면 저장 시 검증 오류.\n라벨 우측에 빨간 * 표시.",
|
||||
},
|
||||
{
|
||||
key: "editable",
|
||||
label: "편집 가능",
|
||||
default: true,
|
||||
desc: "사용자가 값을 수정할 수 있어요.\nOFF = 화면에는 보이되 편집 불가 (readonly).",
|
||||
},
|
||||
{
|
||||
key: "disabled",
|
||||
label: "비활성화",
|
||||
desc: "필드 자체를 회색 처리하고 입력/포커스 차단.\n조건부 표시와 함께 자주 쓰여요.",
|
||||
},
|
||||
]}
|
||||
source={current as any}
|
||||
onToggle={(k, v) => patch({ [k]: v } as Partial<InputConfig>)}
|
||||
/>
|
||||
</CPGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
InvInputConfigPanel.displayName = "InvInputConfigPanel";
|
||||
|
||||
export default InvInputConfigPanel;
|
||||
@@ -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<string, any>,
|
||||
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";
|
||||
|
||||
+5
-5
@@ -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<SearchField["type"], string> = {
|
||||
select: "선택",
|
||||
};
|
||||
|
||||
export const SearchConfigPanel: React.FC<SearchConfigPanelProps> = ({
|
||||
export const InvSearchConfigPanel: React.FC<InvSearchConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
selectedComponent,
|
||||
@@ -287,7 +287,7 @@ export const SearchConfigPanel: React.FC<SearchConfigPanelProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
SearchConfigPanel.displayName = "SearchConfigPanel";
|
||||
InvSearchConfigPanel.displayName = "InvSearchConfigPanel";
|
||||
|
||||
// ───────────────────────────────────────────────────────
|
||||
// SearchFieldRow — Dense list 한 줄 (높이 ~24px, 카드 박스 폐기)
|
||||
@@ -424,4 +424,4 @@ function SearchFieldRow({
|
||||
);
|
||||
}
|
||||
|
||||
export default SearchConfigPanel;
|
||||
export default InvSearchConfigPanel;
|
||||
@@ -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<string, any>,
|
||||
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";
|
||||
|
||||
+5
-5
@@ -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<StatsConfigPanelProps> = ({
|
||||
export const InvStatsConfigPanel: React.FC<InvStatsConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
selectedComponent,
|
||||
@@ -346,7 +346,7 @@ export const StatsConfigPanel: React.FC<StatsConfigPanelProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -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<string, any>,
|
||||
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";
|
||||
|
||||
+5
-5
@@ -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<TableConfigPanelProps> = ({
|
||||
export const InvTableConfigPanel: React.FC<InvTableConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
selectedComponent,
|
||||
@@ -470,7 +470,7 @@ export const TableConfigPanel: React.FC<TableConfigPanelProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -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<string, any>,
|
||||
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";
|
||||
|
||||
+5
-5
@@ -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<TitleConfigPanelProps> = ({
|
||||
export const InvTitleConfigPanel: React.FC<InvTitleConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
onUpdateProperty,
|
||||
@@ -199,6 +199,6 @@ export const TitleConfigPanel: React.FC<TitleConfigPanelProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
TitleConfigPanel.displayName = "TitleConfigPanel";
|
||||
InvTitleConfigPanel.displayName = "InvTitleConfigPanel";
|
||||
|
||||
export default TitleConfigPanel;
|
||||
export default InvTitleConfigPanel;
|
||||
@@ -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<string, any>,
|
||||
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";
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<string, () => Promise<any>> = {
|
||||
// ========== 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<string, () => Promise<any>> = {
|
||||
|
||||
// ========== 버튼 ==========
|
||||
"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<string, () => Promise<any>> = {
|
||||
"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<string, React.ComponentType<any>>();
|
||||
* 컴포넌트 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<string, string> = {
|
||||
"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",
|
||||
|
||||
Reference in New Issue
Block a user