Files
sam-manage/resources/views/user-permissions/partials/permission-matrix.blade.php
hskwon 7546771ee5 feat(mng): 개인 권한 관리 통합 매트릭스 구현
- 역할/부서/개인 권한을 통합하여 최종 유효 권한 표시
- 권한 소스별 색상 구분 UI (보라=역할, 파랑=부서, 녹색=개인허용, 빨강=개인거부)
- 스마트 토글 로직 (상속된 권한 오버라이드 지원)
- UserPermissionService: getRolePermissions(), getDepartmentPermissions(), getPersonalOverrides()
- 사용자 ID 뱃지 스타일 개선
2025-11-26 20:40:54 +09:00

164 lines
9.5 KiB
PHP

<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider" style="width: 60px;">순번</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">메뉴명</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">URL</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-700 uppercase tracking-wider" style="width: 70px;">순서</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-700 uppercase tracking-wider" style="width: 70px;">조회</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-700 uppercase tracking-wider" style="width: 70px;">생성</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-700 uppercase tracking-wider" style="width: 70px;">수정</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-700 uppercase tracking-wider" style="width: 70px;">삭제</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-700 uppercase tracking-wider" style="width: 70px;">승인</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-700 uppercase tracking-wider" style="width: 80px;">내보내기</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-700 uppercase tracking-wider" style="width: 70px;">관리</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@php
$permissionTypes = ['view', 'create', 'update', 'delete', 'approve', 'export', 'manage'];
@endphp
@forelse($menus as $index => $menu)
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 text-center">
{{ $index + 1 }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center text-sm text-gray-900" style="padding-left: {{ ($menu->depth ?? 0) * 20 }}px;">
@if(($menu->depth ?? 0) > 0)
<span class="mr-2 text-gray-400"></span>
@endif
<span>{{ $menu->name }}</span>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span class="truncate max-w-xs inline-block" title="{{ $menu->url }}">
{{ $menu->url }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 text-center">
{{ $menu->sort_order }}
</td>
@foreach($permissionTypes as $type)
@php
// 권한 데이터 추출
$permData = $permissions[$menu->id][$type] ?? ['effective' => null, 'source' => null, 'personal' => null];
$effective = $permData['effective'] ?? null;
$source = $permData['source'] ?? null;
$personal = $permData['personal'] ?? null;
// 스타일 결정
// - 개인 DENY: 빨간색 (최우선)
// - 개인 ALLOW: 녹색
// - 역할 권한: 보라색
// - 부서 권한: 파란색
// - 미설정: 회색
if ($personal === 'deny') {
$bgClass = 'bg-red-100 text-red-600 hover:bg-red-200 focus:ring-red-500';
$icon = 'deny';
$title = '개인 거부 (클릭: 미설정으로 변경)';
} elseif ($personal === 'allow') {
$bgClass = 'bg-green-100 text-green-600 hover:bg-green-200 focus:ring-green-500';
$icon = 'allow';
$title = '개인 허용 (클릭: 거부로 변경)';
} elseif ($source === 'role') {
$bgClass = 'bg-purple-100 text-purple-600 hover:bg-purple-200 focus:ring-purple-500';
$icon = 'allow';
$title = '역할 권한 (클릭: 개인 거부로 오버라이드)';
} elseif ($source === 'department') {
$bgClass = 'bg-blue-100 text-blue-600 hover:bg-blue-200 focus:ring-blue-500';
$icon = 'allow';
$title = '부서 권한 (클릭: 개인 거부로 오버라이드)';
} else {
$bgClass = 'bg-gray-100 text-gray-400 hover:bg-gray-200 focus:ring-gray-400';
$icon = 'none';
$title = '미설정 (클릭: 개인 허용으로 변경)';
}
@endphp
<td class="px-6 py-4 whitespace-nowrap text-center">
<button
type="button"
class="w-8 h-8 rounded-lg flex items-center justify-center transition-all duration-200 hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-1 {{ $bgClass }}"
title="{{ $title }}"
hx-post="/api/admin/user-permissions/toggle"
hx-target="#permission-matrix"
hx-include="[name='user_id'],[name='guard_name']"
hx-vals='{"menu_id": {{ $menu->id }}, "permission_type": "{{ $type }}"}'
>
@if($icon === 'allow')
{{-- 허용: 체크 아이콘 --}}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path>
</svg>
@elseif($icon === 'deny')
{{-- 거부: X 아이콘 --}}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"></path>
</svg>
@else
{{-- 미설정: 마이너스 아이콘 --}}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M20 12H4"></path>
</svg>
@endif
</button>
</td>
@endforeach
</tr>
@empty
<tr>
<td colspan="11" class="px-6 py-12 text-center text-gray-500">
활성화된 메뉴가 없습니다.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- 범례 --}}
<div class="mt-4 flex flex-wrap items-center gap-6 text-sm text-gray-600 px-6 pb-4">
<span class="font-medium">범례:</span>
<div class="flex items-center gap-2">
<span class="inline-flex items-center justify-center w-6 h-6 rounded bg-gray-100 text-gray-400">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M20 12H4"></path>
</svg>
</span>
<span>미설정</span>
</div>
<div class="flex items-center gap-2">
<span class="inline-flex items-center justify-center w-6 h-6 rounded bg-purple-100 text-purple-600">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path>
</svg>
</span>
<span>역할</span>
</div>
<div class="flex items-center gap-2">
<span class="inline-flex items-center justify-center w-6 h-6 rounded bg-blue-100 text-blue-600">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path>
</svg>
</span>
<span>부서</span>
</div>
<div class="flex items-center gap-2">
<span class="inline-flex items-center justify-center w-6 h-6 rounded bg-green-100 text-green-600">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path>
</svg>
</span>
<span>개인 허용</span>
</div>
<div class="flex items-center gap-2">
<span class="inline-flex items-center justify-center w-6 h-6 rounded bg-red-100 text-red-600">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</span>
<span>개인 거부</span>
</div>
</div>