feat: dynamic role-based permission system
- Add permissions & role_permissions tables with migration - Add PermissionMiddleware for route-level access control - Add CheckRole middleware for role-based fallback - Add PermissionController with UI (Settings > Hak Akses Role) - Add @canPermission Blade directive for view-level checks - Convert all routes to use dynamic permission middleware - Add canPermission() method to User model - All access rights configurable via admin UI
This commit is contained in:
@@ -20,6 +20,8 @@ $faIcons = [
|
||||
'cog' => 'fa-solid fa-gear',
|
||||
'users-cog' => 'fa-solid fa-user-gear',
|
||||
'database' => 'fa-solid fa-database',
|
||||
'arrows-rotate' => 'fa-solid fa-arrows-rotate',
|
||||
'lock' => 'fa-solid fa-lock',
|
||||
];
|
||||
$faClass = $faIcons[$icon] ?? 'fa-solid fa-circle';
|
||||
@endphp
|
||||
|
||||
@@ -44,24 +44,55 @@
|
||||
</button>
|
||||
</div>
|
||||
<nav class="mt-2 px-2 space-y-1">
|
||||
@canPermission('access-dashboard')
|
||||
<x-nav-item href="{{ route('dashboard') }}" icon="home" :active="request()->routeIs('dashboard')">Dashboard</x-nav-item>
|
||||
@endcanPermission
|
||||
@canPermission('input-violations')
|
||||
<x-nav-item href="{{ route('violations.create') }}" icon="plus-circle" :active="request()->routeIs('violations.create')">Input Pelanggaran</x-nav-item>
|
||||
@endcanPermission
|
||||
@canPermission('view-violations')
|
||||
<x-nav-item href="{{ route('violations.index') }}" icon="exclamation-triangle" :active="request()->routeIs('violations.index')">Data Pelanggaran</x-nav-item>
|
||||
@endcanPermission
|
||||
@canPermission('view-students')
|
||||
<x-nav-item href="{{ route('students.index') }}" icon="users" :active="request()->routeIs('students.index*')">Data Siswa</x-nav-item>
|
||||
@endcanPermission
|
||||
@canPermission('manage-attendance')
|
||||
<x-nav-item href="{{ route('attendances.index') }}" icon="clipboard-check" :active="request()->routeIs('attendances*')">Presensi Siswa</x-nav-item>
|
||||
@endcanPermission
|
||||
@canPermission('view-sp-letters')
|
||||
<x-nav-item href="{{ route('sp-letters.index') }}" icon="document-text" :active="request()->routeIs('sp-letters.*')">Surat Peringatan</x-nav-item>
|
||||
@endcanPermission
|
||||
|
||||
@canPermission('categories-manage')
|
||||
<div class="pt-4 mt-4 border-t border-gray-200">
|
||||
<p class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Pengaturan</p>
|
||||
<p class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Master Data</p>
|
||||
</div>
|
||||
<x-nav-item href="{{ route('settings.categories') }}" icon="tag" :active="request()->routeIs('settings.categories*')">Kategori Pelanggaran</x-nav-item>
|
||||
<x-nav-item href="{{ route('settings.violation-types') }}" icon="list" :active="request()->routeIs('settings.violation-types*')">Jenis Pelanggaran</x-nav-item>
|
||||
<x-nav-item href="{{ route('settings.thresholds') }}" icon="chart-bar" :active="request()->routeIs('settings.thresholds*')">Ambang SP</x-nav-item>
|
||||
@endcanPermission
|
||||
|
||||
@canPermission('settings-manage')
|
||||
<div class="pt-4 mt-4 border-t border-gray-200">
|
||||
<p class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Administrasi</p>
|
||||
</div>
|
||||
@endcanPermission
|
||||
@canPermission('sync-data')
|
||||
<x-nav-item href="{{ route('settings.sync') }}" icon="refresh" :active="request()->routeIs('settings.sync*')">Sinkronisasi</x-nav-item>
|
||||
@endcanPermission
|
||||
@canPermission('users-manage')
|
||||
<x-nav-item href="{{ route('users.index') }}" icon="users-cog" :active="request()->routeIs('users.*')">Manajemen User</x-nav-item>
|
||||
<x-nav-item href="{{ route('settings.permissions') }}" icon="lock" :active="request()->routeIs('settings.permissions*')">Hak Akses Role</x-nav-item>
|
||||
@endcanPermission
|
||||
@canPermission('backup-database')
|
||||
<x-nav-item href="{{ route('settings.backup') }}" icon="database" :active="request()->routeIs('settings.backup*')">Backup Database</x-nav-item>
|
||||
<x-nav-item href="{{ route('settings.reset') }}" icon="refresh" :active="request()->routeIs('settings.reset*')">Reset Aplikasi</x-nav-item>
|
||||
@endcanPermission
|
||||
@canPermission('reset-application')
|
||||
<x-nav-item href="{{ route('settings.reset') }}" icon="arrows-rotate" :active="request()->routeIs('settings.reset*')">Reset Aplikasi</x-nav-item>
|
||||
@endcanPermission
|
||||
@canPermission('settings-manage')
|
||||
<x-nav-item href="{{ route('settings.index') }}" icon="cog" :active="request()->routeIs('settings.index')">Pengaturan</x-nav-item>
|
||||
@endcanPermission
|
||||
</nav>
|
||||
|
||||
{{-- Footer --}}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Hak Akses Role')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900">Hak Akses Role</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">Atur permission/fitur apa saja yang bisa diakses oleh setiap role pengguna.</p>
|
||||
</div>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg text-sm text-green-700">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('settings.permissions.update') }}">
|
||||
@csrf
|
||||
|
||||
<div class="overflow-x-auto bg-white rounded-xl shadow-sm border border-gray-200">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 border-b border-gray-200">
|
||||
<th class="text-left px-4 py-3 font-semibold text-gray-700">Permission</th>
|
||||
@foreach ($roles as $role)
|
||||
<th class="text-center px-3 py-3 font-semibold text-gray-700 capitalize">{{ $role }}</th>
|
||||
@endforeach
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($permissions as $group => $groupPermissions)
|
||||
<tr class="bg-gray-100 border-b border-gray-200">
|
||||
<td colspan="{{ count($roles) + 1 }}" class="px-4 py-2 font-bold text-gray-700 uppercase text-xs tracking-wider">
|
||||
{{ $group === 'master-data' ? 'Master Data' : ($group === 'administrasi' ? 'Administrasi' : $group) }}
|
||||
</td>
|
||||
</tr>
|
||||
@foreach ($groupPermissions as $perm)
|
||||
<tr class="border-b border-gray-100 hover:bg-gray-50">
|
||||
<td class="px-4 py-3">
|
||||
<div class="font-medium text-gray-800">{{ $perm->label }}</div>
|
||||
<div class="text-xs text-gray-400 font-mono">{{ $perm->key }}</div>
|
||||
</td>
|
||||
@foreach ($roles as $role)
|
||||
@php
|
||||
$checked = in_array($perm->id, $rolePermissions[$role] ?? []);
|
||||
$disabled = ($role === 'admin');
|
||||
@endphp
|
||||
<td class="text-center px-3 py-3">
|
||||
<input type="checkbox"
|
||||
name="permissions[{{ $role }}][]"
|
||||
value="{{ $perm->id }}"
|
||||
{{ $checked ? 'checked' : '' }}
|
||||
{{ $disabled ? 'disabled' : '' }}
|
||||
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 cursor-pointer">
|
||||
</td>
|
||||
@endforeach
|
||||
</tr>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex items-center justify-between">
|
||||
<p class="text-xs text-gray-400">Role <strong>Admin</strong> selalu memiliki semua hak akses (tidak bisa diubah).</p>
|
||||
<button type="submit" class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition font-medium">
|
||||
Simpan Hak Akses
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user