From 77a6b50761f2459ed1483016c2d3476b73fd47b9 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 11 Sep 2025 16:21:00 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=8B=A8=EC=88=9C?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screen/RealtimePreviewDynamic.tsx | 16 ++- frontend/components/screen/ScreenDesigner.tsx | 133 ++++++++++------- .../components/screen/panels/LayoutsPanel.tsx | 46 +++++- .../screen/panels/PropertiesPanel.tsx | 32 +++-- frontend/docs/레이아웃_추가_가이드.md | 134 +++++++++++++++++- .../lib/registry/DynamicComponentRenderer.tsx | 4 + .../lib/registry/DynamicLayoutRenderer.tsx | 7 +- .../layouts/accordion/AccordionLayout.tsx | 5 +- .../layouts/card-layout/CardLayoutLayout.tsx | 4 +- .../lib/registry/layouts/card-layout/index.ts | 1 + .../layouts/flexbox/FlexboxLayout.tsx | 15 +- .../lib/registry/layouts/grid/GridLayout.tsx | 15 +- .../hero-section/HeroSectionLayout.tsx | 3 +- .../registry/layouts/split/SplitLayout.tsx | 3 +- 14 files changed, 312 insertions(+), 106 deletions(-) diff --git a/frontend/components/screen/RealtimePreviewDynamic.tsx b/frontend/components/screen/RealtimePreviewDynamic.tsx index 2c460598..ed8d872c 100644 --- a/frontend/components/screen/RealtimePreviewDynamic.tsx +++ b/frontend/components/screen/RealtimePreviewDynamic.tsx @@ -28,7 +28,9 @@ interface RealtimePreviewProps { onDragEnd?: () => void; onGroupToggle?: (groupId: string) => void; // 그룹 접기/펼치기 children?: React.ReactNode; // 그룹 내 자식 컴포넌트들 - selectedScreen?: any; // 선택된 화면 정보 + selectedScreen?: any; + onZoneComponentDrop?: (e: React.DragEvent, zoneId: string, layoutId: string) => void; // 존별 드롭 핸들러 + onZoneClick?: (zoneId: string) => void; // 존 클릭 핸들러 } // 동적 위젯 타입 아이콘 (레지스트리에서 조회) @@ -67,6 +69,8 @@ export const RealtimePreviewDynamic: React.FC = ({ onGroupToggle, children, selectedScreen, + onZoneComponentDrop, + onZoneClick, }) => { const { id, type, position, size, style: componentStyle } = component; @@ -79,13 +83,13 @@ export const RealtimePreviewDynamic: React.FC = ({ } : {}; - // 컴포넌트 기본 스타일 + // 컴포넌트 기본 스타일 - 레이아웃은 항상 맨 아래 const baseStyle = { left: `${position.x}px`, top: `${position.y}px`, - width: `${size.width}px`, - height: `${size.height}px`, - zIndex: position.z || 1, + width: `${size?.width || 100}px`, + height: `${size?.height || 36}px`, + zIndex: component.type === "layout" ? 1 : position.z || 2, // 레이아웃은 z-index 1, 다른 컴포넌트는 2 이상 ...componentStyle, }; @@ -123,6 +127,8 @@ export const RealtimePreviewDynamic: React.FC = ({ onDragEnd={onDragEnd} children={children} selectedScreen={selectedScreen} + onZoneComponentDrop={onZoneComponentDrop} + onZoneClick={onZoneClick} /> diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index cf18f209..599eebec 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -1345,9 +1345,81 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD [layout, gridInfo, screenResolution, snapToGrid, saveToHistory, openPanel], ); - // 컴포넌트 드래그 처리 + // handleZoneComponentDrop은 handleComponentDrop으로 대체됨 + + // 존 클릭 핸들러 + const handleZoneClick = useCallback((zoneId: string) => { + console.log("🎯 존 클릭:", zoneId); + // 필요시 존 선택 로직 추가 + }, []); + + // 웹타입별 기본 설정 생성 함수를 상위로 이동 + const getDefaultWebTypeConfig = useCallback((webType: string) => { + switch (webType) { + case "button": + return { + actionType: "custom", + variant: "default", + confirmationMessage: "", + popupTitle: "", + popupContent: "", + navigateUrl: "", + }; + case "date": + return { + format: "YYYY-MM-DD", + showTime: false, + placeholder: "날짜를 선택하세요", + }; + case "number": + return { + format: "integer", + placeholder: "숫자를 입력하세요", + }; + case "select": + return { + options: [ + { label: "옵션 1", value: "option1" }, + { label: "옵션 2", value: "option2" }, + { label: "옵션 3", value: "option3" }, + ], + multiple: false, + searchable: false, + placeholder: "옵션을 선택하세요", + }; + case "file": + return { + accept: ["*/*"], + maxSize: 10485760, // 10MB + multiple: false, + showPreview: true, + autoUpload: false, + }; + default: + return {}; + } + }, []); + + // 컴포넌트 드래그 처리 (캔버스 레벨 드롭) const handleComponentDrop = useCallback( - (e: React.DragEvent, component: any) => { + (e: React.DragEvent, component?: any, zoneId?: string, layoutId?: string) => { + // 존별 드롭인 경우 dragData에서 컴포넌트 정보 추출 + if (!component) { + const dragData = e.dataTransfer.getData("application/json"); + if (!dragData) return; + + try { + const parsedData = JSON.parse(dragData); + if (parsedData.type === "component") { + component = parsedData.component; + } else { + return; + } + } catch (error) { + console.error("드래그 데이터 파싱 오류:", error); + return; + } + } const rect = canvasRef.current?.getBoundingClientRect(); if (!rect) return; @@ -1377,53 +1449,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD snappedPosition, }); - // 웹타입별 기본 설정 생성 - const getDefaultWebTypeConfig = (webType: string) => { - switch (webType) { - case "button": - return { - actionType: "custom", - variant: "default", - confirmationMessage: "", - popupTitle: "", - popupContent: "", - navigateUrl: "", - }; - case "date": - return { - format: "YYYY-MM-DD", - showTime: false, - placeholder: "날짜를 선택하세요", - }; - case "number": - return { - format: "integer", - placeholder: "숫자를 입력하세요", - }; - case "select": - return { - options: [ - { label: "옵션 1", value: "option1" }, - { label: "옵션 2", value: "option2" }, - { label: "옵션 3", value: "option3" }, - ], - multiple: false, - searchable: false, - placeholder: "옵션을 선택하세요", - }; - case "file": - return { - accept: ["*/*"], - maxSize: 10485760, // 10MB - multiple: false, - showPreview: true, - autoUpload: false, - }; - default: - return {}; - } - }; - // 새 컴포넌트 생성 console.log("🔍 ScreenDesigner handleComponentDrop:", { componentName: component.name, @@ -3105,8 +3130,10 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD onDragStart={(e) => startComponentDrag(component, e)} onDragEnd={endDrag} selectedScreen={selectedScreen} + // onZoneComponentDrop 제거 + onZoneClick={handleZoneClick} > - {/* 컨테이너, 그룹, 영역의 자식 컴포넌트들 렌더링 */} + {/* 컨테이너, 그룹, 영역의 자식 컴포넌트들 렌더링 (레이아웃은 독립적으로 렌더링) */} {(component.type === "group" || component.type === "container" || component.type === "area") && layout.components .filter((child) => child.parentId === component.id) @@ -3182,6 +3209,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD onDragStart={(e) => startComponentDrag(child, e)} onDragEnd={endDrag} selectedScreen={selectedScreen} + // onZoneComponentDrop 제거 + onZoneClick={handleZoneClick} /> ); })} @@ -3290,7 +3319,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD id="layouts" title="레이아웃" isOpen={panelStates.layouts?.isOpen || false} - onClose={() => closePanelState("layouts")} + onClose={() => closePanel("layouts")} position="left" width={380} height={700} @@ -3304,6 +3333,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD }; e.dataTransfer.setData("application/json", JSON.stringify(dragData)); }} + gridSettings={layout.gridSettings || { columns: 12, gap: 16, padding: 16, snapToGrid: true }} + screenResolution={screenResolution} /> diff --git a/frontend/components/screen/panels/LayoutsPanel.tsx b/frontend/components/screen/panels/LayoutsPanel.tsx index 68e3ae92..233b5739 100644 --- a/frontend/components/screen/panels/LayoutsPanel.tsx +++ b/frontend/components/screen/panels/LayoutsPanel.tsx @@ -9,6 +9,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Grid, Layout, LayoutDashboard, Table, Navigation, FileText, Building, Search, Plus } from "lucide-react"; import { LAYOUT_CATEGORIES, LayoutCategory } from "@/types/layout"; import { LayoutRegistry } from "@/lib/registry/LayoutRegistry"; +import { calculateGridInfo, calculateWidthFromColumns } from "@/lib/utils/gridUtils"; // 카테고리 아이콘 매핑 const CATEGORY_ICONS = { @@ -36,9 +37,25 @@ interface LayoutsPanelProps { onDragStart: (e: React.DragEvent, layoutData: any) => void; onLayoutSelect?: (layoutDefinition: any) => void; className?: string; + gridSettings?: { + columns: number; + gap: number; + padding: number; + snapToGrid: boolean; + }; + screenResolution?: { + width: number; + height: number; + }; } -export default function LayoutsPanel({ onDragStart, onLayoutSelect, className }: LayoutsPanelProps) { +export default function LayoutsPanel({ + onDragStart, + onLayoutSelect, + className, + gridSettings, + screenResolution, +}: LayoutsPanelProps) { const [searchTerm, setSearchTerm] = useState(""); const [selectedCategory, setSelectedCategory] = useState("all"); @@ -78,6 +95,29 @@ export default function LayoutsPanel({ onDragStart, onLayoutSelect, className }: // 레이아웃 드래그 시작 핸들러 const handleDragStart = (e: React.DragEvent, layoutDefinition: any) => { + // 격자 기반 동적 크기 계산 + let calculatedSize = layoutDefinition.defaultSize || { width: 400, height: 300 }; + + if (gridSettings && screenResolution && layoutDefinition.id === "card-layout") { + // 카드 레이아웃의 경우 8그리드 컬럼에 맞는 너비 계산 + const gridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, gridSettings); + const calculatedWidth = calculateWidthFromColumns(8, gridInfo, gridSettings); + + calculatedSize = { + width: Math.max(calculatedWidth, 400), // 최소 400px 보장 + height: 400, // 높이는 고정 + }; + + console.log("🎯 카드 레이아웃 동적 크기 계산:", { + gridColumns: 8, + screenResolution, + gridSettings, + gridInfo, + calculatedWidth, + finalSize: calculatedSize, + }); + } + // 새 레이아웃 컴포넌트 데이터 생성 const layoutData = { id: `layout_${Date.now()}`, @@ -88,8 +128,9 @@ export default function LayoutsPanel({ onDragStart, onLayoutSelect, className }: children: [], allowedComponentTypes: [], position: { x: 0, y: 0 }, - size: layoutDefinition.defaultSize || { width: 400, height: 300 }, + size: calculatedSize, label: layoutDefinition.name, + gridColumns: layoutDefinition.id === "card-layout" ? 8 : 1, // 카드 레이아웃은 기본 8그리드 }; // 드래그 데이터 설정 @@ -192,4 +233,3 @@ export default function LayoutsPanel({ onDragStart, onLayoutSelect, className }: ); } - diff --git a/frontend/components/screen/panels/PropertiesPanel.tsx b/frontend/components/screen/panels/PropertiesPanel.tsx index fe26b32f..ca1d70d2 100644 --- a/frontend/components/screen/panels/PropertiesPanel.tsx +++ b/frontend/components/screen/panels/PropertiesPanel.tsx @@ -191,9 +191,11 @@ const PropertiesPanelComponent: React.FC = ({ positionX: currentPosition.x.toString(), positionY: currentPosition.y.toString(), positionZ: selectedComponent?.position.z?.toString() || "1", - width: selectedComponent?.size.width?.toString() || "0", - height: selectedComponent?.size.height?.toString() || "0", - gridColumns: selectedComponent?.gridColumns?.toString() || "1", + width: selectedComponent?.size?.width?.toString() || "0", + height: selectedComponent?.size?.height?.toString() || "0", + gridColumns: + selectedComponent?.gridColumns?.toString() || + (selectedComponent?.type === "layout" && (selectedComponent as any)?.layoutType === "card-layout" ? "8" : "1"), labelText: selectedComponent?.style?.labelText || selectedComponent?.label || "", labelFontSize: selectedComponent?.style?.labelFontSize || "12px", labelColor: selectedComponent?.style?.labelColor || "#374151", @@ -244,14 +246,18 @@ const PropertiesPanelComponent: React.FC = ({ description: area?.description || "", positionX: currentPos.x.toString(), positionY: currentPos.y.toString(), - positionZ: selectedComponent.position.z?.toString() || "1", - width: selectedComponent.size.width?.toString() || "0", - height: selectedComponent.size.height?.toString() || "0", - gridColumns: selectedComponent.gridColumns?.toString() || "1", - labelText: selectedComponent.style?.labelText || selectedComponent.label || "", - labelFontSize: selectedComponent.style?.labelFontSize || "12px", - labelColor: selectedComponent.style?.labelColor || "#374151", - labelMarginBottom: selectedComponent.style?.labelMarginBottom || "4px", + positionZ: selectedComponent?.position?.z?.toString() || "1", + width: selectedComponent?.size?.width?.toString() || "0", // 안전한 접근 + height: selectedComponent?.size?.height?.toString() || "0", // 안전한 접근 + gridColumns: + selectedComponent?.gridColumns?.toString() || + (selectedComponent?.type === "layout" && (selectedComponent as any)?.layoutType === "card-layout" + ? "8" + : "1"), + labelText: selectedComponent?.style?.labelText || selectedComponent?.label || "", + labelFontSize: selectedComponent?.style?.labelFontSize || "12px", + labelColor: selectedComponent?.style?.labelColor || "#374151", + labelMarginBottom: selectedComponent?.style?.labelMarginBottom || "4px", required: widget?.required || false, readonly: widget?.readonly || false, labelDisplay: selectedComponent.style?.labelDisplay !== false, @@ -584,7 +590,7 @@ const PropertiesPanelComponent: React.FC = ({ onChange={(e) => { const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, width: newValue })); - onUpdateProperty("size", { ...selectedComponent.size, width: Number(newValue) }); + onUpdateProperty("size", { ...(selectedComponent.size || {}), width: Number(newValue) }); }} className="mt-1" /> @@ -601,7 +607,7 @@ const PropertiesPanelComponent: React.FC = ({ onChange={(e) => { const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, height: newValue })); - onUpdateProperty("size", { ...selectedComponent.size, height: Number(newValue) }); + onUpdateProperty("size", { ...(selectedComponent.size || {}), height: Number(newValue) }); }} className="mt-1" /> diff --git a/frontend/docs/레이아웃_추가_가이드.md b/frontend/docs/레이아웃_추가_가이드.md index f0e65ed3..03dded6e 100644 --- a/frontend/docs/레이아웃_추가_가이드.md +++ b/frontend/docs/레이아웃_추가_가이드.md @@ -8,7 +8,7 @@ 2. [CLI를 이용한 자동 생성](#cli를-이용한-자동-생성) 3. [생성된 파일 구조](#생성된-파일-구조) 4. [레이아웃 커스터마이징](#레이아웃-커스터마이징) -5. [카드 레이아웃 상세 가이드](#카드-레이아웃-상세-가이드)웃 +5. [카드 레이아웃 상세 가이드](#카드-레이아웃-상세-가이드) 6. [고급 설정](#고급-설정) 7. [문제 해결](#문제-해결) @@ -846,6 +846,62 @@ const getColumnLabel = (columnName: string) => { {getColumnLabel(columnName)}: ``` +#### 9. 레이아웃 드롭 시 오류 발생 문제 (9월 11일 해결됨) + +```typescript +// ❌ 문제: 복잡한 존별 드롭 로직으로 인한 오류 +- Runtime TypeError: Cannot read properties of undefined (reading 'width') +- 다중선택 기능 중단 +- 존별 드롭 이벤트 충돌 + +// ✅ 해결: 드롭 시스템 완전 단순화 +- 모든 존별 드롭 로직 제거 +- 일반 캔버스 드롭만 사용 +- 레이아웃은 시각적 가이드 역할만 +- z-index 기반 레이어 분리 (레이아웃=1, 컴포넌트=2+) +``` + +##### 드롭 시스템 단순화 후 장점 + +- ✅ **안정성**: 복잡한 이벤트 체인 제거로 오류 가능성 감소 +- ✅ **일관성**: 모든 영역에서 동일한 드롭 동작 +- ✅ **성능**: 불필요한 prop 전달 및 매핑 로직 제거 +- ✅ **유지보수**: 단순한 구조로 디버깅 및 수정 용이 + +##### 새로운 레이아웃 개발 시 주의사항 + +```typescript +// ✅ 올바른 레이아웃 구현 +export const YourLayoutLayout: React.FC = ({ layout, isDesignMode, ...props }) => { + // 🚫 존별 드롭 이벤트 구현 금지 + // onDrop, onDragOver 등 드롭 관련 이벤트 추가하지 않음 + + return ( +
+ {layout.zones.map((zone) => ( +
+ {/* 존 내용 */} +
+ ))} + + +
+ ); +}; +``` + ### 디버깅 도구 #### 브라우저 개발자 도구 @@ -1025,6 +1081,53 @@ node scripts/create-layout.js form-layout --category=form --zones=3 --descriptio - ✅ **시각적 피드백**: 모든 레이아웃에서 존 경계 명확히 표시 - ✅ **React 경고 해결**: Key prop, DOM prop 전달 등 모든 경고 해결 +#### 🔧 드롭 시스템 대폭 단순화 (9월 11일 추가) + +기존의 복잡한 존별 드롭 시스템을 완전히 제거하고 단순한 캔버스 드롭 방식으로 통일했습니다: + +##### 변경 사항 + +- ✅ **복잡한 드롭 로직 제거**: `onZoneComponentDrop`, `handleZoneComponentDrop` 등 모든 존별 드롭 이벤트 제거 +- ✅ **일반 캔버스 드롭만 사용**: 모든 드롭이 `handleComponentDrop`으로 통일 +- ✅ **레이아웃은 시각적 가이드 역할**: 레이아웃 존은 배치 가이드라인 역할만 수행 +- ✅ **z-index 기반 레이어링**: 레이아웃 z-index=1, 컴포넌트 z-index=2+로 설정 +- ✅ **prop 전달 체인 단순화**: 불필요한 prop 매핑 및 전달 로직 제거 + +##### 새로운 동작 방식 + +**이전 (복잡한 방식)**: +``` +컴포넌트 드래그 → 레이아웃 존 감지 → 존별 드롭 이벤트 → 복잡한 매핑 → 오류 발생 +``` + +**현재 (단순한 방식)**: +``` +컴포넌트 드래그 → 캔버스에 드롭 → 일반 handleComponentDrop만 실행 → 안정적 동작 +``` + +##### 해결된 문제들 + +- ✅ **Runtime TypeError 해결**: `selectedComponent.size.width` undefined 오류 완전 해결 +- ✅ **다중선택 복구**: 드래그로 다중선택 기능 정상화 +- ✅ **안정적 드롭**: 레이아웃 위든 어디든 일관된 드롭 동작 +- ✅ **코드 단순화**: 복잡한 존별 로직 제거로 유지보수성 향상 + +##### 기술적 변경점 + +1. **GridLayout, FlexboxLayout**: `onDragOver`, `onDrop` 이벤트 핸들러 제거 +2. **ScreenDesigner**: `onZoneComponentDrop` prop 전달 제거 +3. **DynamicComponentRenderer**: `onComponentDrop` 매핑 로직 제거 +4. **DynamicLayoutRenderer**: 존별 prop 전달 제거 +5. **RealtimePreviewDynamic**: z-index 기반 레이어링 적용 + +##### 개발자 가이드 + +새로운 시스템에서는: +- 🚫 **존별 드롭 로직 구현 금지**: 모든 드롭은 캔버스 레벨에서 처리 +- ✅ **시각적 가이드만 제공**: 레이아웃은 배치 가이드라인 역할만 +- ✅ **z-index로 레이어 관리**: 레이아웃=1, 컴포넌트=2+ 설정 +- ✅ **단순한 이벤트 처리**: 복잡한 이벤트 체인 대신 직접적인 핸들링 + #### 개발자 도구 강화 - ✅ **디버깅 로그**: 카드 설정 로드, 데이터 가져오기 등 상세 로깅 @@ -1042,9 +1145,38 @@ node scripts/create-layout.js form-layout --category=form --zones=3 --descriptio ### 🔮 향후 계획 +#### 새로운 레이아웃 타입 - **Table Layout**: 데이터 테이블 전용 레이아웃 - **Form Layout**: 폼 입력에 최적화된 레이아웃 - **Dashboard Layout**: 위젯 배치에 특화된 레이아웃 - **Mobile Responsive**: 모바일 대응 반응형 레이아웃 +#### 시스템 개선 +- **레이아웃 테마 시스템**: 다크/라이트 모드 지원 +- **레이아웃 스타일 프리셋**: 미리 정의된 스타일 템플릿 +- **레이아웃 애니메이션**: 전환 효과 및 인터랙션 개선 +- **성능 최적화**: 가상화 및 지연 로딩 적용 + +#### 개발자 도구 +- **레이아웃 빌더 GUI**: 코드 없이 레이아웃 생성 도구 +- **실시간 프리뷰**: 레이아웃 편집 중 실시간 미리보기 +- **레이아웃 디버거**: 시각적 디버깅 도구 +- **성능 모니터링**: 레이아웃 렌더링 성능 분석 + +### 🎯 중요한 변화: 단순화된 드롭 시스템 + +**2025년 9월 11일**부터 모든 레이아웃에서 **복잡한 존별 드롭 로직이 완전히 제거**되었습니다. + +새로운 시스템의 핵심 원칙: +- 🎯 **레이아웃 = 시각적 가이드**: 배치 참고용으로만 사용 +- 🎯 **캔버스 = 실제 배치**: 모든 컴포넌트는 캔버스에 자유롭게 배치 +- 🎯 **z-index = 레이어 분리**: 레이아웃(1) 위에 컴포넌트(2+) 배치 +- 🎯 **단순함 = 안정성**: 복잡한 로직 제거로 오류 최소화 + +이 변화로 인해: +- ✅ 모든 드롭 관련 오류 해결 +- ✅ 다중선택 기능 정상화 +- ✅ 레이아웃 개발이 더욱 단순해짐 +- ✅ 시스템 전체 안정성 크게 향상 + 더 자세한 정보가 필요하면 각 레이아웃의 `README.md` 파일을 참고하거나, 브라우저 개발자 도구에서 `window.__LAYOUT_REGISTRY__.help()`를 실행해보세요! 🚀 diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index f6c12f18..a0a838f5 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -16,6 +16,8 @@ export interface ComponentRenderer { onDragStart?: (e: React.DragEvent) => void; onDragEnd?: () => void; children?: React.ReactNode; + onZoneComponentDrop?: (e: React.DragEvent, zoneId: string, layoutId: string) => void; + onZoneClick?: (zoneId: string) => void; [key: string]: any; }): React.ReactElement; } @@ -89,6 +91,8 @@ export const DynamicComponentRenderer: React.FC = onDragStart={onDragStart} onDragEnd={onDragEnd} onUpdateLayout={props.onUpdateLayout} + // onComponentDrop 제거 - 일반 캔버스 드롭만 사용 + onZoneClick={props.onZoneClick} {...props} /> ); diff --git a/frontend/lib/registry/DynamicLayoutRenderer.tsx b/frontend/lib/registry/DynamicLayoutRenderer.tsx index 535faf2a..7564f7a3 100644 --- a/frontend/lib/registry/DynamicLayoutRenderer.tsx +++ b/frontend/lib/registry/DynamicLayoutRenderer.tsx @@ -10,13 +10,14 @@ export interface DynamicLayoutRendererProps { isDesignMode?: boolean; isSelected?: boolean; onClick?: (e: React.MouseEvent) => void; - onZoneClick?: (zoneId: string, e: React.MouseEvent) => void; - onComponentDrop?: (zoneId: string, component: ComponentData, e: React.DragEvent) => void; + onZoneClick?: (zoneId: string) => void; + onComponentDrop?: (e: React.DragEvent, zoneId: string, layoutId: string) => void; onDragStart?: (e: React.DragEvent) => void; onDragEnd?: (e: React.DragEvent) => void; onUpdateLayout?: (updatedLayout: LayoutComponent) => void; // 레이아웃 업데이트 콜백 className?: string; style?: React.CSSProperties; + [key: string]: any; // 추가 props 허용 } export const DynamicLayoutRenderer: React.FC = ({ @@ -77,7 +78,7 @@ export const DynamicLayoutRenderer: React.FC = ({ isSelected={isSelected} onClick={onClick} onZoneClick={onZoneClick} - onComponentDrop={onComponentDrop} + // onComponentDrop 제거 onDragStart={onDragStart} onDragEnd={onDragEnd} onUpdateLayout={onUpdateLayout} diff --git a/frontend/lib/registry/layouts/accordion/AccordionLayout.tsx b/frontend/lib/registry/layouts/accordion/AccordionLayout.tsx index 0ca623b0..43488c51 100644 --- a/frontend/lib/registry/layouts/accordion/AccordionLayout.tsx +++ b/frontend/lib/registry/layouts/accordion/AccordionLayout.tsx @@ -17,6 +17,8 @@ export const AccordionLayout: React.FC = ({ onClick, className = "", renderer, + onZoneComponentDrop, + onZoneClick, ...props }) => { if (!layout.layoutConfig.accordion) { @@ -57,7 +59,6 @@ export const AccordionLayout: React.FC = ({ onSelectComponent, isDesignMode: _isDesignMode, allComponents, - onZoneClick, onComponentDrop, onDragStart, onDragEnd, @@ -205,7 +206,7 @@ const AccordionSection: React.FC<{ e.preventDefault(); e.stopPropagation(); if (onComponentDrop) { - onComponentDrop(e, zone.id); // 존 ID와 함께 드롭 이벤트 전달 + onComponentDrop(e, zone.id, layout.id); // 존 ID와 레이아웃 ID를 함께 전달 } }} onClick={(e) => { diff --git a/frontend/lib/registry/layouts/card-layout/CardLayoutLayout.tsx b/frontend/lib/registry/layouts/card-layout/CardLayoutLayout.tsx index 2a95bcd5..66876e67 100644 --- a/frontend/lib/registry/layouts/card-layout/CardLayoutLayout.tsx +++ b/frontend/lib/registry/layouts/card-layout/CardLayoutLayout.tsx @@ -169,7 +169,7 @@ export const CardLayoutLayout: React.FC = ({ className="hover:border-blue-500 hover:shadow-md" > {/* 존 라벨 */} -
+
{zone.name}
@@ -247,7 +247,7 @@ export const CardLayoutLayout: React.FC = ({ return (
- {getColumnLabel(columnName)}: + {getColumnLabel(columnName)}: {value}
); diff --git a/frontend/lib/registry/layouts/card-layout/index.ts b/frontend/lib/registry/layouts/card-layout/index.ts index cb2ee229..e4d9d3a8 100644 --- a/frontend/lib/registry/layouts/card-layout/index.ts +++ b/frontend/lib/registry/layouts/card-layout/index.ts @@ -24,6 +24,7 @@ export const CardLayoutDefinition = createLayoutDefinition({ category: "dashboard", icon: "grid-3x3", component: CardLayoutWrapper, + defaultSize: { width: 800, height: 400 }, defaultConfig: { cardLayout: { columns: 3, diff --git a/frontend/lib/registry/layouts/flexbox/FlexboxLayout.tsx b/frontend/lib/registry/layouts/flexbox/FlexboxLayout.tsx index 210480e4..1da0c1c5 100644 --- a/frontend/lib/registry/layouts/flexbox/FlexboxLayout.tsx +++ b/frontend/lib/registry/layouts/flexbox/FlexboxLayout.tsx @@ -17,6 +17,8 @@ export const FlexboxLayout: React.FC = ({ onClick, className = "", renderer, + onZoneComponentDrop, + onZoneClick, ...props }) => { if (!layout.layoutConfig.flexbox) { @@ -98,7 +100,6 @@ export const FlexboxLayout: React.FC = ({ onSelectComponent, isDesignMode: _isDesignMode, allComponents, - onZoneClick, onComponentDrop, onDragStart, onDragEnd, @@ -200,17 +201,7 @@ export const FlexboxLayout: React.FC = ({ e.currentTarget.style.borderColor = "#e5e7eb"; e.currentTarget.style.boxShadow = "0 1px 3px 0 rgba(0, 0, 0, 0.1)"; }} - onDragOver={(e) => { - e.preventDefault(); - e.dataTransfer.dropEffect = "copy"; - }} - onDrop={(e) => { - e.preventDefault(); - e.stopPropagation(); - if (onComponentDrop) { - onComponentDrop(e, zone.id); // 존 ID와 함께 드롭 이벤트 전달 - } - }} + // 드롭 이벤트 제거 - 일반 캔버스 드롭만 사용 onClick={(e) => { e.stopPropagation(); if (onZoneClick) { diff --git a/frontend/lib/registry/layouts/grid/GridLayout.tsx b/frontend/lib/registry/layouts/grid/GridLayout.tsx index be40d0bb..5aaf2b87 100644 --- a/frontend/lib/registry/layouts/grid/GridLayout.tsx +++ b/frontend/lib/registry/layouts/grid/GridLayout.tsx @@ -17,6 +17,8 @@ export const GridLayout: React.FC = ({ onClick, className = "", renderer, + onZoneComponentDrop, + onZoneClick, ...props }) => { if (!layout.layoutConfig.grid) { @@ -66,7 +68,6 @@ export const GridLayout: React.FC = ({ onSelectComponent, isDesignMode: _isDesignMode, allComponents, - onZoneClick, onComponentDrop, onDragStart, onDragEnd, @@ -158,17 +159,7 @@ export const GridLayout: React.FC = ({ e.currentTarget.style.borderColor = "#e5e7eb"; e.currentTarget.style.boxShadow = "0 1px 3px 0 rgba(0, 0, 0, 0.1)"; }} - onDragOver={(e) => { - e.preventDefault(); - e.dataTransfer.dropEffect = "copy"; - }} - onDrop={(e) => { - e.preventDefault(); - e.stopPropagation(); - if (onComponentDrop) { - onComponentDrop(e, zone.id); // 존 ID와 함께 드롭 이벤트 전달 - } - }} + // 드롭 이벤트 제거 - 일반 캔버스 드롭만 사용 onClick={(e) => { e.stopPropagation(); if (onZoneClick) { diff --git a/frontend/lib/registry/layouts/hero-section/HeroSectionLayout.tsx b/frontend/lib/registry/layouts/hero-section/HeroSectionLayout.tsx index dddcb6cf..660690f4 100644 --- a/frontend/lib/registry/layouts/hero-section/HeroSectionLayout.tsx +++ b/frontend/lib/registry/layouts/hero-section/HeroSectionLayout.tsx @@ -17,6 +17,8 @@ export const HeroSectionLayout: React.FC = ({ onClick, className = "", renderer, + onZoneComponentDrop, + onZoneClick, ...props }) => { if (!layout.layoutConfig.heroSection) { @@ -56,7 +58,6 @@ export const HeroSectionLayout: React.FC = ({ onSelectComponent, isDesignMode: _isDesignMode, allComponents, - onZoneClick, onComponentDrop, onDragStart, onDragEnd, diff --git a/frontend/lib/registry/layouts/split/SplitLayout.tsx b/frontend/lib/registry/layouts/split/SplitLayout.tsx index cc72c0c0..1a85164d 100644 --- a/frontend/lib/registry/layouts/split/SplitLayout.tsx +++ b/frontend/lib/registry/layouts/split/SplitLayout.tsx @@ -17,6 +17,8 @@ export const SplitLayout: React.FC = ({ onClick, className = "", renderer, + onZoneComponentDrop, + onZoneClick, ...props }) => { if (!layout.layoutConfig.split) { @@ -57,7 +59,6 @@ export const SplitLayout: React.FC = ({ onSelectComponent, isDesignMode: _isDesignMode, allComponents, - onZoneClick, onComponentDrop, onDragStart, onDragEnd,