From b1fba586cb6744c2b059bf7e240b9b6a73ad0d63 Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 26 Jan 2026 11:04:39 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=83=AD=20=EC=BB=A8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=84=88=EB=A1=9C=EC=9D=98=20=EB=93=9C=EB=A1=AD=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 드래그 종료 시 탭 컨테이너에 컴포넌트를 드롭할 수 있는 기능을 구현하였습니다. - 드래그된 컴포넌트를 탭 내부 컴포넌트로 변환하여 해당 탭에 추가하는 로직을 추가하였습니다. - 드래그 상태 초기화 및 성공적인 드롭 시 사용자에게 알림을 표시하도록 하였습니다. --- frontend/components/screen/ScreenDesigner.tsx | 102 +++++++++++++++++- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index c10e9bff..c8941515 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -3568,8 +3568,104 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD ); // 드래그 종료 - const endDrag = useCallback(() => { + const endDrag = useCallback((mouseEvent?: MouseEvent) => { if (dragState.isDragging && dragState.draggedComponent) { + // 🎯 탭 컨테이너로의 드롭 처리 (기존 컴포넌트 이동) + if (mouseEvent) { + const dropTarget = document.elementFromPoint(mouseEvent.clientX, mouseEvent.clientY) as HTMLElement; + const tabsContainer = dropTarget?.closest('[data-tabs-container="true"]'); + + if (tabsContainer) { + const containerId = tabsContainer.getAttribute("data-component-id"); + const activeTabId = tabsContainer.getAttribute("data-active-tab-id"); + + if (containerId && activeTabId) { + const targetComponent = layout.components.find((c) => c.id === containerId); + const compType = (targetComponent as any)?.componentType; + + // 자기 자신을 자신에게 드롭하는 것 방지 + if (targetComponent && + (compType === "tabs-widget" || compType === "v2-tabs-widget") && + dragState.draggedComponent !== containerId) { + + const draggedComponent = layout.components.find((c) => c.id === dragState.draggedComponent); + if (draggedComponent) { + const currentConfig = (targetComponent as any).componentConfig || {}; + const tabs = currentConfig.tabs || []; + + // 탭 컨텐츠 영역 기준 드롭 위치 계산 + const tabContentRect = tabsContainer.getBoundingClientRect(); + const dropX = (mouseEvent.clientX - tabContentRect.left) / zoomLevel; + const dropY = (mouseEvent.clientY - tabContentRect.top) / zoomLevel; + + // 기존 컴포넌트를 탭 내부 컴포넌트로 변환 + const newTabComponent = { + id: `tab_comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + componentType: (draggedComponent as any).componentType || draggedComponent.type, + label: (draggedComponent as any).label || "컴포넌트", + position: { x: Math.max(0, dropX), y: Math.max(0, dropY) }, + size: draggedComponent.size || { width: 200, height: 100 }, + componentConfig: (draggedComponent as any).componentConfig || {}, + style: (draggedComponent as any).style || {}, + }; + + // 해당 탭에 컴포넌트 추가 + const updatedTabs = tabs.map((tab: any) => { + if (tab.id === activeTabId) { + return { + ...tab, + components: [...(tab.components || []), newTabComponent], + }; + } + return tab; + }); + + // 탭 컴포넌트 업데이트 + 원래 컴포넌트 캔버스에서 제거 + const newLayout = { + ...layout, + components: layout.components + .filter((c) => c.id !== dragState.draggedComponent) // 캔버스에서 제거 + .map((c) => { + if (c.id === containerId) { + return { + ...c, + componentConfig: { + ...currentConfig, + tabs: updatedTabs, + }, + }; + } + return c; + }), + }; + + setLayout(newLayout); + saveToHistory(newLayout); + setSelectedComponent(null); + toast.success("컴포넌트가 탭으로 이동되었습니다"); + + // 드래그 상태 초기화 후 종료 + setDragState({ + isDragging: false, + draggedComponent: null, + draggedComponents: [], + originalPosition: { x: 0, y: 0, z: 1 }, + currentPosition: { x: 0, y: 0, z: 1 }, + grabOffset: { x: 0, y: 0 }, + justFinishedDrag: true, + }); + + setTimeout(() => { + setDragState((prev) => ({ ...prev, justFinishedDrag: false })); + }, 100); + + return; // 탭으로 이동 완료, 일반 드래그 종료 로직 스킵 + } + } + } + } + } + // 주 드래그 컴포넌트의 최종 위치 계산 const draggedComponent = layout.components.find((c) => c.id === dragState.draggedComponent); let finalPosition = dragState.currentPosition; @@ -4445,12 +4541,12 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD } }; - const handleMouseUp = () => { + const handleMouseUp = (e: MouseEvent) => { if (dragState.isDragging) { if (animationFrameId) { cancelAnimationFrame(animationFrameId); } - endDrag(); + endDrag(e); } else if (selectionDrag.isSelecting) { endSelectionDrag(); }