From 108af2a68b58e8e9d45427412b43d087609f1979 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Tue, 18 Nov 2025 11:28:22 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Section=20Card/Paper=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=ED=8C=A8=EB=84=90=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 새로운 그룹화 레이아웃 컴포넌트 2종 추가: - Section Card: 제목+테두리 기반 명확한 섹션 구분 - Section Paper: 배경색 기반 미니멀한 섹션 구분 주요 변경사항: - 새 컴포넌트 등록 (각 4개 파일: Component, ConfigPanel, Renderer, index) - UnifiedPropertiesPanel에 인라인 설정 UI 추가 (280줄) - DetailSettingsPanel ConfigPanel 인터페이스 통일화 (config → componentConfig) - getComponentConfigPanel에 동적 import 매핑 추가 - 기존 컴포넌트 타입 정리 (autocomplete, entity-search, modal-repeater) 특징: - shadcn/ui 기반 일관된 디자인 시스템 준수 - 중첩 박스 금지 원칙 적용 - 반응형 지원 (모바일 우선) - collapsible 기능 지원 (Section Card) --- .../screen/panels/DetailSettingsPanel.tsx | 8 +- .../screen/panels/UnifiedPropertiesPanel.tsx | 302 +++++++++++++++++- .../AutocompleteSearchInputRenderer.tsx | 4 +- .../EntitySearchInputRenderer.tsx | 4 +- .../entity-search-input/EntitySearchModal.tsx | 14 +- frontend/lib/registry/components/index.ts | 4 + .../ModalRepeaterTableRenderer.tsx | 4 +- .../section-card/SectionCardComponent.tsx | 178 +++++++++++ .../section-card/SectionCardConfigPanel.tsx | 172 ++++++++++ .../section-card/SectionCardRenderer.tsx | 27 ++ .../registry/components/section-card/index.ts | 43 +++ .../section-paper/SectionPaperComponent.tsx | 138 ++++++++ .../section-paper/SectionPaperConfigPanel.tsx | 151 +++++++++ .../section-paper/SectionPaperRenderer.tsx | 27 ++ .../components/section-paper/index.ts | 40 +++ .../lib/utils/getComponentConfigPanel.tsx | 5 + 16 files changed, 1098 insertions(+), 23 deletions(-) create mode 100644 frontend/lib/registry/components/section-card/SectionCardComponent.tsx create mode 100644 frontend/lib/registry/components/section-card/SectionCardConfigPanel.tsx create mode 100644 frontend/lib/registry/components/section-card/SectionCardRenderer.tsx create mode 100644 frontend/lib/registry/components/section-card/index.ts create mode 100644 frontend/lib/registry/components/section-paper/SectionPaperComponent.tsx create mode 100644 frontend/lib/registry/components/section-paper/SectionPaperConfigPanel.tsx create mode 100644 frontend/lib/registry/components/section-paper/SectionPaperRenderer.tsx create mode 100644 frontend/lib/registry/components/section-paper/index.ts diff --git a/frontend/components/screen/panels/DetailSettingsPanel.tsx b/frontend/components/screen/panels/DetailSettingsPanel.tsx index 6a1c4d7f..81e1b2a9 100644 --- a/frontend/components/screen/panels/DetailSettingsPanel.tsx +++ b/frontend/components/screen/panels/DetailSettingsPanel.tsx @@ -883,10 +883,12 @@ export const DetailSettingsPanel: React.FC = ({ // 래퍼 컴포넌트: 새 ConfigPanel 인터페이스를 기존 패턴에 맞춤 const ConfigPanelWrapper = () => { - const config = currentConfig.config || definition.defaultConfig || {}; + // Section Card, Section Paper 등 신규 컴포넌트는 componentConfig 바로 아래에 설정 저장 + const config = currentConfig || definition.defaultProps?.componentConfig || {}; const handleConfigChange = (newConfig: any) => { - onUpdateProperty(selectedComponent.id, "componentConfig.config", newConfig); + // componentConfig 전체를 업데이트 + onUpdateProperty(selectedComponent.id, "componentConfig", newConfig); }; return ( @@ -895,7 +897,7 @@ export const DetailSettingsPanel: React.FC = ({

{definition.name} 설정

- + ); }; diff --git a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx index f4942953..fce80d48 100644 --- a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx +++ b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx @@ -8,6 +8,7 @@ import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; +import { Textarea } from "@/components/ui/textarea"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { ChevronDown, Settings, Info, Database, Trash2, Copy, Palette, Monitor } from "lucide-react"; import { @@ -266,7 +267,12 @@ export const UnifiedPropertiesPanel: React.FC = ({ const renderComponentConfigPanel = () => { if (!selectedComponent) return null; - const componentType = selectedComponent.componentConfig?.type || selectedComponent.type; + // 🎯 Section Card, Section Paper 등 신규 컴포넌트는 componentType에서 감지 + const componentType = + selectedComponent.componentType || // ⭐ 1순위: ScreenDesigner가 설정한 componentType (section-card 등) + selectedComponent.componentConfig?.type || + selectedComponent.componentConfig?.id || + selectedComponent.type; const handleUpdateProperty = (path: string, value: any) => { onUpdateProperty(selectedComponent.id, path, value); @@ -276,10 +282,15 @@ export const UnifiedPropertiesPanel: React.FC = ({ onUpdateProperty(selectedComponent.id, "componentConfig.config", newConfig); }; - // 🆕 ComponentRegistry에서 ConfigPanel 가져오기 - const componentId = selectedComponent.componentConfig?.type || selectedComponent.componentConfig?.id; + // 🆕 ComponentRegistry에서 ConfigPanel 가져오기 시도 + const componentId = + selectedComponent.componentType || // ⭐ section-card 등 + selectedComponent.componentConfig?.type || + selectedComponent.componentConfig?.id; + if (componentId) { const definition = ComponentRegistry.getComponent(componentId); + if (definition?.configPanel) { const ConfigPanelComponent = definition.configPanel; const currentConfig = selectedComponent.componentConfig || {}; @@ -293,10 +304,12 @@ export const UnifiedPropertiesPanel: React.FC = ({ // 래퍼 컴포넌트: 새 ConfigPanel 인터페이스를 기존 패턴에 맞춤 const ConfigPanelWrapper = () => { - const config = currentConfig.config || definition.defaultConfig || {}; + // Section Card, Section Paper 등 신규 컴포넌트는 componentConfig 바로 아래에 설정 저장 + const config = currentConfig || definition.defaultProps?.componentConfig || {}; const handleConfigChange = (newConfig: any) => { - onUpdateProperty(selectedComponent.id, "componentConfig.config", newConfig); + // componentConfig 전체를 업데이트 + onUpdateProperty(selectedComponent.id, "componentConfig", newConfig); }; return ( @@ -305,18 +318,19 @@ export const UnifiedPropertiesPanel: React.FC = ({

{definition.name} 설정

- + ); }; return ; } else { - console.warn("⚠️ ConfigPanel 없음:", { + console.warn("⚠️ ComponentRegistry에서 ConfigPanel을 찾을 수 없음 - switch case로 이동:", { componentId, definitionName: definition?.name, hasDefinition: !!definition, }); + // ConfigPanel이 없으면 아래 switch case로 넘어감 } } @@ -363,6 +377,280 @@ export const UnifiedPropertiesPanel: React.FC = ({ case "badge-status": return ; + case "section-card": + return ( +
+
+

Section Card 설정

+

+ 제목과 테두리가 있는 명확한 그룹화 컨테이너 +

+
+ + {/* 헤더 표시 */} +
+ { + handleUpdateProperty(selectedComponent.id, "componentConfig.showHeader", checked); + }} + /> + +
+ + {/* 제목 */} + {selectedComponent.componentConfig?.showHeader !== false && ( +
+ + { + handleUpdateProperty(selectedComponent.id, "componentConfig.title", e.target.value); + }} + placeholder="섹션 제목 입력" + className="h-9 text-xs" + /> +
+ )} + + {/* 설명 */} + {selectedComponent.componentConfig?.showHeader !== false && ( +
+ +