@@ -11,7 +11,7 @@ import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts";
1111import { ResizablePanel } from "../components/ui/ResizablePanel" ;
1212import MainHeader from "../components/ui/MainHeader" ;
1313import toast from "react-hot-toast" ;
14- import { getStackingHint , canDeleteBlockWithStore , getStackedBlocks } from "../utils/stackingRules" ;
14+ import { getStackingHint , canDeleteBlockWithStore , getStackedBlocks , requiresParent } from "../utils/stackingRules" ;
1515import { providerManager , CloudProviderType } from "../providers" ;
1616
1717// Zustand 스토어들
@@ -258,8 +258,9 @@ function ProjectEditorPage() {
258258 const ec2Area = ec2SizeX * ec2SizeZ ;
259259 const overlapRatio = ec2Area > 0 ? overlapArea / ec2Area : 0 ;
260260
261- // Y축 검증
261+ // Y축 검증 + 스태킹 규칙 검증 (VPC 위에 EC2 올라가는 것 방지)
262262 const yValid = validateStacking ( newBlock , subnet ) ;
263+ const stackingRuleValid = canStack ( newBlock . type , subnet . type ) ;
263264
264265 console . log ( `🎯 [NewStacking] Subnet 겹침 분석: ${ subnet . type } ` , {
265266 subnetId : subnet . id . substring ( 0 , 8 ) ,
@@ -268,15 +269,16 @@ function ProjectEditorPage() {
268269 xOverlap : xOverlap . toFixed ( 2 ) ,
269270 zOverlap : zOverlap . toFixed ( 2 ) ,
270271 overlapRatio : ( overlapRatio * 100 ) . toFixed ( 1 ) + '%' ,
271- yValid
272+ yValid,
273+ stackingRuleValid : stackingRuleValid ? '✅' : '❌ (VPC 등 잘못된 타입)'
272274 } ) ;
273275
274- return { subnet, overlapRatio, yValid } ;
276+ return { subnet, overlapRatio, yValid, stackingRuleValid } ;
275277 } ) ;
276278
277- // 겹침 면적이 30% 이상이고 Y축 검증 통과한 Subnet 필터링
279+ // 겹침 면적이 30% 이상이고 Y축 검증 + 스태킹 규칙 모두 통과한 Subnet 필터링
278280 const validSubnets = subnetOverlapData
279- . filter ( data => data . overlapRatio >= 0.3 && data . yValid )
281+ . filter ( data => data . overlapRatio >= 0.3 && data . yValid && data . stackingRuleValid )
280282 . sort ( ( a , b ) => b . overlapRatio - a . overlapRatio ) ;
281283
282284 console . log ( "✅ [NewStacking] 최종 스태킹 타겟:" , {
@@ -439,11 +441,11 @@ function ProjectEditorPage() {
439441 ) ;
440442 } ;
441443
442- // 블록 이동 시 스태킹 업데이트
444+ // 블록 이동 시 스태킹 업데이트 (검증 실패 시 false 반환)
443445 const handleStackingForMovedBlock = (
444446 blockId : string ,
445447 allBlocks : DroppedBlock [ ]
446- ) => {
448+ ) : boolean => {
447449 console . log (
448450 "🔄🔄🔄 [NewStacking] ===== 이동된 블록 스태킹 업데이트 시작 ====="
449451 ) ;
@@ -458,38 +460,62 @@ function ProjectEditorPage() {
458460 const movedBlock = allBlocks . find ( ( block ) => block . id === blockId ) ;
459461 console . log ( "🔍 [NewStacking] 이동된 블록 찾기:" , ! ! movedBlock ) ;
460462
461- if ( movedBlock ) {
462- console . log ( "🎯 [NewStacking] 새로운 스태킹 처리 호출" ) ;
463- handleStackingForNewBlock ( movedBlock , allBlocks ) ;
463+ if ( ! movedBlock ) {
464+ console . log ( "❌ [NewStacking] 이동된 블록을 찾을 수 없음" ) ;
465+ return false ;
466+ }
464467
465- // 즉시 연결 업데이트
466- console . log ( "🔗 [NewStacking] 연결 업데이트 시작" ) ;
467- const derivedConnections = deriveConnectionsFromStacking ( allBlocks ) ;
468- console . log (
469- "🔗 [NewStacking] 파생된 연결 수:" ,
470- derivedConnections . length
471- ) ;
468+ // 규칙 기반 검증: VPC/Virtual Network 같은 Foundation 블록은 부모가 필요없음
469+ const needsParent = requiresParent ( movedBlock . type ) ;
472470
473- const nonStackingConnections = connections . filter (
474- ( conn ) => ! conn . properties ?. stackConnection
475- ) ;
476- console . log (
477- "🔗 [NewStacking] 비스태킹 연결 수:" ,
478- nonStackingConnections . length
479- ) ;
471+ if ( ! needsParent ) {
472+ console . log ( "✅ [NewStacking] Foundation 블록 (VPC/Virtual Network) - 스태킹 검증 생략" ) ;
473+ return true ;
474+ }
480475
481- const allConnections = [ ... nonStackingConnections , ... derivedConnections ] ;
482- console . log ( "🔗 [NewStacking] 총 연결 수:" , allConnections . length ) ;
476+ console . log ( "🎯 [NewStacking] 새로운 스태킹 처리 호출" ) ;
477+ handleStackingForNewBlock ( movedBlock , allBlocks ) ;
483478
484- setConnections ( allConnections ) ;
479+ // 즉시 연결 업데이트
480+ console . log ( "🔗 [NewStacking] 연결 업데이트 시작" ) ;
481+ const derivedConnections = deriveConnectionsFromStacking ( allBlocks ) ;
482+ console . log (
483+ "🔗 [NewStacking] 파생된 연결 수:" ,
484+ derivedConnections . length
485+ ) ;
485486
486- console . log ( "✅ [NewStacking] 이동 후 연결 업데이트 완료" ) ;
487- } else {
488- console . log ( "❌ [NewStacking] 이동된 블록을 찾을 수 없음" ) ;
487+ // 규칙 기반 검증: 부모가 필요한 블록은 반드시 연결이 있어야 함
488+ if ( needsParent ) {
489+ const hasValidConnection = derivedConnections . some ( conn =>
490+ conn . fromBlockId === blockId || conn . toBlockId === blockId
491+ ) ;
492+
493+ if ( ! hasValidConnection ) {
494+ console . log ( `❌ [NewStacking] ${ movedBlock . type } 블록이 필수 부모에 연결되지 않음 - 이동 실패` ) ;
495+ console . log ( ` 필수: ${ getStackingHint ( movedBlock . type ) } ` ) ;
496+ return false ;
497+ }
498+ console . log ( `✅ [NewStacking] ${ movedBlock . type } 블록이 올바른 부모에 연결됨` ) ;
489499 }
500+
501+ const nonStackingConnections = connections . filter (
502+ ( conn ) => ! conn . properties ?. stackConnection
503+ ) ;
504+ console . log (
505+ "🔗 [NewStacking] 비스태킹 연결 수:" ,
506+ nonStackingConnections . length
507+ ) ;
508+
509+ const allConnections = [ ...nonStackingConnections , ...derivedConnections ] ;
510+ console . log ( "🔗 [NewStacking] 총 연결 수:" , allConnections . length ) ;
511+
512+ setConnections ( allConnections ) ;
513+
514+ console . log ( "✅ [NewStacking] 이동 후 연결 업데이트 완료" ) ;
490515 console . log (
491516 "🔄🔄🔄 [NewStacking] ===== 이동된 블록 스태킹 업데이트 종료 ====="
492517 ) ;
518+ return true ;
493519 } ;
494520
495521 // 블록 변경 시 HCL 코드 자동 생성 (연결 정보 포함)
@@ -1169,6 +1195,10 @@ terraform {
11691195 finalPosition
11701196 ) ;
11711197
1198+ // 원래 위치 저장 (복원용)
1199+ const originalPosition = movingBlock . position ;
1200+ console . log ( "💾 [APP_MOVE] 원래 위치 저장:" , originalPosition ) ;
1201+
11721202 moveBlock ( blockId , finalPosition ) ;
11731203
11741204 // 드래그 종료 시 상태 초기화
@@ -1182,7 +1212,31 @@ terraform {
11821212 ) ;
11831213
11841214 console . log ( "🔄 [APP_MOVE] 업데이트된 블록 배열로 스태킹 처리" ) ;
1185- handleStackingForMovedBlock ( blockId , updatedBlocks ) ;
1215+ const stackingSuccess = handleStackingForMovedBlock ( blockId , updatedBlocks ) ;
1216+
1217+ // 스태킹 검증 실패 시 원래 위치로 복원
1218+ if ( ! stackingSuccess ) {
1219+ console . log ( "❌ [APP_MOVE] 스태킹 검증 실패 - 원래 위치로 복원" ) ;
1220+ moveBlock ( blockId , originalPosition ) ;
1221+
1222+ const hint = getStackingHint ( movingBlock . type ) ;
1223+ toast . error (
1224+ `${ movingBlock . type } 블록은 ${ hint } 올려야 합니다.\n현재 위치에서는 올바른 연결을 찾을 수 없습니다.` ,
1225+ {
1226+ id : `stacking-failed-${ blockId } ` ,
1227+ position : "bottom-center" ,
1228+ duration : 4000 ,
1229+ style : {
1230+ whiteSpace : 'pre-line' ,
1231+ maxWidth : '400px'
1232+ }
1233+ }
1234+ ) ;
1235+
1236+ console . log ( "🔄 [APP_MOVE] 원래 위치로 복원 완료:" , originalPosition ) ;
1237+ console . log ( "🎯 [APP_MOVE] ========== BLOCK MOVE REVERTED ==========" ) ;
1238+ return ;
1239+ }
11861240
11871241 console . log ( "🔄 [APP_MOVE] Block moved:" , blockId , finalPosition ) ;
11881242 console . log ( "🎯 [APP_MOVE] ========== BLOCK MOVE END ==========" ) ;
0 commit comments