From b80d6cb85ee851008cb5369e948aca3436e09ba8 Mon Sep 17 00:00:00 2001 From: dohyeons Date: Mon, 24 Nov 2025 17:02:22 +0900 Subject: [PATCH] =?UTF-8?q?=EC=98=81=EC=97=AD=EC=9D=98=20=EC=9E=90?= =?UTF-8?q?=EC=9E=AC=EB=A5=BC=20=E2=80=9C=ED=95=B4=EB=8B=B9=20=EC=98=81?= =?UTF-8?q?=EC=97=AD=E2=80=9D=EC=97=90=EB=A7=8C=20=EB=B0=B0=EC=B9=98?= =?UTF-8?q?=EA=B0=80=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/yard-3d/DigitalTwinEditor.tsx | 79 ++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx b/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx index 3a8975c8..8b2d88f8 100644 --- a/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx +++ b/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx @@ -778,9 +778,32 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi return; } - // 부모 ID 설정 + // 부모 ID 설정 및 논리적 유효성 검사 if (validation.parent) { + // 1. 부모 객체 찾기 + const parentObj = placedObjects.find((obj) => obj.id === validation.parent!.id); + + // 2. 논리적 키 검사 (DB에서 가져온 데이터인 경우) + if (parentObj && parentObj.externalKey && newObject.parentKey) { + if (parentObj.externalKey !== newObject.parentKey) { + toast({ + variant: "destructive", + title: "배치 오류", + description: `이 Location은 '${newObject.parentKey}' Area에만 배치할 수 있습니다. (현재 선택된 Area: ${parentObj.externalKey})`, + }); + return; + } + } + newObject.parentId = validation.parent.id; + } else if (newObject.parentKey) { + // DB 데이터인데 부모 영역 위에 놓이지 않은 경우 + toast({ + variant: "destructive", + title: "배치 오류", + description: `이 Location은 '${newObject.parentKey}' Area 내부에 배치해야 합니다.`, + }); + return; } } @@ -964,7 +987,59 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi return obj; }); - // 2. 그룹 이동: 자식 객체들도 함께 이동 + // 2. 하위 계층 객체 이동 시 논리적 키 검증 + if (hierarchyConfig && targetObj.hierarchyLevel && targetObj.hierarchyLevel > 1) { + const spatialObjects = updatedObjects.map((obj) => ({ + id: obj.id, + position: obj.position, + size: obj.size, + hierarchyLevel: obj.hierarchyLevel || 1, + parentId: obj.parentId, + })); + + const targetSpatialObj = spatialObjects.find((obj) => obj.id === objectId); + if (targetSpatialObj) { + const validation = validateSpatialContainment( + targetSpatialObj, + spatialObjects.filter((obj) => obj.id !== objectId), + ); + + // 새로운 부모 영역 찾기 + if (validation.parent) { + const newParentObj = prev.find((obj) => obj.id === validation.parent!.id); + + // DB에서 가져온 데이터인 경우 논리적 키 검증 + if (newParentObj && newParentObj.externalKey && targetObj.parentKey) { + if (newParentObj.externalKey !== targetObj.parentKey) { + toast({ + variant: "destructive", + title: "이동 불가", + description: `이 Location은 '${targetObj.parentKey}' Area에만 배치할 수 있습니다. (현재 선택된 Area: ${newParentObj.externalKey})`, + }); + return prev; // 이동 취소 + } + } + + // 부모 ID 업데이트 + updatedObjects = updatedObjects.map((obj) => { + if (obj.id === objectId) { + return { ...obj, parentId: validation.parent!.id }; + } + return obj; + }); + } else if (targetObj.parentKey) { + // DB 데이터인데 부모 영역 밖으로 이동하려는 경우 + toast({ + variant: "destructive", + title: "이동 불가", + description: `이 Location은 '${targetObj.parentKey}' Area 내부에 있어야 합니다.`, + }); + return prev; // 이동 취소 + } + } + } + + // 3. 그룹 이동: 자식 객체들도 함께 이동 const spatialObjects = updatedObjects.map((obj) => ({ id: obj.id, position: obj.position,