feat: Global Menu 계층 이동 기능 추가 및 Role 삭제 오류 수정
Global Menu indent/outdent 기능: - GlobalMenuController에 move() 메서드 추가 - MenuService에 moveGlobalMenu(), isGlobalDescendant(), reorderGlobalSiblings(), compactGlobalSiblings() 추가 - global-index.blade.php에 드래그 계층 이동 JavaScript 추가 - routes/api.php에 POST /move 라우트 추가 Role 삭제 500 에러 수정: - config/auth.php에 api guard 추가 (Spatie Permission getModelForGuard 오류 해결) - RoleService에서 불필요한 users()->detach() 제거 (FK CASCADE 처리) - RoleController에서 HTMX 요청 시 View 직접 반환 (JSON 파싱 에러 해결) - index.blade.php에서 불필요한 afterSwap 핸들러 제거 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -836,6 +836,105 @@ public function reorderGlobalMenus(array $items): bool
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 글로벌 메뉴 이동 (계층 구조 변경 - 인덴트/아웃덴트)
|
||||
* - 다른 부모 아래로 이동 가능
|
||||
* - 하위 메뉴는 자동으로 따라감
|
||||
* - 순환 참조 방지
|
||||
*/
|
||||
public function moveGlobalMenu(int $menuId, ?int $newParentId, int $sortOrder): array
|
||||
{
|
||||
$menu = GlobalMenu::find($menuId);
|
||||
if (! $menu) {
|
||||
return ['success' => false, 'message' => '글로벌 메뉴를 찾을 수 없습니다.'];
|
||||
}
|
||||
|
||||
// 순환 참조 방지: 자신의 하위 메뉴로 이동 불가
|
||||
if ($newParentId !== null && $this->isGlobalDescendant($menuId, $newParentId)) {
|
||||
return ['success' => false, 'message' => '자신의 하위 메뉴로 이동할 수 없습니다.'];
|
||||
}
|
||||
|
||||
// 자기 자신을 부모로 설정 방지
|
||||
if ($newParentId === $menuId) {
|
||||
return ['success' => false, 'message' => '자기 자신을 부모로 설정할 수 없습니다.'];
|
||||
}
|
||||
|
||||
return \DB::transaction(function () use ($menu, $newParentId, $sortOrder) {
|
||||
$oldParentId = $menu->parent_id;
|
||||
|
||||
// 부모 변경
|
||||
$menu->parent_id = $newParentId;
|
||||
$menu->sort_order = $sortOrder;
|
||||
$menu->save();
|
||||
|
||||
// 같은 부모의 다른 메뉴들 순서 재정렬
|
||||
$this->reorderGlobalSiblings($newParentId, $menu->id, $sortOrder);
|
||||
|
||||
// 이전 부모의 메뉴들도 순서 재정렬 (빈 자리 채우기)
|
||||
if ($oldParentId !== $newParentId) {
|
||||
$this->compactGlobalSiblings($oldParentId);
|
||||
}
|
||||
|
||||
return ['success' => true, 'message' => '글로벌 메뉴가 이동되었습니다.'];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 글로벌 메뉴가 다른 메뉴의 하위인지 확인 (순환 참조 방지)
|
||||
*/
|
||||
private function isGlobalDescendant(int $ancestorId, int $menuId): bool
|
||||
{
|
||||
$menu = GlobalMenu::find($menuId);
|
||||
while ($menu) {
|
||||
if ($menu->id === $ancestorId) {
|
||||
return true;
|
||||
}
|
||||
$menu = $menu->parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 같은 부모의 글로벌 형제 메뉴들 순서 재정렬
|
||||
*/
|
||||
private function reorderGlobalSiblings(?int $parentId, int $excludeId, int $insertAt): void
|
||||
{
|
||||
$siblings = GlobalMenu::where('parent_id', $parentId)
|
||||
->where('id', '!=', $excludeId)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
|
||||
$order = 1;
|
||||
foreach ($siblings as $sibling) {
|
||||
if ($order === $insertAt) {
|
||||
$order++; // 삽입 위치 건너뛰기
|
||||
}
|
||||
if ($sibling->sort_order !== $order) {
|
||||
$sibling->update(['sort_order' => $order]);
|
||||
}
|
||||
$order++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 글로벌 형제 메뉴들 순서 압축 (빈 자리 채우기)
|
||||
*/
|
||||
private function compactGlobalSiblings(?int $parentId): void
|
||||
{
|
||||
$siblings = GlobalMenu::where('parent_id', $parentId)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
|
||||
$order = 1;
|
||||
foreach ($siblings as $sibling) {
|
||||
if ($sibling->sort_order !== $order) {
|
||||
$sibling->update(['sort_order' => $order]);
|
||||
}
|
||||
$order++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 글로벌 메뉴 목록 조회 (가져오기 상태 포함)
|
||||
* - 모든 글로벌 메뉴 반환
|
||||
|
||||
Reference in New Issue
Block a user