diff --git a/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx b/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx new file mode 100644 index 00000000..66c7b698 --- /dev/null +++ b/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx @@ -0,0 +1,162 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { ConditionalContainerProps, ConditionalSection } from "./types"; +import { ConditionalSectionViewer } from "./ConditionalSectionViewer"; +import { cn } from "@/lib/utils"; + +/** + * 조건부 컨테이너 컴포넌트 + * 상단 셀렉트박스 값에 따라 하단에 다른 UI를 표시 + */ +export function ConditionalContainerComponent({ + config, + controlField: propControlField, + controlLabel: propControlLabel, + sections: propSections, + defaultValue: propDefaultValue, + showBorder: propShowBorder, + spacing: propSpacing, + value, + onChange, + formData, + onFormDataChange, + isDesignMode = false, + onUpdateComponent, + onDeleteComponent, + onSelectComponent, + selectedComponentId, + style, + className, +}: ConditionalContainerProps) { + // config prop 우선, 없으면 개별 prop 사용 + const controlField = config?.controlField || propControlField || "condition"; + const controlLabel = config?.controlLabel || propControlLabel || "조건 선택"; + const sections = config?.sections || propSections || []; + const defaultValue = config?.defaultValue || propDefaultValue || sections[0]?.condition; + const showBorder = config?.showBorder ?? propShowBorder ?? true; + const spacing = config?.spacing || propSpacing || "normal"; + + // 현재 선택된 값 + const [selectedValue, setSelectedValue] = useState( + value || formData?.[controlField] || defaultValue || "" + ); + + // formData 변경 시 동기화 + useEffect(() => { + if (formData?.[controlField]) { + setSelectedValue(formData[controlField]); + } + }, [formData, controlField]); + + // 값 변경 핸들러 + const handleValueChange = (newValue: string) => { + setSelectedValue(newValue); + + if (onChange) { + onChange(newValue); + } + + if (onFormDataChange) { + onFormDataChange(controlField, newValue); + } + }; + + + // 간격 스타일 + const spacingClass = { + tight: "space-y-2", + normal: "space-y-4", + loose: "space-y-8", + }[spacing]; + + return ( +
+ {/* 제어 셀렉트박스 */} +
+ + +
+ + {/* 조건별 섹션들 */} +
+ {isDesignMode ? ( + // 디자인 모드: 모든 섹션 표시 +
+ {sections.map((section) => ( + + ))} +
+ ) : ( + // 실행 모드: 활성 섹션만 표시 + sections.map((section) => + selectedValue === section.condition ? ( + + ) : null + ) + )} + + {/* 섹션이 없는 경우 안내 */} + {sections.length === 0 && isDesignMode && ( +
+

+ 설정 패널에서 조건을 추가하세요 +

+
+ )} +
+
+ ); +} + diff --git a/frontend/lib/registry/components/conditional-container/ConditionalContainerConfigPanel.tsx b/frontend/lib/registry/components/conditional-container/ConditionalContainerConfigPanel.tsx new file mode 100644 index 00000000..1d7f3c0c --- /dev/null +++ b/frontend/lib/registry/components/conditional-container/ConditionalContainerConfigPanel.tsx @@ -0,0 +1,336 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Plus, Trash2, GripVertical, Loader2 } from "lucide-react"; +import { ConditionalContainerConfig, ConditionalSection } from "./types"; +import { screenManagementApi } from "@/lib/api/screenManagement"; + +interface ConditionalContainerConfigPanelProps { + config: ConditionalContainerConfig; + onConfigChange: (config: ConditionalContainerConfig) => void; +} + +export function ConditionalContainerConfigPanel({ + config, + onConfigChange, +}: ConditionalContainerConfigPanelProps) { + const [localConfig, setLocalConfig] = useState({ + controlField: config.controlField || "condition", + controlLabel: config.controlLabel || "조건 선택", + sections: config.sections || [], + defaultValue: config.defaultValue || "", + showBorder: config.showBorder ?? true, + spacing: config.spacing || "normal", + }); + + // 화면 목록 상태 + const [screens, setScreens] = useState([]); + const [screensLoading, setScreensLoading] = useState(false); + + // 화면 목록 로드 + useEffect(() => { + const loadScreens = async () => { + setScreensLoading(true); + try { + const response = await screenManagementApi.getScreenList(); + if (response.success && response.data) { + setScreens(response.data); + } + } catch (error) { + console.error("화면 목록 로드 실패:", error); + } finally { + setScreensLoading(false); + } + }; + loadScreens(); + }, []); + + // 설정 업데이트 헬퍼 + const updateConfig = (updates: Partial) => { + const newConfig = { ...localConfig, ...updates }; + setLocalConfig(newConfig); + onConfigChange(newConfig); + }; + + // 새 섹션 추가 + const addSection = () => { + const newSection: ConditionalSection = { + id: `section_${Date.now()}`, + condition: `condition_${localConfig.sections.length + 1}`, + label: `조건 ${localConfig.sections.length + 1}`, + screenId: null, + screenName: undefined, + }; + + updateConfig({ + sections: [...localConfig.sections, newSection], + }); + }; + + // 섹션 삭제 + const removeSection = (sectionId: string) => { + updateConfig({ + sections: localConfig.sections.filter((s) => s.id !== sectionId), + }); + }; + + // 섹션 업데이트 + const updateSection = ( + sectionId: string, + updates: Partial + ) => { + updateConfig({ + sections: localConfig.sections.map((s) => + s.id === sectionId ? { ...s, ...updates } : s + ), + }); + }; + + return ( +
+
+

조건부 컨테이너 설정

+ + {/* 제어 필드 설정 */} +
+
+ + updateConfig({ controlField: e.target.value })} + placeholder="예: inputMode" + className="h-8 text-xs" + /> +

