fix(mng): HTMX SPA 네비게이션 오류 수정
## 수정 내용 ### HTMX 응답 형식 수정 - DepartmentController: view 직접 반환 (JSON 래핑 제거) - MenuController: ->render() 제거하여 SVG 이스케이프 문제 해결 ### 사이드바 개선 - hx-boost 적용하여 SPA 스타일 네비게이션 구현 - 메뉴 클릭 시 활성화 상태 즉시 반영 - 스크롤 위치 저장/복원 기능 추가 ### 불필요한 코드 제거 - departments/index.blade.php: JSON.parse 코드 제거 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,20 +18,16 @@ public function __construct(
|
||||
/**
|
||||
* 부서 목록 조회
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
public function index(Request $request): JsonResponse|\Illuminate\View\View
|
||||
{
|
||||
$departments = $this->departmentService->getDepartments(
|
||||
$request->all(),
|
||||
$request->integer('per_page', 10)
|
||||
);
|
||||
|
||||
// HTMX 요청 시 HTML 반환
|
||||
// HTMX 요청 시 HTML 직접 반환
|
||||
if ($request->header('HX-Request')) {
|
||||
$html = view('departments.partials.table', compact('departments'))->render();
|
||||
|
||||
return response()->json([
|
||||
'html' => $html,
|
||||
]);
|
||||
return view('departments.partials.table', compact('departments'));
|
||||
}
|
||||
|
||||
// 일반 요청 시 JSON 반환
|
||||
|
||||
@@ -34,11 +34,9 @@ public function index(Request $request): JsonResponse|\Illuminate\Http\Response
|
||||
);
|
||||
}
|
||||
|
||||
// HTMX 요청인 경우 HTML 직접 반환 (JSON 래핑 없이)
|
||||
// HTMX 요청인 경우 HTML 직접 반환
|
||||
if ($request->header('HX-Request')) {
|
||||
$html = view('menus.partials.table', compact('menus', 'importMode'))->render();
|
||||
|
||||
return response($html)->header('Content-Type', 'text/html');
|
||||
return view('menus.partials.table', compact('menus', 'importMode'));
|
||||
}
|
||||
|
||||
// 일반 API 요청인 경우 JSON 반환
|
||||
|
||||
@@ -70,16 +70,6 @@ class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
htmx.trigger('#department-table', 'filterSubmit');
|
||||
});
|
||||
|
||||
// HTMX 응답 처리
|
||||
document.body.addEventListener('htmx:afterSwap', function(event) {
|
||||
if (event.detail.target.id === 'department-table') {
|
||||
const response = JSON.parse(event.detail.xhr.response);
|
||||
if (response.html) {
|
||||
event.detail.target.innerHTML = response.html;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 삭제 확인
|
||||
window.confirmDelete = function(id, name) {
|
||||
showDeleteConfirm(name, () => {
|
||||
|
||||
@@ -63,7 +63,12 @@ class="w-full border-gray-300 rounded-lg text-sm focus:ring-primary focus:border
|
||||
</div>
|
||||
|
||||
<!-- Navigation Menu -->
|
||||
<nav class="flex-1 overflow-y-auto p-4 sidebar-nav">
|
||||
<nav class="flex-1 overflow-y-auto p-4 sidebar-nav"
|
||||
hx-boost="true"
|
||||
hx-target="#main-content"
|
||||
hx-select="#main-content"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true">
|
||||
<ul class="space-y-1">
|
||||
{{-- Main Section Menus (Dynamic from DB) --}}
|
||||
<x-sidebar.menu-tree :menus="$mainMenus" />
|
||||
@@ -75,7 +80,12 @@ class="w-full border-gray-300 rounded-lg text-sm focus:ring-primary focus:border
|
||||
|
||||
<!-- 개발 도구 (하단 고정, DB 기반) -->
|
||||
@if(!empty($toolsMenus) && $toolsMenus->count() > 0)
|
||||
<div class="border-t border-gray-200 p-2 bg-gray-50">
|
||||
<div class="border-t border-gray-200 p-2 bg-gray-50"
|
||||
hx-boost="true"
|
||||
hx-target="#main-content"
|
||||
hx-select="#main-content"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true">
|
||||
<x-sidebar.tools-menu :menus="$toolsMenus" />
|
||||
</div>
|
||||
@endif
|
||||
@@ -565,20 +575,49 @@ function scrollSidebarToBottom() {
|
||||
// 저장된 스크롤 위치 복원
|
||||
function restoreSidebarScroll() {
|
||||
const sidebarNav = document.querySelector('.sidebar-nav');
|
||||
const scrollToBottom = localStorage.getItem('sidebar-scroll-bottom');
|
||||
if (!sidebarNav) return;
|
||||
|
||||
if (sidebarNav && scrollToBottom === 'true') {
|
||||
const scrollToBottom = localStorage.getItem('sidebar-scroll-bottom');
|
||||
const savedScrollTop = localStorage.getItem('sidebar-scroll-top');
|
||||
|
||||
if (scrollToBottom === 'true') {
|
||||
setTimeout(function() {
|
||||
sidebarNav.scrollTop = sidebarNav.scrollHeight;
|
||||
}, 100);
|
||||
} else if (savedScrollTop) {
|
||||
setTimeout(function() {
|
||||
sidebarNav.scrollTop = parseInt(savedScrollTop, 10);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// 일반 메뉴 클릭 시 스크롤 위치 초기화
|
||||
function resetSidebarScroll() {
|
||||
localStorage.removeItem('sidebar-scroll-bottom');
|
||||
// 메뉴 클릭 시 현재 스크롤 위치 저장
|
||||
function saveSidebarScroll() {
|
||||
const sidebarNav = document.querySelector('.sidebar-nav');
|
||||
if (sidebarNav) {
|
||||
localStorage.setItem('sidebar-scroll-top', sidebarNav.scrollTop);
|
||||
localStorage.removeItem('sidebar-scroll-bottom');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== HTMX 메뉴 활성화 ==========
|
||||
|
||||
// 메뉴 클릭 시 활성화 처리
|
||||
document.getElementById('sidebar').addEventListener('click', function(e) {
|
||||
const link = e.target.closest('a[href]');
|
||||
if (!link || link.getAttribute('href') === '#') return;
|
||||
|
||||
// 모든 메뉴에서 활성화 클래스 제거
|
||||
this.querySelectorAll('nav a, .border-t a').forEach(a => {
|
||||
a.classList.remove('bg-primary', 'text-white', 'hover:bg-primary');
|
||||
a.classList.add('text-gray-700', 'hover:bg-gray-100');
|
||||
});
|
||||
|
||||
// 클릭한 메뉴에 활성화 클래스 추가
|
||||
link.classList.remove('text-gray-700', 'hover:bg-gray-100');
|
||||
link.classList.add('bg-primary', 'text-white', 'hover:bg-primary');
|
||||
});
|
||||
|
||||
// ========== R&D Labs 탭 전환 함수 ==========
|
||||
|
||||
// 확장 상태: 탭 전환
|
||||
@@ -825,13 +864,24 @@ function initSidebarTooltips() {
|
||||
});
|
||||
}
|
||||
|
||||
// 일반 메뉴 클릭 시 스크롤 위치 초기화
|
||||
// 메뉴 클릭 시 스크롤 위치 저장
|
||||
const sidebarNav = document.querySelector('.sidebar-nav');
|
||||
if (sidebarNav) {
|
||||
sidebarNav.addEventListener('click', function(e) {
|
||||
const link = e.target.closest('a[href]');
|
||||
if (link && !link.closest('#lab-menu-container')) {
|
||||
resetSidebarScroll();
|
||||
saveSidebarScroll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 개발 도구 메뉴 클릭 시 스크롤 위치 저장
|
||||
const toolsMenu = document.querySelector('.border-t.border-gray-200');
|
||||
if (toolsMenu) {
|
||||
toolsMenu.addEventListener('click', function(e) {
|
||||
const link = e.target.closest('a[href]');
|
||||
if (link) {
|
||||
saveSidebarScroll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user