From c94e1cff41c93e86ca983fb5d5d40a125e75d391 Mon Sep 17 00:00:00 2001 From: hskwon Date: Thu, 18 Dec 2025 11:19:07 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EA=B4=80=EB=A6=AC=20HTMX?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=EB=8F=84=EA=B5=AC=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EB=8F=99=EC=A0=81=20=EB=A0=8C=EB=8D=94=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HTMX 응답 에러 수정: JSON 래핑 대신 HTML 직접 반환 - MenuController, GlobalMenuController의 index 메소드 수정 - index.blade.php, global-index.blade.php의 JSON 파싱 로직 제거 - 메뉴 options 필드 검증 추가 - StoreMenuRequest, UpdateMenuRequest에 options 필드 추가 - section 변경이 정상 저장되도록 수정 - 개발도구 메뉴 하드코딩 제거, DB 기반 동적 렌더링 - sidebar.blade.php에서 하드코딩된 메뉴 제거 - tools-menu.blade.php 컴포넌트 신규 생성 - section=tools 메뉴가 하단 고정 영역에 동적 표시 --- .../Api/Admin/GlobalMenuController.php | 6 +- .../Controllers/Api/Admin/MenuController.php | 6 +- app/Http/Requests/StoreMenuRequest.php | 3 + app/Http/Requests/UpdateMenuRequest.php | 3 + .../components/sidebar/tools-menu.blade.php | 68 +++++++++++++++++++ resources/views/menus/global-index.blade.php | 8 +-- resources/views/menus/index.blade.php | 9 +-- resources/views/partials/sidebar.blade.php | 39 ++--------- 8 files changed, 90 insertions(+), 52 deletions(-) create mode 100644 resources/views/components/sidebar/tools-menu.blade.php diff --git a/app/Http/Controllers/Api/Admin/GlobalMenuController.php b/app/Http/Controllers/Api/Admin/GlobalMenuController.php index 101b0495..e774ad7c 100644 --- a/app/Http/Controllers/Api/Admin/GlobalMenuController.php +++ b/app/Http/Controllers/Api/Admin/GlobalMenuController.php @@ -18,18 +18,18 @@ public function __construct( /** * 글로벌 메뉴 목록 조회 */ - public function index(Request $request): JsonResponse + public function index(Request $request): JsonResponse|\Illuminate\Http\Response { $menus = $this->menuService->getGlobalMenus( $request->all(), $request->integer('per_page', 100) // 글로벌 메뉴는 많지 않으므로 페이지당 100개 ); - // HTMX 요청인 경우 HTML 반환 + // HTMX 요청인 경우 HTML 직접 반환 (JSON 래핑 없이) if ($request->header('HX-Request')) { $html = view('menus.partials.global-table', compact('menus'))->render(); - return response()->json(['html' => $html]); + return response($html)->header('Content-Type', 'text/html'); } // 일반 API 요청인 경우 JSON 반환 diff --git a/app/Http/Controllers/Api/Admin/MenuController.php b/app/Http/Controllers/Api/Admin/MenuController.php index 94038e2f..07b5850e 100644 --- a/app/Http/Controllers/Api/Admin/MenuController.php +++ b/app/Http/Controllers/Api/Admin/MenuController.php @@ -18,7 +18,7 @@ public function __construct( /** * 메뉴 목록 조회 */ - public function index(Request $request): JsonResponse + public function index(Request $request): JsonResponse|\Illuminate\Http\Response { $tenantId = session('selected_tenant_id'); $importMode = $request->get('mode') === 'import' && $tenantId; @@ -34,11 +34,11 @@ public function index(Request $request): JsonResponse ); } - // HTMX 요청인 경우 HTML 반환 + // HTMX 요청인 경우 HTML 직접 반환 (JSON 래핑 없이) if ($request->header('HX-Request')) { $html = view('menus.partials.table', compact('menus', 'importMode'))->render(); - return response()->json(['html' => $html]); + return response($html)->header('Content-Type', 'text/html'); } // 일반 API 요청인 경우 JSON 반환 diff --git a/app/Http/Requests/StoreMenuRequest.php b/app/Http/Requests/StoreMenuRequest.php index 4c1dbde2..2b75e876 100644 --- a/app/Http/Requests/StoreMenuRequest.php +++ b/app/Http/Requests/StoreMenuRequest.php @@ -31,6 +31,9 @@ public function rules(): array 'hidden' => 'nullable|boolean', 'is_external' => 'nullable|boolean', 'external_url' => 'nullable|string|max:255|required_if:is_external,1', + 'options' => 'nullable|array', + 'options.section' => 'nullable|string|in:main,tools,labs', + 'options.meta' => 'nullable|array', ]; } diff --git a/app/Http/Requests/UpdateMenuRequest.php b/app/Http/Requests/UpdateMenuRequest.php index c5382836..3d94b839 100644 --- a/app/Http/Requests/UpdateMenuRequest.php +++ b/app/Http/Requests/UpdateMenuRequest.php @@ -42,6 +42,9 @@ function ($attribute, $value, $fail) use ($menuId) { 'hidden' => 'nullable|boolean', 'is_external' => 'nullable|boolean', 'external_url' => 'nullable|string|max:255|required_if:is_external,1', + 'options' => 'nullable|array', + 'options.section' => 'nullable|string|in:main,tools,labs', + 'options.meta' => 'nullable|array', ]; } diff --git a/resources/views/components/sidebar/tools-menu.blade.php b/resources/views/components/sidebar/tools-menu.blade.php new file mode 100644 index 00000000..94c2733e --- /dev/null +++ b/resources/views/components/sidebar/tools-menu.blade.php @@ -0,0 +1,68 @@ +@props(['menus']) + +@php + $sidebarMenuService = app(\App\Services\SidebarMenuService::class); +@endphp + +@foreach($menus as $toolsGroup) + @php + $groupId = 'tools-group-' . $toolsGroup->id; + $children = $toolsGroup->menuChildren ?? collect(); + $hasChildren = $children->isNotEmpty(); + $isExpanded = $sidebarMenuService->isMenuOrChildActive($toolsGroup); + @endphp + + {{-- 그룹 헤더 (접기/펼치기 버튼) --}} + + + {{-- 하위 메뉴 목록 --}} + +@endforeach \ No newline at end of file diff --git a/resources/views/menus/global-index.blade.php b/resources/views/menus/global-index.blade.php index 570ec817..179c1c10 100644 --- a/resources/views/menus/global-index.blade.php +++ b/resources/views/menus/global-index.blade.php @@ -110,13 +110,11 @@ class="bg-white rounded-lg shadow-sm overflow-hidden"> }); // HTMX 응답 처리 + SortableJS 초기화 + // 서버가 HTML을 직접 반환하므로 HTMX가 자동으로 swap 처리 document.body.addEventListener('htmx:afterSwap', function(event) { if (event.detail.target.id === 'menu-table') { - const response = JSON.parse(event.detail.xhr.response); - if (response.html) { - event.detail.target.innerHTML = response.html; - initGlobalMenuSortable(); - } + // 테이블 로드 후 SortableJS 초기화 + initGlobalMenuSortable(); } }); diff --git a/resources/views/menus/index.blade.php b/resources/views/menus/index.blade.php index 5a5f0001..a3c586b0 100644 --- a/resources/views/menus/index.blade.php +++ b/resources/views/menus/index.blade.php @@ -214,14 +214,11 @@ class="bg-white rounded-lg shadow-sm overflow-hidden"> }); // HTMX 응답 처리 + SortableJS 초기화 + // 서버가 HTML을 직접 반환하므로 HTMX가 자동으로 swap 처리 document.body.addEventListener('htmx:afterSwap', function(event) { if (event.detail.target.id === 'menu-table') { - const response = JSON.parse(event.detail.xhr.response); - if (response.html) { - event.detail.target.innerHTML = response.html; - // 테이블 로드 후 SortableJS 초기화 - initMenuSortable(); - } + // 테이블 로드 후 SortableJS 초기화 + initMenuSortable(); } }); diff --git a/resources/views/partials/sidebar.blade.php b/resources/views/partials/sidebar.blade.php index 56732419..c51dda5f 100644 --- a/resources/views/partials/sidebar.blade.php +++ b/resources/views/partials/sidebar.blade.php @@ -39,43 +39,12 @@ class="sidebar-collapsed-only hidden w-full p-2 text-xl font-bold text-gray-900 - + + @if(!empty($toolsMenus) && $toolsMenus->count() > 0)
- - +
+ @endif