From 3404b5d568bece62be43b4949d7d5d968b6a1b7c Mon Sep 17 00:00:00 2001 From: hskwon Date: Tue, 16 Dec 2025 16:27:13 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20options=20JSON=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 리스트: 섹션, 메타 컬럼 분리 표시 - 등록/수정: 확장 옵션 섹션 추가 (section, meta) - 메타 데이터 팝오버로 상세 보기 --- resources/views/menus/create.blade.php | 86 ++++++++++++++++ resources/views/menus/edit.blade.php | 97 +++++++++++++++++++ resources/views/menus/index.blade.php | 25 +++++ .../views/menus/partials/table.blade.php | 52 +++++++++- 4 files changed, 259 insertions(+), 1 deletion(-) diff --git a/resources/views/menus/create.blade.php b/resources/views/menus/create.blade.php index 996b290d..2c2c5623 100644 --- a/resources/views/menus/create.blade.php +++ b/resources/views/menus/create.blade.php @@ -109,6 +109,43 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc placeholder="https://example.com"> + +
+
+

확장 옵션

+ +
+ + +
+
0 ? options : null; + } + // 폼 제출 처리 document.getElementById('menuForm').addEventListener('submit', async function(e) { e.preventDefault(); @@ -144,6 +216,20 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition" const formData = new FormData(this); const data = Object.fromEntries(formData.entries()); + // option_ 접두사 필드 제거 + delete data.option_section; + delete data.option_meta; + + // options 객체 추가 + try { + const options = buildOptionsObject(); + if (options) { + data.options = options; + } + } catch (e) { + return; // JSON 파싱 에러 + } + try { const response = await fetch('/api/admin/menus', { method: 'POST', diff --git a/resources/views/menus/edit.blade.php b/resources/views/menus/edit.blade.php index b87708bf..b9a30911 100644 --- a/resources/views/menus/edit.blade.php +++ b/resources/views/menus/edit.blade.php @@ -122,6 +122,54 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc placeholder="https://example.com">
+ + @php + $hasOptions = $menu->options && count($menu->options) > 0; + $currentSection = $menu->getSection() ?? 'main'; + $currentMeta = $menu->getMeta(); + $currentMetaJson = $currentMeta ? json_encode($currentMeta, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : ''; + @endphp +
+
+

+ 확장 옵션 + @if($hasOptions) + {{ count($menu->options) }} + @endif +

+ +
+ +
+ +
+ + +

사이드바 영역 구분. main: 메인 메뉴, tools: 개발도구 영역, labs: R&D 실험실 영역

+
+ + +
+ + +

추가 메타 데이터 (JSON 형식). 탭 구분, 뱃지 표시 등 커스텀 속성 저장

+
+
+
+
0 ? options : null; + } + // 폼 제출 처리 document.getElementById('menuForm').addEventListener('submit', async function(e) { e.preventDefault(); @@ -157,6 +240,20 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition" const formData = new FormData(this); const data = Object.fromEntries(formData.entries()); + // option_ 접두사 필드 제거 + delete data.option_section; + delete data.option_meta; + + // options 객체 추가 + try { + const options = buildOptionsObject(); + if (options) { + data.options = options; + } + } catch (e) { + return; // JSON 파싱 에러 + } + try { const response = await fetch('/api/admin/menus/{{ $menu->id }}', { method: 'PUT', diff --git a/resources/views/menus/index.blade.php b/resources/views/menus/index.blade.php index 93afb9c3..5a5f0001 100644 --- a/resources/views/menus/index.blade.php +++ b/resources/views/menus/index.blade.php @@ -792,6 +792,31 @@ function saveMenuOrder(items) { }, { title: '메뉴 가져오기', icon: 'question' }); }; + // 옵션 팝오버 토글 + window.toggleOptionsPopover = function(menuId) { + const popover = document.getElementById(`options-popover-${menuId}`); + if (!popover) return; + + // 다른 팝오버 모두 닫기 + document.querySelectorAll('.options-popover').forEach(p => { + if (p.id !== `options-popover-${menuId}`) { + p.classList.add('hidden'); + } + }); + + // 현재 팝오버 토글 + popover.classList.toggle('hidden'); + }; + + // 외부 클릭 시 팝오버 닫기 + document.addEventListener('click', function(e) { + if (!e.target.closest('.options-popover') && !e.target.closest('[onclick*="toggleOptionsPopover"]')) { + document.querySelectorAll('.options-popover').forEach(p => { + p.classList.add('hidden'); + }); + } + }); + @endpush diff --git a/resources/views/menus/partials/table.blade.php b/resources/views/menus/partials/table.blade.php index fbea6886..af4e8f7e 100644 --- a/resources/views/menus/partials/table.blade.php +++ b/resources/views/menus/partials/table.blade.php @@ -20,6 +20,8 @@ class="w-4 h-4 rounded border-gray-300 text-green-600 focus:ring-green-500"> 활성 숨김 구분 + 섹션 + 메타 작업 @@ -155,6 +157,54 @@ class="relative inline-flex h-4 w-7 items-center rounded-full transition-colors - @endif + {{-- 섹션 칸 --}} + + @php + $section = $menu->getSection(); + $sectionColors = [ + 'main' => 'bg-gray-100 text-gray-600', + 'tools' => 'bg-orange-100 text-orange-700', + 'labs' => 'bg-purple-100 text-purple-700', + ]; + $sectionLabels = [ + 'main' => 'main', + 'tools' => 'tools', + 'labs' => 'labs', + ]; + @endphp + @if($section && $section !== 'main') + + {{ $sectionLabels[$section] ?? $section }} + + @else + - + @endif + + {{-- 메타 칸 --}} + + @php $meta = $menu->getMeta(); @endphp + @if(!empty($meta)) +
+ + {{-- 팝오버 --}} + +
+ @else + - + @endif + {{-- 작업 칸 --}} @if($importMode ?? false) @@ -188,7 +238,7 @@ class="inline-flex items-center px-2 py-1 text-xs font-medium rounded bg-red-100 @empty - + 메뉴가 없습니다.