feat(mng): 권한 관리 기능 구현
- Permission 모델 및 PermissionService 생성 (Spatie Permission 확장) - HTMX 기반 권한 CRUD API 구현 - Blade 기반 권한 관리 화면 (index, create, edit) - 권한명 포맷팅 로직 추가 (menu:id.type 파싱) - 사이드바 메뉴 추가 - 멀티테넌트 지원 (tenant_id nullable) - 할당된 역할/부서 표시 기능
This commit is contained in:
@@ -53,6 +53,17 @@ class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- 권한 관리 -->
|
||||
<li>
|
||||
<a href="{{ route('permissions.index') }}"
|
||||
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100 {{ request()->routeIs('permissions.*') ? 'bg-primary text-white hover:bg-primary' : '' }}">
|
||||
<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" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
<span class="font-medium">권한 관리</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- 부서 관리 -->
|
||||
<li>
|
||||
<a href="{{ route('departments.index') }}"
|
||||
|
||||
109
resources/views/permissions/create.blade.php
Normal file
109
resources/views/permissions/create.blade.php
Normal file
@@ -0,0 +1,109 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '권한 생성')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">🛡️ 새 권한 생성</h1>
|
||||
<a href="{{ route('permissions.index') }}" class="text-gray-600 hover:text-gray-800">
|
||||
← 목록으로
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 폼 -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<form id="permissionForm" action="/api/admin/permissions" method="POST">
|
||||
@csrf
|
||||
|
||||
<!-- 권한 이름 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
권한 이름 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text"
|
||||
name="name"
|
||||
required
|
||||
placeholder="예: users.view, products.create, orders.delete"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<p class="mt-1 text-sm text-gray-500">영문 소문자, 점(.), 하이픈(-), 언더스코어(_)만 사용</p>
|
||||
</div>
|
||||
|
||||
<!-- Guard 이름 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
Guard 이름 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="guard_name"
|
||||
required
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="web" selected>web</option>
|
||||
<option value="api">api</option>
|
||||
</select>
|
||||
<p class="mt-1 text-sm text-gray-500">기본값: web</p>
|
||||
</div>
|
||||
|
||||
<!-- 테넌트 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
테넌트
|
||||
</label>
|
||||
<select name="tenant_id"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">전체 (마스터 권한)</option>
|
||||
@foreach($tenants as $tenant)
|
||||
<option value="{{ $tenant->id }}" {{ session('selected_tenant_id') == $tenant->id ? 'selected' : '' }}>
|
||||
{{ $tenant->company_name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="mt-1 text-sm text-gray-500">선택하지 않으면 전체 테넌트에서 사용 가능한 마스터 권한</p>
|
||||
</div>
|
||||
|
||||
<!-- 버튼 -->
|
||||
<div class="flex gap-3">
|
||||
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition">
|
||||
생성
|
||||
</button>
|
||||
<a href="{{ route('permissions.index') }}" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-2 rounded-lg transition">
|
||||
취소
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.getElementById('permissionForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
fetch('/api/admin/permissions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert(data.message);
|
||||
window.location.href = '{{ route("permissions.index") }}';
|
||||
} else {
|
||||
alert(data.message || '권한 생성에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('권한 생성 중 오류가 발생했습니다.');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
157
resources/views/permissions/edit.blade.php
Normal file
157
resources/views/permissions/edit.blade.php
Normal file
@@ -0,0 +1,157 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '권한 수정')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">🛡️ 권한 수정</h1>
|
||||
<a href="{{ route('permissions.index') }}" class="text-gray-600 hover:text-gray-800">
|
||||
← 목록으로
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 폼 -->
|
||||
<div id="loadingState" class="bg-white rounded-lg shadow-sm p-6">
|
||||
<div class="flex justify-center items-center p-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="formContainer" class="bg-white rounded-lg shadow-sm p-6" style="display: none;">
|
||||
<form id="permissionForm" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<!-- 권한 이름 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
권한 이름 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
required
|
||||
placeholder="예: users.view, products.create"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<p class="mt-1 text-sm text-gray-500">영문 소문자, 점(.), 하이픈(-), 언더스코어(_)만 사용</p>
|
||||
</div>
|
||||
|
||||
<!-- Guard 이름 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
Guard 이름 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="guard_name"
|
||||
id="guard_name"
|
||||
required
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="web">web</option>
|
||||
<option value="api">api</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 테넌트 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
테넌트
|
||||
</label>
|
||||
<select name="tenant_id"
|
||||
id="tenant_id"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">전체 (마스터 권한)</option>
|
||||
@foreach($tenants as $tenant)
|
||||
<option value="{{ $tenant->id }}">{{ $tenant->company_name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 할당된 역할 수 (읽기 전용) -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
할당된 역할 수
|
||||
</label>
|
||||
<p id="rolesCount" class="text-gray-900 text-lg font-medium">-</p>
|
||||
</div>
|
||||
|
||||
<!-- 버튼 -->
|
||||
<div class="flex gap-3">
|
||||
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition">
|
||||
수정
|
||||
</button>
|
||||
<a href="{{ route('permissions.index') }}" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-2 rounded-lg transition">
|
||||
취소
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
const permissionId = {{ $id }};
|
||||
|
||||
// 권한 정보 로드
|
||||
fetch(`/api/admin/permissions/${permissionId}`, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const permission = data.data;
|
||||
|
||||
// 폼 필드 채우기
|
||||
document.getElementById('name').value = permission.name;
|
||||
document.getElementById('guard_name').value = permission.guard_name;
|
||||
document.getElementById('tenant_id').value = permission.tenant_id || '';
|
||||
document.getElementById('rolesCount').textContent = permission.roles?.length || 0;
|
||||
|
||||
// 로딩 숨기고 폼 표시
|
||||
document.getElementById('loadingState').style.display = 'none';
|
||||
document.getElementById('formContainer').style.display = 'block';
|
||||
} else {
|
||||
alert('권한 정보를 불러올 수 없습니다.');
|
||||
window.location.href = '{{ route("permissions.index") }}';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('권한 정보 로드 중 오류가 발생했습니다.');
|
||||
window.location.href = '{{ route("permissions.index") }}';
|
||||
});
|
||||
|
||||
// 폼 제출
|
||||
document.getElementById('permissionForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
fetch(`/api/admin/permissions/${permissionId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert(data.message);
|
||||
window.location.href = '{{ route("permissions.index") }}';
|
||||
} else {
|
||||
alert(data.message || '권한 수정에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('권한 수정 중 오류가 발생했습니다.');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
92
resources/views/permissions/index.blade.php
Normal file
92
resources/views/permissions/index.blade.php
Normal file
@@ -0,0 +1,92 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '권한 관리')
|
||||
|
||||
@section('content')
|
||||
<!-- Tenant Selector -->
|
||||
@include('partials.tenant-selector')
|
||||
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="flex justify-between items-center mt-6 mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">🛡️ 권한 관리</h1>
|
||||
<a href="{{ route('permissions.create') }}" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition">
|
||||
+ 새 권한
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 필터 영역 -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
|
||||
<form id="filterForm" class="flex gap-4">
|
||||
<!-- 검색 -->
|
||||
<div class="flex-1">
|
||||
<input type="text"
|
||||
name="search"
|
||||
placeholder="권한 이름으로 검색..."
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<!-- Guard 필터 -->
|
||||
<div class="w-48">
|
||||
<select name="guard_name" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">전체 Guard</option>
|
||||
<option value="web">web</option>
|
||||
<option value="api">api</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 검색 버튼 -->
|
||||
<button type="submit" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded-lg transition">
|
||||
검색
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 테이블 영역 (HTMX로 로드) -->
|
||||
<div id="permission-table"
|
||||
hx-get="/api/admin/permissions"
|
||||
hx-trigger="load, filterSubmit from:body"
|
||||
hx-include="#filterForm"
|
||||
hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
|
||||
class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<!-- 로딩 스피너 -->
|
||||
<div class="flex justify-center items-center p-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||
<script>
|
||||
// 폼 제출 시 HTMX 이벤트 트리거
|
||||
document.getElementById('filterForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
htmx.trigger('#permission-table', 'filterSubmit');
|
||||
});
|
||||
|
||||
// 권한 삭제 확인
|
||||
function confirmDelete(id, name) {
|
||||
if (confirm(`"${name}" 권한을 삭제하시겠습니까?\n\n이 권한이 할당된 역할이 있는 경우 삭제할 수 없습니다.`)) {
|
||||
fetch(`/api/admin/permissions/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
htmx.trigger('#permission-table', 'filterSubmit');
|
||||
alert(data.message);
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('권한 삭제 중 오류가 발생했습니다.');
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
195
resources/views/permissions/partials/table.blade.php
Normal file
195
resources/views/permissions/partials/table.blade.php
Normal file
@@ -0,0 +1,195 @@
|
||||
@php
|
||||
use App\Models\Commons\Menu;
|
||||
|
||||
/**
|
||||
* 권한명을 파싱하여 메뉴 태그와 권한 타입을 시각적으로 표시
|
||||
*/
|
||||
function formatPermissionName(string $permissionName): string
|
||||
{
|
||||
// menu:{menu_id}.{permission_type} 패턴 파싱
|
||||
if (preg_match('/^menu:(\d+)\.(\w+)$/', $permissionName, $matches)) {
|
||||
$menuId = (int) $matches[1];
|
||||
$permissionType = $matches[2];
|
||||
|
||||
// 메뉴 정보 조회
|
||||
$menu = Menu::find($menuId);
|
||||
$menuName = $menu ? $menu->name : '알 수 없는 메뉴';
|
||||
|
||||
// 권한 타입별 설정
|
||||
$permissionConfig = getPermissionConfig($permissionType);
|
||||
|
||||
// HTML 생성
|
||||
$html = '<div class="flex items-center gap-2">';
|
||||
|
||||
// 메뉴 태그 (회색 배지)
|
||||
$html .= sprintf(
|
||||
'<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full bg-gray-100 text-gray-700">메뉴 #%d</span>',
|
||||
$menuId
|
||||
);
|
||||
|
||||
// 메뉴명
|
||||
$html .= sprintf(
|
||||
'<span class="text-sm text-gray-700">%s</span>',
|
||||
htmlspecialchars($menuName)
|
||||
);
|
||||
|
||||
// 권한 타입 배지
|
||||
$html .= sprintf(
|
||||
'<span class="inline-flex items-center px-2 py-0.5 text-xs font-semibold rounded-full %s" title="%s">%s</span>',
|
||||
$permissionConfig['class'],
|
||||
$permissionConfig['label'],
|
||||
$permissionConfig['badge']
|
||||
);
|
||||
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
// 패턴이 일치하지 않으면 원본 반환
|
||||
return '<span class="text-sm text-gray-900">' . htmlspecialchars($permissionName) . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 권한 타입별 설정 반환
|
||||
*/
|
||||
function getPermissionConfig(string $type): array
|
||||
{
|
||||
$configs = [
|
||||
'view' => [
|
||||
'badge' => 'V',
|
||||
'label' => '조회',
|
||||
'class' => 'bg-blue-100 text-blue-800',
|
||||
],
|
||||
'create' => [
|
||||
'badge' => 'C',
|
||||
'label' => '생성',
|
||||
'class' => 'bg-green-100 text-green-800',
|
||||
],
|
||||
'update' => [
|
||||
'badge' => 'U',
|
||||
'label' => '수정',
|
||||
'class' => 'bg-orange-100 text-orange-800',
|
||||
],
|
||||
'delete' => [
|
||||
'badge' => 'D',
|
||||
'label' => '삭제',
|
||||
'class' => 'bg-red-100 text-red-800',
|
||||
],
|
||||
'approve' => [
|
||||
'badge' => 'A',
|
||||
'label' => '승인',
|
||||
'class' => 'bg-purple-100 text-purple-800',
|
||||
],
|
||||
'export' => [
|
||||
'badge' => 'E',
|
||||
'label' => '내보내기',
|
||||
'class' => 'bg-sky-100 text-sky-600',
|
||||
],
|
||||
'manage' => [
|
||||
'badge' => 'M',
|
||||
'label' => '관리',
|
||||
'class' => 'bg-gray-100 text-gray-800',
|
||||
],
|
||||
];
|
||||
|
||||
return $configs[$type] ?? [
|
||||
'badge' => strtoupper(substr($type, 0, 1)),
|
||||
'label' => $type,
|
||||
'class' => 'bg-gray-100 text-gray-800',
|
||||
];
|
||||
}
|
||||
@endphp
|
||||
|
||||
<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-sm font-semibold text-gray-700 uppercase tracking-wider">ID</th>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">권한명</th>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">테넌트</th>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">가드</th>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">할당된 역할</th>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">할당된 부서</th>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">생성일</th>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">수정일</th>
|
||||
<th class="px-6 py-3 text-right text-sm font-semibold text-gray-700 uppercase tracking-wider">액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse($permissions as $permission)
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ $permission->id }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
{!! formatPermissionName($permission->name) !!}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $permission->tenant?->company_name ?? '전역' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full {{ $permission->guard_name === 'web' ? 'bg-blue-100 text-blue-800' : 'bg-blue-100 text-blue-800' }}">
|
||||
{{ $permission->guard_name }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-900">
|
||||
@if($permission->roles->isNotEmpty())
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@foreach($permission->roles as $role)
|
||||
<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full bg-green-100 text-green-800">
|
||||
{{ $role->name }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<span class="text-gray-400">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-900">
|
||||
@if($permission->departments->isNotEmpty())
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@foreach($permission->departments as $department)
|
||||
<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full bg-yellow-100 text-yellow-800">
|
||||
{{ $department->name }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<span class="text-gray-400">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $permission->created_at?->format('Y-m-d H:i') ?? '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $permission->updated_at?->format('Y-m-d H:i') ?? '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<a href="{{ route('permissions.edit', $permission->id) }}"
|
||||
class="text-blue-600 hover:text-blue-900 mr-3">
|
||||
수정
|
||||
</a>
|
||||
<button onclick="confirmDelete({{ $permission->id }}, '{{ $permission->name }}')"
|
||||
class="text-red-600 hover:text-red-900">
|
||||
삭제
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="9" class="px-6 py-12 text-center text-gray-500">
|
||||
등록된 권한이 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 페이지네이션 -->
|
||||
@include('partials.pagination', [
|
||||
'paginator' => $permissions,
|
||||
'target' => '#permission-table',
|
||||
'includeForm' => '#filterForm'
|
||||
])
|
||||
Reference in New Issue
Block a user