Files
sam-manage/resources/views/user-permissions/index.blade.php
hskwon 8c348f2e02 fix: 개인 권한 관리 페이지 메뉴 리스트 표시 오류 수정
- getMenuTree()에 TenantScope 비활성화 추가 (HQ 관리자가 다른 테넌트 메뉴 조회 가능)
- getRolePermissions()에 user_roles 테이블 쿼리 추가 (테넌트별 역할 권한 반영)
- hasRolePermission(), getUserPermissionCounts()도 user_roles 포함하도록 수정
- 사용자 버튼의 data-context-menu를 아이디 뱃지로 이동 (클릭 이벤트 충돌 해결)
2025-12-09 18:43:44 +09:00

217 lines
11 KiB
PHP

@extends('layouts.app')
@section('title', '개인 권한 관리')
@section('content')
<!-- 페이지 헤더 -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">개인 권한 관리</h1>
</div>
<!-- 사용자 선택 -->
<div class="bg-white rounded-lg shadow-sm mb-6">
<div class="px-6 py-4">
@if($requireTenant)
{{-- 전체 테넌트 선택 : 테넌트 선택 안내 --}}
<div class="text-center py-8">
<div class="text-yellow-500 text-4xl mb-3">⚠️</div>
<p class="text-gray-700 font-medium mb-2">테넌트를 선택해주세요</p>
<p class="text-gray-500 text-sm">개인 권한을 관리하려면 상단 헤더에서 특정 테넌트를 선택해야 합니다.</p>
</div>
@else
{{-- 특정 테넌트 선택 : 사용자 목록 표시 --}}
@if($users->isEmpty())
<div class="text-center py-8">
<div class="text-gray-400 text-4xl mb-3">👤</div>
<p class="text-gray-700 font-medium mb-2">사용자가 없습니다</p>
<p class="text-gray-500 text-sm">선택한 테넌트에 등록된 사용자가 없습니다.</p>
</div>
@else
<div class="flex flex-wrap items-center gap-3">
<span class="text-sm font-medium text-gray-700">사용자 선택:</span>
@php
$autoSelectId = $selectedUserId ?? ($users->first()->id ?? null);
@endphp
@foreach($users as $user)
<button
type="button"
class="user-button px-4 py-2 text-sm font-medium rounded-lg border transition-colors bg-white text-gray-700 border-gray-300 hover:bg-gray-50 inline-flex items-center gap-2"
data-user-id="{{ $user->id }}"
data-user-name="{{ $user->name }}"
data-user-login="{{ $user->user_id }}"
data-auto-select="{{ $user->id == $autoSelectId ? 'true' : 'false' }}"
hx-get="/api/admin/user-permissions/matrix"
hx-target="#permission-matrix"
hx-include="[name='guard_name']"
hx-vals='{"user_id": {{ $user->id }}}'
onclick="selectUser(this)"
>
{{ $user->name }}
<span
class="user-id-badge px-1.5 py-0.5 text-xs bg-gray-200 text-gray-600 rounded cursor-pointer"
data-context-menu="user"
data-entity-id="{{ $user->id }}"
data-entity-name="{{ $user->name }}"
onclick="event.stopPropagation();"
>&nbsp;{{ $user->user_id }}&nbsp;</span>
@php
$showWebCount = auth()->user()?->is_super_admin && $user->web_permission_count > 0;
$showApiCount = $user->api_permission_count > 0;
@endphp
@if($showWebCount || $showApiCount)
<span class="permission-counts inline-flex items-center gap-1 text-[10px]">
@if($showWebCount)
<span class="px-1 py-0.5 bg-blue-100 text-blue-600 rounded">web:{{ $user->web_permission_count }}</span>
@endif
@if($showApiCount)
<span class="px-1 py-0.5 bg-green-100 text-green-600 rounded">api:{{ $user->api_permission_count }}</span>
@endif
</span>
@endif
</button>
@endforeach
</div>
@endif
@endif
</div>
</div>
<!-- 액션 버튼 -->
<div class="bg-white rounded-lg shadow-sm mb-6" id="action-buttons" style="display: none;">
<div class="px-6 py-4 border-b border-gray-200">
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-gray-700" id="selected-user-name">선택된 사용자</span>
<div class="flex items-center gap-2">
<input type="hidden" name="user_id" id="userIdInput" value="">
@if(auth()->user()?->is_super_admin)
<!-- Guard 선택 (라디오 버튼) - 슈퍼관리자만 표시 -->
<div class="flex items-center gap-4">
<label class="inline-flex items-center cursor-pointer">
<input
type="radio"
name="guard_name"
value="api"
checked
class="w-4 h-4 text-green-600 bg-gray-100 border-gray-300 focus:ring-green-500"
onchange="reloadPermissions()"
>
<span class="ml-2 px-2 py-1 text-sm font-medium bg-green-100 text-green-700 rounded">API</span>
</label>
<label class="inline-flex items-center cursor-pointer">
<input
type="radio"
name="guard_name"
value="web"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500"
onchange="reloadPermissions()"
>
<span class="ml-2 px-2 py-1 text-sm font-medium bg-blue-100 text-blue-700 rounded">Web</span>
</label>
</div>
<!-- 구분선 -->
<div class="h-8 w-px bg-gray-300 mx-1"></div>
@else
<!-- 일반 관리자: API만 사용 -->
<input type="hidden" name="guard_name" value="api">
<span class="px-2 py-1 text-sm font-medium bg-green-100 text-green-700 rounded">API 권한 관리</span>
<div class="h-8 w-px bg-gray-300 mx-1"></div>
@endif
<button
type="button"
class="px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-lg hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500"
hx-post="/api/admin/user-permissions/allow-all"
hx-target="#permission-matrix"
hx-include="[name='user_id'],[name='guard_name']"
>
전체 허용
</button>
<button
type="button"
class="px-4 py-2 bg-red-600 text-white text-sm font-medium rounded-lg hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500"
hx-post="/api/admin/user-permissions/deny-all"
hx-target="#permission-matrix"
hx-include="[name='user_id'],[name='guard_name']"
>
전체 거부
</button>
<button
type="button"
class="px-4 py-2 bg-gray-500 text-white text-sm font-medium rounded-lg hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400"
hx-post="/api/admin/user-permissions/reset"
hx-target="#permission-matrix"
hx-include="[name='user_id'],[name='guard_name']"
title="모든 메뉴의 조회(view) 권한만 허용"
>
초기화
</button>
</div>
</div>
</div>
</div>
<!-- 권한 매트릭스 테이블 -->
<div id="permission-matrix" class="bg-white rounded-lg shadow-sm">
@include('user-permissions.partials.empty-state')
</div>
<script>
function selectUser(button) {
// 모든 버튼의 활성 상태 제거
document.querySelectorAll('.user-button').forEach(btn => {
btn.classList.remove('bg-blue-700', 'text-white', 'border-blue-700', 'hover:bg-blue-800');
btn.classList.add('bg-white', 'text-gray-700', 'border-gray-300', 'hover:bg-gray-50');
// user-id 뱃지 스타일 복원
const badge = btn.querySelector('.user-id-badge');
if (badge) {
badge.classList.remove('bg-blue-500', 'text-white');
badge.classList.add('bg-gray-200', 'text-gray-600');
}
});
// 클릭된 버튼 활성화
button.classList.remove('bg-white', 'text-gray-700', 'border-gray-300', 'hover:bg-gray-50');
button.classList.add('bg-blue-700', 'text-white', 'border-blue-700', 'hover:bg-blue-800');
// user-id 뱃지 스타일 변경
const activeBadge = button.querySelector('.user-id-badge');
if (activeBadge) {
activeBadge.classList.remove('bg-gray-200', 'text-gray-600');
activeBadge.classList.add('bg-blue-500', 'text-white');
}
// 사용자 정보 저장
const userId = button.getAttribute('data-user-id');
const userName = button.getAttribute('data-user-name');
const userLogin = button.getAttribute('data-user-login');
document.getElementById('userIdInput').value = userId;
document.getElementById('selected-user-name').innerHTML = `${userName} <span class="px-3 py-1 text-sm text-blue-600 border border-blue-400 rounded ml-2">&nbsp;${userLogin}&nbsp;</span>`;
// 액션 버튼 표시
document.getElementById('action-buttons').style.display = 'block';
}
// Guard 변경 시 권한 매트릭스 새로고침
function reloadPermissions() {
const selectedButton = document.querySelector('.user-button.bg-blue-700');
if (selectedButton) {
htmx.trigger(selectedButton, 'click');
}
}
// 페이지 로드 시 첫 번째 사용자 자동 선택 (특정 테넌트 선택 시에만)
document.addEventListener('DOMContentLoaded', function() {
const autoSelectButton = document.querySelector('.user-button[data-auto-select="true"]');
if (autoSelectButton) {
// onclick 핸들러 실행
selectUser(autoSelectButton);
// HTMX 이벤트 트리거
htmx.trigger(autoSelectButton, 'click');
}
});
</script>
<script src="{{ asset('js/menu-tree.js') }}"></script>
@endsection