+ formData에 저장될 필드명 +

+
+ +
+ + updateConfig({ controlLabel: e.target.value })} + placeholder="예: 입력 방식" + className="h-8 text-xs" + /> +
+
+ + {/* 조건별 섹션 설정 */} +
+
+ + +
+ + {localConfig.sections.length === 0 ? ( +
+

+ 조건별 섹션을 추가하세요 +

+
+ ) : ( +
+ {localConfig.sections.map((section, index) => ( +
+ {/* 섹션 헤더 */} +
+
+ + + 섹션 {index + 1} + +
+ +
+ + {/* 조건 값 */} +
+ + + updateSection(section.id, { condition: e.target.value }) + } + placeholder="예: customer_first" + className="h-7 text-xs" + /> +
+ + {/* 조건 라벨 */} +
+ + + updateSection(section.id, { label: e.target.value }) + } + placeholder="예: 거래처 우선" + className="h-7 text-xs" + /> +
+ + {/* 화면 선택 */} +
+ + {screensLoading ? ( +
+ + 로딩 중... +
+ ) : ( + + )} + {section.screenId && ( +
+ 화면 ID: {section.screenId} +
+ )} +
+
+ ))} +
+ )} +
+ + {/* 기본값 설정 */} + {localConfig.sections.length > 0 && ( +
+ + +
+ )} + + {/* 스타일 설정 */} +
+ + + {/* 테두리 표시 */} +
+ + + updateConfig({ showBorder: checked }) + } + /> +
+ + {/* 간격 설정 */} +
+ + +
+
+
+
+ ); +} + diff --git a/frontend/lib/registry/components/conditional-container/ConditionalContainerRenderer.tsx b/frontend/lib/registry/components/conditional-container/ConditionalContainerRenderer.tsx new file mode 100644 index 00000000..a057590a --- /dev/null +++ b/frontend/lib/registry/components/conditional-container/ConditionalContainerRenderer.tsx @@ -0,0 +1,20 @@ +"use client"; + +import React from "react"; +import { ComponentRegistry } from "@/lib/registry/ComponentRegistry"; +import ConditionalContainerDefinition from "./index"; +import { ConditionalContainerComponent } from "./ConditionalContainerComponent"; +import { ConditionalContainerConfigPanel } from "./ConditionalContainerConfigPanel"; + +// 컴포넌트 자동 등록 +if (typeof window !== "undefined") { + ComponentRegistry.registerComponent({ + ...ConditionalContainerDefinition, + component: ConditionalContainerComponent, + renderer: ConditionalContainerComponent, + configPanel: ConditionalContainerConfigPanel, + } as any); +} + +export { ConditionalContainerComponent }; + diff --git a/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx b/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx new file mode 100644 index 00000000..6ff42149 --- /dev/null +++ b/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx @@ -0,0 +1,89 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { ConditionalSectionViewerProps } from "./types"; +import { InteractiveScreenViewer } from "@/components/screen/InteractiveScreenViewer"; +import { cn } from "@/lib/utils"; +import { Loader2 } from "lucide-react"; + +/** + * 조건부 섹션 뷰어 컴포넌트 + * 각 조건에 해당하는 화면을 표시 + */ +export function ConditionalSectionViewer({ + sectionId, + condition, + label, + screenId, + screenName, + isActive, + isDesignMode, + showBorder = true, + formData, + onFormDataChange, +}: ConditionalSectionViewerProps) { + const [isLoading, setIsLoading] = useState(false); + + // 디자인 모드가 아니고 비활성 섹션이면 렌더링하지 않음 + if (!isDesignMode && !isActive) { + return null; + } + + return ( +
+ {/* 섹션 라벨 (디자인 모드에서만 표시) */} + {isDesignMode && ( +
+ {label} {isActive && "(활성)"} + {screenId && ` - 화면 ID: ${screenId}`} +
+ )} + + {/* 화면 미선택 안내 (디자인 모드 + 화면 없을 때) */} + {isDesignMode && !screenId && ( +
+
+

설정 패널에서 화면을 선택하세요

+

조건: {condition}

+
+
+ )} + + {/* 로딩 중 */} + {isLoading && ( +
+
+ +

화면 로드 중...

+
+
+ )} + + {/* 화면 렌더링 */} + {screenId && ( +
+ +
+ )} +
+ ); +} + diff --git a/frontend/lib/registry/components/conditional-container/README.md b/frontend/lib/registry/components/conditional-container/README.md new file mode 100644 index 00000000..bbd87911 --- /dev/null +++ b/frontend/lib/registry/components/conditional-container/README.md @@ -0,0 +1,293 @@ +# 조건부 컨테이너 (ConditionalContainer) - 화면 선택 방식 + +제어 셀렉트박스 값에 따라 다른 **화면**을 표시하는 조건부 컨테이너 컴포넌트입니다. + +## 📋 개요 + +화면 편집기에서 조건별로 표시할 화면을 선택하여 조건부 UI를 구성할 수 있는 컨테이너입니다. 상단의 셀렉트박스 값에 따라 하단에 미리 만들어진 화면을 표시합니다. + +## ✨ 주요 기능 + +- ✅ **조건별 화면 전환**: 셀렉트박스 값에 따라 다른 화면 표시 +- ✅ **화면 재사용**: 기존에 만든 화면을 조건별로 할당 +- ✅ **간편한 구성**: 복잡한 입력 폼도 화면 선택으로 간단히 구성 +- ✅ **자동 동기화**: 화면 수정 시 자동 반영 +- ✅ **폼 데이터 연동**: formData와 자동 동기화 +- ✅ **커스터마이징**: 테두리, 간격, 기본값 등 설정 가능 + +## 🎯 사용 사례 + +### 1. 입력 방식 선택 +``` +[셀렉트: 입력 방식] +├─ 거래처 우선: "거래처_우선_입력_화면" (화면 ID: 101) +├─ 견적서 기반: "견적서_업로드_화면" (화면 ID: 102) +└─ 단가 직접입력: "단가_직접입력_화면" (화면 ID: 103) +``` + +### 2. 판매 유형 선택 +``` +[셀렉트: 판매 유형] +├─ 국내 판매: "국내판매_기본폼" (화면 ID: 201) +└─ 해외 판매: "해외판매_무역정보폼" (화면 ID: 202) +``` + +### 3. 문서 유형 선택 +``` +[셀렉트: 문서 유형] +├─ 신규 작성: "신규문서_입력폼" (화면 ID: 301) +├─ 복사 생성: "문서복사_화면" (화면 ID: 302) +└─ 불러오기: "파일업로드_화면" (화면 ID: 303) +``` + +## 📐 구조 + +``` +┌─────────────────────────────────┐ +│ ConditionalContainer │ +├─────────────────────────────────┤ +│ [제어 셀렉트박스] │ ← controlField, controlLabel +├─────────────────────────────────┤ +│ 📄 조건 1: "옵션 A" 선택 시 │ ← sections[0] +│ ┌─────────────────────────────┐│ +│ │ [선택된 화면이 표시됨] ││ ← screenId로 지정된 화면 +│ │ (화면 ID: 101) ││ +│ │ ││ +│ └─────────────────────────────┘│ +├─────────────────────────────────┤ +│ 📄 조건 2: "옵션 B" 선택 시 │ ← sections[1] +│ ┌─────────────────────────────┐│ +│ │ [다른 화면이 표시됨] ││ ← screenId로 지정된 다른 화면 +│ │ (화면 ID: 102) ││ +│ └─────────────────────────────┘│ +└─────────────────────────────────┘ +``` + +## 🔧 설정 방법 + +### 1. 컴포넌트 추가 +화면 편집기의 컴포넌트 패널에서 **"조건부 컨테이너"**를 드래그하여 캔버스에 배치합니다. + +### 2. 설정 패널에서 구성 + +#### 제어 필드 설정 +- **제어 필드명**: formData에 저장될 필드명 (예: `inputMode`) +- **셀렉트박스 라벨**: 화면에 표시될 라벨 (예: "입력 방식") + +#### 조건별 섹션 추가 +1. **"섹션 추가"** 버튼 클릭 +2. 각 섹션 설정: + - **조건 값**: 고유한 값 (예: `customer_first`) + - **표시 라벨**: 사용자에게 보이는 텍스트 (예: "거래처 우선") + +#### 기본값 설정 +- 처음 화면 로드 시 선택될 기본 조건 선택 + +#### 스타일 설정 +- **섹션 테두리 표시**: ON/OFF +- **섹션 간격**: 좁게 / 보통 / 넓게 + +### 3. 조건별 화면 선택 + +1. **디자인 모드**에서 모든 조건 섹션이 표시됩니다 +2. 각 섹션의 **"표시할 화면"** 드롭다운에서 화면을 선택합니다 +3. 선택된 화면 ID와 이름이 자동으로 저장됩니다 + +**장점:** +- ✅ 이미 만든 화면을 재사용 +- ✅ 복잡한 입력 폼도 간단히 구성 +- ✅ 화면 수정 시 자동 반영 + +### 4. 실행 모드 동작 + +- 셀렉트박스에서 조건 선택 +- 선택된 조건의 **화면**이 표시됨 +- 다른 조건의 화면은 자동으로 숨김 + +## 💻 기술 사양 + +### Props + +```typescript +interface ConditionalContainerProps { + // 제어 필드 + controlField: string; // 예: "inputMode" + controlLabel: string; // 예: "입력 방식" + + // 조건별 섹션 + sections: ConditionalSection[]; + + // 기본값 + defaultValue?: string; + + // 스타일 + showBorder?: boolean; // 기본: true + spacing?: "tight" | "normal" | "loose"; // 기본: "normal" + + // 폼 연동 + formData?: Record; + onFormDataChange?: (fieldName: string, value: any) => void; +} + +interface ConditionalSection { + id: string; // 고유 ID + condition: string; // 조건 값 + label: string; // 표시 라벨 + screenId: number | null; // 표시할 화면 ID + screenName?: string; // 화면 이름 (표시용) +} +``` + +### 기본 설정 + +```typescript +defaultSize: { + width: 800, + height: 600, +} + +defaultConfig: { + controlField: "condition", + controlLabel: "조건 선택", + sections: [ + { + id: "section_1", + condition: "option1", + label: "옵션 1", + screenId: null, // 화면 미선택 상태 + }, + { + id: "section_2", + condition: "option2", + label: "옵션 2", + screenId: null, // 화면 미선택 상태 + }, + ], + defaultValue: "option1", + showBorder: true, + spacing: "normal", +} +``` + +## 🎨 디자인 모드 vs 실행 모드 + +### 디자인 모드 (편집기) +- ✅ 모든 조건 섹션 표시 +- ✅ 각 섹션에 "조건: XXX" 라벨 표시 +- ✅ 화면 선택 안내 메시지 (미선택 시) +- ✅ 선택된 화면 ID 표시 +- ✅ 활성 조건 "(활성)" 표시 + +### 실행 모드 (할당된 화면) +- ✅ 선택된 조건의 화면만 표시 +- ✅ 다른 조건의 화면 자동 숨김 +- ✅ 깔끔한 UI (라벨, 점선 테두리 제거) +- ✅ 선택된 화면이 완전히 통합되어 표시 + +## 📊 폼 데이터 연동 + +### 자동 동기화 +```typescript +// formData 읽기 +formData[controlField] // 현재 선택된 값 + +// formData 쓰기 +onFormDataChange(controlField, newValue) +``` + +### 예시 +```typescript +// controlField = "salesType" +formData = { + salesType: "export", // ← 자동으로 여기에 저장됨 + // ... 다른 필드들 +} + +// 셀렉트박스 값 변경 시 자동으로 formData 업데이트 +``` + +## 🔍 주의사항 + +1. **조건 값은 고유해야 함**: 각 섹션의 `condition` 값은 중복되면 안 됩니다 +2. **최소 1개 섹션 필요**: 섹션이 없으면 안내 메시지 표시 +3. **컴포넌트 ID 충돌 방지**: 각 섹션의 컴포넌트 ID는 전역적으로 고유해야 함 + +## 📝 예시: 수주 입력 방식 선택 + +```typescript +{ + controlField: "inputMode", + controlLabel: "입력 방식", + sections: [ + { + id: "customer_first", + condition: "customer_first", + label: "거래처 우선", + components: [ + // 거래처 검색 컴포넌트 + // 품목 선택 테이블 + // 저장 버튼 + ] + }, + { + id: "quotation", + condition: "quotation", + label: "견적서 기반", + components: [ + // 견적서 검색 컴포넌트 + // 견적서 내용 표시 + // 수주 전환 버튼 + ] + }, + { + id: "unit_price", + condition: "unit_price", + label: "단가 직접입력", + components: [ + // 품목 입력 테이블 + // 단가 입력 필드들 + // 계산 위젯 + ] + } + ], + defaultValue: "customer_first", + showBorder: true, + spacing: "normal" +} +``` + +## 🚀 로드맵 + +- [ ] 다중 제어 필드 지원 (AND/OR 조건) +- [ ] 섹션 전환 애니메이션 +- [ ] 조건별 검증 규칙 +- [ ] 템플릿 저장/불러오기 + +## 🐛 트러블슈팅 + +### Q: 섹션이 전환되지 않아요 +A: `controlField` 값이 formData에 제대로 저장되고 있는지 확인하세요. + +### Q: 컴포넌트가 드롭되지 않아요 +A: 디자인 모드인지 확인하고, 드롭존 영역에 정확히 드롭하세요. + +### Q: 다른 조건의 UI가 계속 보여요 +A: 실행 모드로 전환했는지 확인하세요. 디자인 모드에서는 모든 조건이 표시됩니다. + +## 📦 파일 구조 + +``` +conditional-container/ +├── types.ts # 타입 정의 +├── ConditionalContainerComponent.tsx # 메인 컴포넌트 +├── ConditionalSectionDropZone.tsx # 드롭존 컴포넌트 +├── ConditionalContainerConfigPanel.tsx # 설정 패널 +├── ConditionalContainerRenderer.tsx # 렌더러 및 등록 +├── index.ts # 컴포넌트 정의 +└── README.md # 이 파일 +``` + +## 🎉 완료! + +이제 화면 편집기에서 **조건부 컨테이너**를 사용하여 동적인 UI를 만들 수 있습니다! 🚀 + diff --git a/frontend/lib/registry/components/conditional-container/index.ts b/frontend/lib/registry/components/conditional-container/index.ts new file mode 100644 index 00000000..82592305 --- /dev/null +++ b/frontend/lib/registry/components/conditional-container/index.ts @@ -0,0 +1,96 @@ +/** + * 조건부 컨테이너 컴포넌트 + * 제어 셀렉트박스 값에 따라 다른 UI를 표시하는 컨테이너 + */ + +import { ComponentDefinition, ComponentCategory } from "@/types/component"; + +export const ConditionalContainerDefinition: Omit< + ComponentDefinition, + "renderer" | "configPanel" | "component" +> = { + id: "conditional-container", + name: "조건부 컨테이너", + category: ComponentCategory.LAYOUT, + webType: "container" as const, + description: "셀렉트박스 값에 따라 다른 UI를 표시하는 조건부 컨테이너", + icon: "GitBranch", + version: "1.0.0", + author: "WACE", + tags: ["조건부", "분기", "동적", "레이아웃"], + + defaultSize: { + width: 800, + height: 600, + }, + + defaultConfig: { + controlField: "condition", + controlLabel: "조건 선택", + sections: [ + { + id: "section_1", + condition: "option1", + label: "옵션 1", + screenId: null, + }, + { + id: "section_2", + condition: "option2", + label: "옵션 2", + screenId: null, + }, + ], + defaultValue: "option1", + showBorder: true, + spacing: "normal", + }, + + defaultProps: { + style: { + width: "800px", + height: "600px", + }, + }, + + configSchema: { + controlField: { + type: "string", + label: "제어 필드명", + defaultValue: "condition", + }, + controlLabel: { + type: "string", + label: "셀렉트박스 라벨", + defaultValue: "조건 선택", + }, + sections: { + type: "array", + label: "조건별 섹션", + defaultValue: [], + }, + defaultValue: { + type: "string", + label: "기본 선택 값", + defaultValue: "", + }, + showBorder: { + type: "boolean", + label: "섹션 테두리 표시", + defaultValue: true, + }, + spacing: { + type: "select", + label: "섹션 간격", + options: [ + { label: "좁게", value: "tight" }, + { label: "보통", value: "normal" }, + { label: "넓게", value: "loose" }, + ], + defaultValue: "normal", + }, + }, +}; + +export default ConditionalContainerDefinition; + diff --git a/frontend/lib/registry/components/conditional-container/types.ts b/frontend/lib/registry/components/conditional-container/types.ts new file mode 100644 index 00000000..b0893c5e --- /dev/null +++ b/frontend/lib/registry/components/conditional-container/types.ts @@ -0,0 +1,75 @@ +/** + * ConditionalContainer 컴포넌트 타입 정의 + * 제어 셀렉트박스 값에 따라 다른 UI를 표시하는 조건부 컨테이너 + */ + +import { ComponentData } from "@/types/screen"; + +export interface ConditionalSection { + id: string; // 고유 ID + condition: string; // 조건 값 (예: "customer_first", "quotation") + label: string; // 조건 라벨 (예: "거래처 우선", "견적서 기반") + screenId: number | null; // 이 조건일 때 표시할 화면 ID + screenName?: string; // 화면 이름 (표시용) +} + +export interface ConditionalContainerConfig { + // 제어 셀렉트박스 설정 + controlField: string; // 제어할 필드명 (예: "inputMode") + controlLabel: string; // 셀렉트박스 라벨 (예: "입력 방식") + + // 조건별 섹션 + sections: ConditionalSection[]; + + // 기본 선택 값 + defaultValue?: string; + + // 스타일 + showBorder?: boolean; // 섹션별 테두리 표시 + spacing?: "tight" | "normal" | "loose"; // 섹션 간격 +} + +export interface ConditionalContainerProps { + config?: ConditionalContainerConfig; + + // 개별 props (config 우선) + controlField?: string; + controlLabel?: string; + sections?: ConditionalSection[]; + defaultValue?: string; + showBorder?: boolean; + spacing?: "tight" | "normal" | "loose"; + + // 폼 데이터 연동 + value?: any; // 현재 선택된 값 + onChange?: (value: string) => void; + formData?: Record; + onFormDataChange?: (fieldName: string, value: any) => void; + + // 화면 편집기 관련 + isDesignMode?: boolean; // 디자인 모드 여부 + onUpdateComponent?: (componentId: string, updates: Partial) => void; + onDeleteComponent?: (componentId: string) => void; + onSelectComponent?: (componentId: string) => void; + selectedComponentId?: string; + + // 스타일 + style?: React.CSSProperties; + className?: string; +} + +// 조건부 섹션 뷰어 Props +export interface ConditionalSectionViewerProps { + sectionId: string; + condition: string; + label: string; + screenId: number | null; // 표시할 화면 ID + screenName?: string; // 화면 이름 + isActive: boolean; // 현재 조건이 활성화되어 있는지 + isDesignMode: boolean; + showBorder?: boolean; + // 폼 데이터 전달 + formData?: Record; + onFormDataChange?: (fieldName: string, value: any) => void; +} + diff --git a/frontend/lib/registry/components/index.ts b/frontend/lib/registry/components/index.ts index f7122882..41f8e966 100644 --- a/frontend/lib/registry/components/index.ts +++ b/frontend/lib/registry/components/index.ts @@ -50,6 +50,9 @@ import { EntitySearchInputRenderer } from "./entity-search-input/EntitySearchInp import { ModalRepeaterTableRenderer } from "./modal-repeater-table/ModalRepeaterTableRenderer"; import "./order-registration-modal/OrderRegistrationModalRenderer"; +// 🆕 조건부 컨테이너 컴포넌트 +import "./conditional-container/ConditionalContainerRenderer"; + /** * 컴포넌트 초기화 함수 */ diff --git a/frontend/lib/utils/getComponentConfigPanel.tsx b/frontend/lib/utils/getComponentConfigPanel.tsx index 29e166b2..967b1fd5 100644 --- a/frontend/lib/utils/getComponentConfigPanel.tsx +++ b/frontend/lib/utils/getComponentConfigPanel.tsx @@ -31,6 +31,8 @@ const CONFIG_PANEL_MAP: Record Promise> = { "entity-search-input": () => import("@/lib/registry/components/entity-search-input/EntitySearchInputConfigPanel"), "modal-repeater-table": () => import("@/lib/registry/components/modal-repeater-table/ModalRepeaterTableConfigPanel"), "order-registration-modal": () => import("@/lib/registry/components/order-registration-modal/OrderRegistrationModalConfigPanel"), + // 🆕 조건부 컨테이너 + "conditional-container": () => import("@/lib/registry/components/conditional-container/ConditionalContainerConfigPanel"), }; // ConfigPanel 컴포넌트 캐시 @@ -261,7 +263,8 @@ export const DynamicComponentConfigPanel: React.FC = "autocomplete-search-input", "entity-search-input", "modal-repeater-table", - "order-registration-modal" + "order-registration-modal", + "conditional-container" ].includes(componentId); if (isSimpleConfigPanel) {