From b6a3c4b506aff4a464cafa7c7fcdc724b6f5ac09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Fri, 30 Jan 2026 13:16:44 +0900 Subject: [PATCH] =?UTF-8?q?feat:=EA=B3=B5=ED=86=B5=EC=BD=94=EB=93=9C/?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=B2=8C=ED=81=AC=20?= =?UTF-8?q?=EA=B8=80=EB=A1=9C=EB=B2=8C=20=EB=B3=B5=EC=82=AC,=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=ED=99=98=EA=B2=BD=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 공통코드/카테고리 테넌트→글로벌 체크박스 벌크 복사 기능 추가 - 이미 대상에 존재하는 항목 체크박스 disabled 처리 (양방향) - 공통코드 토글 크기 카테고리와 동일하게 축소 - 동기화 환경설정 모달을 공통 partial로 분리 - 동기화 리스트에서 불필요한 타입 컬럼 제거 Co-Authored-By: Claude Opus 4.5 --- .../Api/Admin/CategoryApiController.php | 47 +++++ app/Http/Controllers/CategoryController.php | 3 + app/Http/Controllers/CommonCodeController.php | 143 ++++++++++++++ resources/views/categories/index.blade.php | 66 +++++++ resources/views/categories/sync.blade.php | 28 +-- resources/views/common-codes/index.blade.php | 75 ++++++- resources/views/common-codes/sync.blade.php | 28 +-- resources/views/menus/sync.blade.php | 184 +---------------- .../partials/sync-settings-modal.blade.php | 185 ++++++++++++++++++ routes/api.php | 1 + routes/web.php | 2 + 11 files changed, 531 insertions(+), 231 deletions(-) create mode 100644 resources/views/partials/sync-settings-modal.blade.php diff --git a/app/Http/Controllers/Api/Admin/CategoryApiController.php b/app/Http/Controllers/Api/Admin/CategoryApiController.php index 8685861e..cfd5506f 100644 --- a/app/Http/Controllers/Api/Admin/CategoryApiController.php +++ b/app/Http/Controllers/Api/Admin/CategoryApiController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Models\Category; +use App\Models\GlobalCategory; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -214,6 +215,52 @@ public function toggle(int $id): JsonResponse ]); } + /** + * 테넌트 카테고리를 글로벌로 복사 (HQ 또는 슈퍼관리자) + */ + public function promoteToGlobal(int $id): JsonResponse + { + $user = Auth::user(); + $tenantId = session('selected_tenant_id'); + $tenant = $tenantId ? \App\Models\Tenants\Tenant::find($tenantId) : null; + $isHQ = $tenant?->tenant_type === 'HQ'; + $isSuperAdmin = $user?->isSuperAdmin() ?? false; + + if (! $isHQ && ! $isSuperAdmin) { + return response()->json(['success' => false, 'message' => '본사 또는 슈퍼관리자만 글로벌로 복사할 수 있습니다.'], 403); + } + + $category = Category::findOrFail($id); + + // 이미 글로벌에 동일 코드가 있는지 확인 + $exists = GlobalCategory::query() + ->where('code_group', $category->code_group) + ->where('code', $category->code) + ->whereNull('deleted_at') + ->exists(); + + if ($exists) { + return response()->json(['success' => false, 'message' => '이미 동일한 글로벌 카테고리가 존재합니다.'], 400); + } + + GlobalCategory::create([ + 'parent_id' => null, + 'code_group' => $category->code_group, + 'code' => $category->code, + 'name' => $category->name, + 'profile_code' => $category->profile_code, + 'description' => $category->description, + 'is_active' => true, + 'sort_order' => $category->sort_order, + 'created_by' => Auth::id(), + ]); + + return response()->json([ + 'success' => true, + 'message' => '글로벌 카테고리로 복사되었습니다.', + ]); + } + /** * 이동 (부모 변경) */ diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 3379634c..d53927e2 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -82,6 +82,8 @@ public function index(Request $request): View|Response $tenantCodes = $tenantCategoriesRaw->pluck('code')->toArray(); } + $globalCodes = $globalCategoriesRaw->pluck('code')->toArray(); + return view('categories.index', [ 'codeGroups' => $codeGroups, 'codeGroupLabels' => $codeGroupLabels, @@ -89,6 +91,7 @@ public function index(Request $request): View|Response 'globalCategories' => $globalCategories, 'tenantCategories' => $tenantCategories, 'tenantCodes' => $tenantCodes, + 'globalCodes' => $globalCodes, 'tenant' => $tenant, 'isHQ' => $isHQ, ]); diff --git a/app/Http/Controllers/CommonCodeController.php b/app/Http/Controllers/CommonCodeController.php index 47d6d33e..96a69ec4 100644 --- a/app/Http/Controllers/CommonCodeController.php +++ b/app/Http/Controllers/CommonCodeController.php @@ -85,6 +85,9 @@ public function index(Request $request): View|Response ->get(); } + $globalCodeKeys = $globalCodes->pluck('code')->toArray(); + $tenantCodeKeys = $tenantCodes->pluck('code')->toArray(); + return view('common-codes.index', [ 'tenant' => $tenant, 'isHQ' => $isHQ, @@ -92,6 +95,8 @@ public function index(Request $request): View|Response 'selectedGroup' => $selectedGroup, 'globalCodes' => $globalCodes, 'tenantCodes' => $tenantCodes, + 'globalCodeKeys' => $globalCodeKeys, + 'tenantCodeKeys' => $tenantCodeKeys, ]); } @@ -273,6 +278,86 @@ public function toggle(Request $request, int $id): JsonResponse ]); } + /** + * 테넌트 코드를 글로벌로 일괄 복사 (HQ 또는 슈퍼관리자) + */ + public function bulkPromoteToGlobal(Request $request): RedirectResponse|JsonResponse + { + $tenant = session('selected_tenant_id') ? Tenant::find(session('selected_tenant_id')) : null; + $isHQ = $tenant?->tenant_type === 'HQ'; + $isSuperAdmin = auth()->user()?->isSuperAdmin() ?? false; + + if (! $isHQ && ! $isSuperAdmin) { + if ($request->ajax()) { + return response()->json(['error' => '본사 또는 슈퍼관리자만 글로벌로 복사할 수 있습니다.'], 403); + } + return redirect()->back()->with('error', '본사 또는 슈퍼관리자만 글로벌로 복사할 수 있습니다.'); + } + + $idsJson = $request->input('ids_json'); + if ($idsJson) { + $ids = json_decode($idsJson, true); + if (! is_array($ids) || empty($ids)) { + return redirect()->back()->with('error', '복사할 코드를 선택해주세요.'); + } + } else { + $validated = $request->validate(['ids' => 'required|array|min:1', 'ids.*' => 'integer']); + $ids = $validated['ids']; + } + + $codeGroup = null; + $copiedCount = 0; + $skippedCount = 0; + + DB::beginTransaction(); + try { + foreach ($ids as $id) { + $tenantCode = CommonCode::whereNotNull('tenant_id')->find($id); + if (! $tenantCode) { + continue; + } + + $codeGroup = $tenantCode->code_group; + + $exists = CommonCode::query() + ->whereNull('tenant_id') + ->where('code_group', $tenantCode->code_group) + ->where('code', $tenantCode->code) + ->exists(); + + if ($exists) { + $skippedCount++; + continue; + } + + CommonCode::create([ + 'tenant_id' => null, + 'code_group' => $tenantCode->code_group, + 'code' => $tenantCode->code, + 'name' => $tenantCode->name, + 'sort_order' => $tenantCode->sort_order, + 'attributes' => $tenantCode->attributes, + 'is_active' => true, + ]); + + $copiedCount++; + } + DB::commit(); + } catch (\Exception $e) { + DB::rollBack(); + return redirect()->back()->with('error', '복사 중 오류가 발생했습니다.'); + } + + $message = "{$copiedCount}개 코드가 글로벌로 복사되었습니다."; + if ($skippedCount > 0) { + $message .= " ({$skippedCount}개는 이미 존재하여 건너뜀)"; + } + + return redirect() + ->route('common-codes.index', ['group' => $codeGroup ?? 'item_type']) + ->with('success', $message); + } + /** * 글로벌 코드를 테넌트용으로 복사 */ @@ -423,6 +508,64 @@ public function bulkCopy(Request $request): RedirectResponse|JsonResponse ->with('success', $message); } + /** + * 테넌트 코드를 글로벌로 복사 (HQ 또는 슈퍼관리자) + */ + public function promoteToGlobal(Request $request, int $id): RedirectResponse|JsonResponse + { + $tenantId = session('selected_tenant_id'); + $tenant = $tenantId ? Tenant::find($tenantId) : null; + $isHQ = $tenant?->tenant_type === 'HQ'; + $isSuperAdmin = auth()->user()?->isSuperAdmin() ?? false; + + if (! $isHQ && ! $isSuperAdmin) { + if ($request->ajax()) { + return response()->json(['error' => '본사 또는 슈퍼관리자만 글로벌로 복사할 수 있습니다.'], 403); + } + return redirect()->back()->with('error', '본사 또는 슈퍼관리자만 글로벌로 복사할 수 있습니다.'); + } + + $tenantCode = CommonCode::whereNotNull('tenant_id')->find($id); + if (! $tenantCode) { + if ($request->ajax()) { + return response()->json(['error' => '테넌트 코드를 찾을 수 없습니다.'], 404); + } + return redirect()->back()->with('error', '테넌트 코드를 찾을 수 없습니다.'); + } + + // 이미 글로벌에 동일 코드가 있는지 확인 + $exists = CommonCode::query() + ->whereNull('tenant_id') + ->where('code_group', $tenantCode->code_group) + ->where('code', $tenantCode->code) + ->exists(); + + if ($exists) { + if ($request->ajax()) { + return response()->json(['error' => '이미 동일한 글로벌 코드가 존재합니다.'], 400); + } + return redirect()->back()->with('error', '이미 동일한 글로벌 코드가 존재합니다.'); + } + + CommonCode::create([ + 'tenant_id' => null, + 'code_group' => $tenantCode->code_group, + 'code' => $tenantCode->code, + 'name' => $tenantCode->name, + 'sort_order' => $tenantCode->sort_order, + 'attributes' => $tenantCode->attributes, + 'is_active' => true, + ]); + + if ($request->ajax()) { + return response()->json(['success' => true, 'message' => '글로벌 코드로 복사되었습니다.']); + } + + return redirect() + ->route('common-codes.index', ['group' => $tenantCode->code_group]) + ->with('success', '테넌트 코드가 글로벌로 복사되었습니다.'); + } + /** * 코드 삭제 (테넌트 코드만) */ diff --git a/resources/views/categories/index.blade.php b/resources/views/categories/index.blade.php index cdc37599..881a7240 100644 --- a/resources/views/categories/index.blade.php +++ b/resources/views/categories/index.blade.php @@ -172,11 +172,31 @@ class="relative inline-flex h-4 w-7 items-center rounded-full transition-colors

테넌트 카테고리

({{ $tenantCategories->count() }}) +
+ @if($tenantCategories->count() > 0 && ($isHQ || auth()->user()?->isSuperAdmin())) + + @endif +
@forelse($tenantCategories as $cat) + @php $existsInGlobal = in_array($cat->code, $globalCodes); @endphp
+ @if($isHQ || auth()->user()?->isSuperAdmin()) + + @endif {{ $cat->code }} {{ $cat->name }} @if($cat->depth > 0) @@ -459,6 +479,52 @@ function copyToTenant(globalId) { .catch(() => showToast('복사 중 오류가 발생했습니다.', 'error')); } + // 벌크 글로벌 복사 버튼 상태 업데이트 + function updateBulkPromoteBtn() { + const checked = document.querySelectorAll('.tenant-cat-checkbox:checked'); + const btn = document.getElementById('bulkPromoteBtn'); + if (btn) { + if (checked.length > 0) { + btn.classList.remove('hidden'); + btn.classList.add('flex'); + document.getElementById('bulkPromoteCount').textContent = checked.length; + } else { + btn.classList.add('hidden'); + btn.classList.remove('flex'); + } + } + } + + // 벌크 테넌트 → 글로벌 복사 + function bulkPromoteToGlobal() { + const ids = Array.from(document.querySelectorAll('.tenant-cat-checkbox:checked')).map(cb => cb.value); + if (ids.length === 0) return; + + if (!confirm(`선택한 ${ids.length}개 테넌트 카테고리를 글로벌로 복사하시겠습니까?`)) return; + + let completed = 0, failed = 0, skipped = 0; + const promises = ids.map(id => + fetch(`/api/admin/categories/${id}/promote-to-global`, { + method: 'POST', + headers: { 'X-CSRF-TOKEN': csrfToken, 'Accept': 'application/json' } + }) + .then(r => r.json()) + .then(result => { + if (result.success) completed++; + else skipped++; + }) + .catch(() => failed++) + ); + + Promise.all(promises).then(() => { + let msg = `완료: ${completed}개 복사`; + if (skipped > 0) msg += `, ${skipped}개 건너뜀`; + if (failed > 0) msg += `, ${failed}개 실패`; + showToast(msg, failed > 0 ? 'error' : 'success'); + location.reload(); + }); + } + // 전체 선택 function toggleSelectAll(checkbox) { document.querySelectorAll('.global-checkbox').forEach(cb => cb.checked = checkbox.checked); diff --git a/resources/views/categories/sync.blade.php b/resources/views/categories/sync.blade.php index f6914bea..d67aba39 100644 --- a/resources/views/categories/sync.blade.php +++ b/resources/views/categories/sync.blade.php @@ -18,14 +18,14 @@ class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg 카테고리 관리 - +
@@ -88,7 +88,7 @@ class="px-6 py-3 text-sm font-medium border-b-2 transition-colors - 메뉴 동기화 환경 설정에서 {{ $environments[$selectedEnv]['name'] ?? strtoupper($selectedEnv) }} 서버 URL을 설정해주세요. + 에서 {{ $environments[$selectedEnv]['name'] ?? strtoupper($selectedEnv) }} 서버 URL을 설정해주세요.
@endif @@ -168,7 +168,6 @@ class="px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white text-xs font-mediu - 타입 그룹 코드 이름 @@ -189,13 +188,6 @@ class="px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white text-xs font-mediu class="w-4 h-4 text-green-600 border-gray-300 rounded focus:ring-green-500" {{ $inBoth ? 'disabled' : '' }}> - - @if($cat['is_global']) - 글로벌 - @else - 테넌트 - @endif - {{ $cat['code_group'] }} {{ $cat['code'] }} {{ $cat['name'] }} @@ -203,7 +195,7 @@ class="w-4 h-4 text-green-600 border-gray-300 rounded focus:ring-green-500" @empty - 카테고리가 없습니다. + 카테고리가 없습니다. @endforelse @@ -253,7 +245,6 @@ class="px-3 py-1.5 bg-purple-600 hover:bg-purple-700 text-white text-xs font-med - 타입 그룹 코드 이름 @@ -274,13 +265,6 @@ class="px-3 py-1.5 bg-purple-600 hover:bg-purple-700 text-white text-xs font-med class="w-4 h-4 text-purple-600 border-gray-300 rounded focus:ring-purple-500" {{ $inBoth ? 'disabled' : '' }}> - - @if($cat['is_global']) - 글로벌 - @else - 테넌트 - @endif - {{ $cat['code_group'] }} {{ $cat['code'] }} {{ $cat['name'] }} @@ -294,6 +278,8 @@ class="w-4 h-4 text-purple-600 border-gray-300 rounded focus:ring-purple-500" + +@include('partials.sync-settings-modal') @endsection @push('scripts') diff --git a/resources/views/common-codes/index.blade.php b/resources/views/common-codes/index.blade.php index 7daa3483..619bb66b 100644 --- a/resources/views/common-codes/index.blade.php +++ b/resources/views/common-codes/index.blade.php @@ -140,11 +140,13 @@ class="w-4 h-4 text-green-600 border-gray-300 rounded focus:ring-green-500"> @forelse($globalCodes as $code) + @php $existsInTenant = in_array($code->code, $tenantCodeKeys); @endphp + class="global-code-checkbox w-4 h-4 rounded focus:ring-green-500 {{ $existsInTenant ? 'bg-gray-200 border-gray-300 cursor-not-allowed' : 'text-green-600 border-gray-300' }}" + {{ $existsInTenant ? 'disabled' : '' }}> {{ $code->code }} @@ -155,8 +157,8 @@ class="global-code-checkbox w-4 h-4 text-green-600 border-gray-300 rounded focus @if($isHQ) @else @@ -221,11 +223,31 @@ class="p-1 text-gray-400 hover:text-red-600 transition-colors"

테넌트 코드

({{ $tenantCodes->count() }}) +
+ @if($tenantCodes->count() > 0 && ($isHQ || auth()->user()?->isSuperAdmin())) + + @endif +
+ @if($isHQ || auth()->user()?->isSuperAdmin()) + + @endif @@ -235,7 +257,16 @@ class="p-1 text-gray-400 hover:text-red-600 transition-colors" @forelse($tenantCodes as $code) + @php $existsInGlobal = in_array($code->code, $globalCodeKeys); @endphp + @if($isHQ || auth()->user()?->isSuperAdmin()) + + @endif @@ -244,8 +275,8 @@ class="p-1 text-gray-400 hover:text-red-600 transition-colors" @empty - @@ -403,6 +434,12 @@ class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon @csrf + + + @csrf + + + @csrf @@ -469,6 +506,32 @@ function copyCode(id) { form.submit(); } + function toggleSelectAllTenant(checkbox) { + document.querySelectorAll('.tenant-code-checkbox').forEach(cb => cb.checked = checkbox.checked); + updateBulkPromoteButton(); + } + + function updateBulkPromoteButton() { + const checked = document.querySelectorAll('.tenant-code-checkbox:checked'); + const btn = document.getElementById('bulkPromoteBtn'); + const count = document.getElementById('bulkPromoteCount'); + if (btn) { + btn.classList.toggle('hidden', checked.length === 0); + btn.classList.toggle('flex', checked.length > 0); + } + if (count) count.textContent = checked.length; + } + + function bulkPromote() { + const ids = Array.from(document.querySelectorAll('.tenant-code-checkbox:checked')).map(cb => parseInt(cb.value)); + if (ids.length === 0) return; + if (!confirm(`선택한 ${ids.length}개 테넌트 코드를 글로벌로 복사하시겠습니까?`)) return; + + const form = document.getElementById('bulkPromoteForm'); + document.getElementById('bulkPromoteIds').value = JSON.stringify(ids); + form.submit(); + } + // 전체 선택 토글 function toggleSelectAll(checkbox) { const checkboxes = document.querySelectorAll('.global-code-checkbox'); diff --git a/resources/views/common-codes/sync.blade.php b/resources/views/common-codes/sync.blade.php index 7f3c3fa3..6029b05f 100644 --- a/resources/views/common-codes/sync.blade.php +++ b/resources/views/common-codes/sync.blade.php @@ -18,14 +18,14 @@ class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg 코드 관리 - + @@ -88,7 +88,7 @@ class="px-6 py-3 text-sm font-medium border-b-2 transition-colors - 메뉴 동기화 환경 설정에서 {{ $environments[$selectedEnv]['name'] ?? strtoupper($selectedEnv) }} 서버 URL을 설정해주세요. + 에서 {{ $environments[$selectedEnv]['name'] ?? strtoupper($selectedEnv) }} 서버 URL을 설정해주세요. @endif @@ -168,7 +168,6 @@ class="px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white text-xs font-mediu - @@ -194,20 +193,13 @@ class="px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white text-xs font-mediu class="w-4 h-4 text-green-600 border-gray-300 rounded focus:ring-green-500" {{ $inBoth ? 'disabled' : '' }}> - @empty - + @endforelse @@ -257,7 +249,6 @@ class="px-3 py-1.5 bg-purple-600 hover:bg-purple-700 text-white text-xs font-med - @@ -276,13 +267,6 @@ class="px-3 py-1.5 bg-purple-600 hover:bg-purple-700 text-white text-xs font-med class="w-4 h-4 text-purple-600 border-gray-300 rounded focus:ring-purple-500" {{ $inBoth ? 'disabled' : '' }}> - @@ -295,6 +279,8 @@ class="w-4 h-4 text-purple-600 border-gray-300 rounded focus:ring-purple-500" + +@include('partials.sync-settings-modal') @endsection @push('scripts') diff --git a/resources/views/menus/sync.blade.php b/resources/views/menus/sync.blade.php index 4f864e45..2cfed22a 100644 --- a/resources/views/menus/sync.blade.php +++ b/resources/views/menus/sync.blade.php @@ -193,96 +193,7 @@ class="px-3 py-1.5 bg-purple-600 hover:bg-purple-700 text-white text-xs font-med - - +@include('partials.sync-settings-modal') @endsection @push('scripts') @@ -290,89 +201,6 @@ class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon const selectedEnv = '{{ $selectedEnv }}'; const csrfToken = '{{ csrf_token() }}'; - // 설정 모달 - function openSettingsModal() { - document.getElementById('settingsModal').classList.remove('hidden'); - document.getElementById('settingsModal').classList.add('flex'); - } - - function closeSettingsModal() { - document.getElementById('settingsModal').classList.add('hidden'); - document.getElementById('settingsModal').classList.remove('flex'); - } - - // 설정 저장 - async function saveSettings() { - const data = { - environments: { - dev: { - name: '개발', - url: document.getElementById('devUrl').value, - api_key: document.getElementById('devApiKey').value - }, - prod: { - name: '운영', - url: document.getElementById('prodUrl').value, - api_key: document.getElementById('prodApiKey').value - } - } - }; - - try { - const response = await fetch('{{ route("menus.sync.settings") }}', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': csrfToken, - 'Accept': 'application/json' - }, - body: JSON.stringify(data) - }); - - const result = await response.json(); - if (result.success) { - alert('설정이 저장되었습니다.'); - location.reload(); - } else { - alert(result.error || '저장 실패'); - } - } catch (e) { - alert('오류 발생: ' + e.message); - } - } - - // 연결 테스트 - async function testConnection(env) { - const url = document.getElementById(env + 'Url').value; - const apiKey = document.getElementById(env + 'ApiKey').value; - - if (!url || !apiKey) { - alert('URL과 API Key를 입력해주세요.'); - return; - } - - try { - const response = await fetch('{{ route("menus.sync.test") }}', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': csrfToken, - 'Accept': 'application/json' - }, - body: JSON.stringify({ url, api_key: apiKey }) - }); - - const result = await response.json(); - if (result.success) { - alert(`연결 성공!\n환경: ${result.environment}\n메뉴 수: ${result.menu_count}개`); - } else { - alert('연결 실패: ' + result.message); - } - } catch (e) { - alert('오류 발생: ' + e.message); - } - } - // 선택된 메뉴 Push async function pushSelected() { const checkboxes = document.querySelectorAll('input[name="local_menu"]:checked'); @@ -449,16 +277,6 @@ function closeSettingsModal() { } } - // 모달 외부 클릭 시 닫기 - document.getElementById('settingsModal').addEventListener('click', function(e) { - if (e.target === this) closeSettingsModal(); - }); - - // ESC 키로 모달 닫기 - document.addEventListener('keydown', function(e) { - if (e.key === 'Escape') closeSettingsModal(); - }); - // 전체 선택 (활성화된 체크박스만) function toggleSelectAll(side, checked) { const checkboxes = document.querySelectorAll(`input[name="${side}_menu"]:not(:disabled)`); diff --git a/resources/views/partials/sync-settings-modal.blade.php b/resources/views/partials/sync-settings-modal.blade.php new file mode 100644 index 00000000..8080d219 --- /dev/null +++ b/resources/views/partials/sync-settings-modal.blade.php @@ -0,0 +1,185 @@ + + + + \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 8d060e20..76fceb16 100644 --- a/routes/api.php +++ b/routes/api.php @@ -824,6 +824,7 @@ // 추가 액션 Route::post('/{id}/toggle', [CategoryApiController::class, 'toggle'])->name('toggle'); Route::post('/{id}/move', [CategoryApiController::class, 'move'])->name('move'); + Route::post('/{id}/promote-to-global', [CategoryApiController::class, 'promoteToGlobal'])->name('promoteToGlobal'); }); /* diff --git a/routes/web.php b/routes/web.php index ccee5b18..61a10d45 100644 --- a/routes/web.php +++ b/routes/web.php @@ -318,9 +318,11 @@ Route::get('/', [CommonCodeController::class, 'index'])->name('index'); Route::post('/', [CommonCodeController::class, 'store'])->name('store'); Route::post('/bulk-copy', [CommonCodeController::class, 'bulkCopy'])->name('bulk-copy'); + Route::post('/bulk-promote', [CommonCodeController::class, 'bulkPromoteToGlobal'])->name('bulk-promote'); Route::put('/{id}', [CommonCodeController::class, 'update'])->name('update'); Route::post('/{id}/toggle', [CommonCodeController::class, 'toggle'])->name('toggle'); Route::post('/{id}/copy', [CommonCodeController::class, 'copy'])->name('copy'); + Route::post('/{id}/promote', [CommonCodeController::class, 'promoteToGlobal'])->name('promote'); Route::delete('/{id}', [CommonCodeController::class, 'destroy'])->name('destroy'); // 공통코드 동기화
+ + 코드 이름 순서
+ + {{ $code->code }} @@ -271,7 +302,7 @@ class="p-1 text-gray-400 hover:text-red-600 transition-colors"
+ 테넌트 코드가 없습니다.
글로벌 코드를 복사하거나 새로 추가하세요.
타입 그룹 코드 이름 - @if($code['is_global'] ?? false) - 글로벌 - @else - 테넌트 - @endif - {{ $code['code_group'] }} {{ $code['code'] }} {{ $code['name'] }}
코드가 없습니다.코드가 없습니다.
타입 그룹 코드 이름 - @if($code['is_global'] ?? false) - 글로벌 - @else - 테넌트 - @endif - {{ $code['code_group'] }} {{ $code['code'] }} {{ $code['name'] }}